Click here to Skip to main content
15,867,453 members
Articles / High Performance Computing / Parallel Processing

Drawing Fractals via WPF

Rate me:
Please Sign up or sign in to vote.
4.86/5 (7 votes)
8 Nov 2010CPOL4 min read 54.1K   4.4K   25   8
Why recursion in C# works well when using WPF.

Recursion in C# works well when using WPF

The aim of this article is to illustrate how to draw a binary tree and a snowflake using WPF. Both examples appear useless, having no practical value, but they are part of a hot topic in parallel computing: fractals. You can define a binary tree recursively as a trunk attached to branches. The branches are attached to smaller branches that are attached to still smaller branches, and so on. That is, we will sort of write a program that continues drawing smaller and smaller branches until the new branches are less than one pixel long. At this point, the program obviously stops. The first place to start is layout management. In WPF, the Canvas control allows elements to be positioned absolutely using fixed coordinates. This layout container is the most similar to traditional Windows Forms, but it doesn't provide anchoring or docking features. The StackPanel control places elements in a horizontal or vertical stack. This layout container is typically used for small sections of a larger, more complex window. Both are used for layout management when building a WPF application. Here is a view of the draw after the start button control is clicked.

XML
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="Project.BinaryTree"
    x:Name="Window"
    Title="BinaryTree"
    Width="345"
    Height="300">
 <Window.Background>
  <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop
     Color="Black" Offset="0"/>
       <GradientStop
         Color="#FF0016FF" Offset="1"/>
  </LinearGradientBrush>
 </Window.Background>
 
 <Viewbox Stretch="Uniform">
 <StackPanel>
 <StackPanel Orientation="Horizontal"
     Margin="5,5,5,0">
 <Button Name="btnStart" Click="btnStart_Click"
   Width="98.502" Content="Start"
   FontFamily="Times New Roman" 
   FontWeight="Bold" FontSize="16"/>
 <TextBlock Name="tbLabel" Margin="20,5,0,0"/>
 </StackPanel>
 <Canvas Name="canvas1" Width="300"
   Height="300" Margin="5" Background="#FFBDFF00"/>
</StackPanel>
</Viewbox>
</Window>

1.JPG

Every stroke gets smaller as the entire tree grows by adding successively smaller branches.

2.JPG

To build this project without the solution file, fire up Visual Studio 2008 (2010) or Expression Blend, start a new C# WPF project called Project. Right-click the project icon and select "Add new item" to select a new window that you will name BinaryTree. Right-click the MainWindow.xaml file and select "Remove from project". You will only see a window with a button. It will draw a tree because of the code we'll write in the corresponding code-behind file.

Here is the code-behind file. Notice the use of recursion:

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Project
{
public partial class BinaryTree : Window
{
   private int II = 0;
   private int i = 0;

   public BinaryTree()
   {
     InitializeComponent();
   }

   private void btnStart_Click(object sender, RoutedEventArgs e)
   {
        canvas1.Children.Clear();
        tbLabel.Text = "";
         i = 0;
         II = 1;
         CompositionTarget.Rendering += StartAnimation;
   }

   private void StartAnimation(object sender, EventArgs e)
   {
       i += 1;
       if (i % 60 == 0)
       {
        DrawBinaryTree(canvas1, II,
        new Point(canvas1.Width / 2,
        0.83 * canvas1.Height),
        0.2 * canvas1.Width, -Math.PI / 2);
        string str = "Binary Tree - Depth = " +
        II.ToString();
        tbLabel.Text = str;
        II += 1;
        if (II > 10)
        {
         tbLabel.Text = "Binary Tree - Depth = 10. Finished";
         CompositionTarget.Rendering -=
         StartAnimation;
       }
   }
}

private double lengthScale = 0.75;
private double deltaTheta = Math.PI / 5;
private void DrawBinaryTree(Canvas canvas,
int depth, Point pt, double length, double theta)
{
    double x1 = pt.X + length * Math.Cos(theta);
    double y1 = pt.Y + length * Math.Sin(theta);
    Line line = new Line();
    line.Stroke = Brushes.Blue;
    line.X1 = pt.X;
    line.Y1 = pt.Y;
    line.X2 = x1;
    line.Y2 = y1;
    canvas.Children.Add(line);

    if (depth > 1)
    {
        DrawBinaryTree(canvas, depth - 1,
        new Point(x1, y1),
        length * lengthScale, theta + deltaTheta);
        DrawBinaryTree(canvas, depth - 1,
        new Point(x1, y1),
        length * lengthScale, theta - deltaTheta);
    }
    else
        return;
    }
}
}

This example holds a type of system concept. If we consider the text and graphics that output to the console screen, the system has had to write them to screen. Perhaps another API makes the text and/or object understandable to the human eye. Having said that, consider this line segment that has been trisected into lines of equal length. We form an equilateral triangle rising out of the middle segment.

The snowflake begins with an equilateral triangle. The program replaces each of the triangle's sides with a properly scaled and rotated version of the basic unit. The program then replaces each of the straight segments in the new figure with a smaller version of the basic unit. It replaces the newer straight segments with smaller and smaller versions of the basic unit until the snowflake reaches the desired depth. By the way, the linear gradients shown in this and the binary tree application are just there to make the appearance a little more stimulating. A white background for window is more than sufficient. So now, add a new WPF window to the project that we called Project. Remove the binary tree XAML file from the project. Name the new window SnowFlake. Here is a view of the before the button click and after:

XML
<Window
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   x:Class="Project.SnowFlake"
   x:Name="Window"
   Title="SnowFlake"
   Width="350"
   Height="300">
 <Window.Background>
   <LinearGradientBrush EndPoint="0.5,1" 
      StartPoint="0.5,0">
   <GradientStop Color="Black" Offset="0"/>
   <GradientStop
       Color="#FFFF0006" Offset="1"/>
  </LinearGradientBrush>
</Window.Background>
 
<Viewbox Stretch="Uniform">
<StackPanel>
<StackPanel Orientation="Horizontal" 
  Margin="5,5,5,0" Background="#FFA6FF00">
<Button Name="btnStart" Click="btnStart_Click" 
  Width="95.949" Content="Start" Height="29.618" 
  FontFamily="Times New Roman" 
  FontWeight="Bold" FontSize="16"/>
<TextBlock Name="tbLabel" Margin="20,5,0,0"/>
</StackPanel>
<Canvas Name="canvas1" Width="300" 
   Height="300" Margin="5">
</Canvas>
</StackPanel>
</Viewbox>
</Window>

4.JPG

5.JPG

Here is the corresponding code-behind file:

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Project
{
public partial class SnowFlake : Window
{
    private double distanceScale = 1.0 / 3;
    double[] dTheta = new double[4] { 0, Math.PI / 3,
    -2 * Math.PI / 3, Math.PI / 3 };
    Polyline pl = new Polyline();
    private Point SnowflakePoint = new Point();
    private double SnowflakeSize;
    private int II = 0;
    private int i = 0;
    public SnowFlake()
    {
        InitializeComponent();
        // determine the size of the snowflake:
        double ysize = 0.8 * canvas1.Height /
        (Math.Sqrt(3) * 4 / 3);
        double xsize = 0.8 * canvas1.Width / 2;
        double size = 0;
        if (ysize < xsize)
        size = ysize;
        else
        size = xsize;
        SnowflakeSize = 2 * size;
        pl.Stroke = Brushes.Blue;
    }
    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
        canvas1.Children.Clear();

        tbLabel.Text = "";
        i = 0;
        II = 0;
        canvas1.Children.Add(pl);
        CompositionTarget.Rendering += StartAnimation;
    }
    private void StartAnimation(object sender, EventArgs e)
    {
        i += 1;
        if (i % 60 == 0)
        {
            pl.Points.Clear();
            DrawSnowFlake(canvas1, SnowflakeSize, II);
            string str = "Snow Flake - Depth = " +
            II.ToString();
            tbLabel.Text = str;
            II += 1;
            if (II > 5)
            {
                tbLabel.Text = "Snow Flake - Depth = 5. Finished";
                CompositionTarget.Rendering -=
                StartAnimation;
            }
        }
    }
    private void SnowFlakeEdge(Canvas canvas,
            int depth, double theta, double distance)
    {
        Point pt = new Point();
        if (depth <= 0)
        {
            pt.X = SnowflakePoint.X +
            distance * Math.Cos(theta);
            pt.Y = SnowflakePoint.Y +
            distance * Math.Sin(theta);
            pl.Points.Add(pt);
            SnowflakePoint = pt;
            return;
        }
        distance *= distanceScale;
        for (int j = 0; j < 4; j++)
        {
            theta += dTheta[j];
            SnowFlakeEdge(canvas, depth - 1,
            theta, distance);
        }
    }

    private void DrawSnowFlake(Canvas canvas, double length, int depth)
    {
        double xmid = canvas.Width / 2;
        double ymid = canvas.Height / 2;
        Point[] pta = new Point[4];
        pta[0] = new Point(xmid, ymid + length / 2 *
        Math.Sqrt(3) * 2 / 3);
        pta[1] = new Point(xmid + length / 2,
        ymid - length / 2 * Math.Sqrt(3) / 3);
        pta[2] = new Point(xmid - length / 2,
        ymid - length / 2 * Math.Sqrt(3) / 3);
        pta[3] = pta[0];
        pl.Points.Add(pta[0]);
        for (int j = 1; j < pta.Length; j++)
        {
            double x1 = pta[j - 1].X;
            double y1 = pta[j - 1].Y;
            double x2 = pta[j].X;
            double y2 = pta[j].Y;
            double dx = x2 - x1;
            double dy = y2 - y1;
            double theta = Math.Atan2(dy, dx);
            SnowflakePoint = new Point(x1, y1);
            SnowFlakeEdge(canvas, depth, theta, length);
        }
    }
}
}

When the program recursively draws a line segment, it begins by drawing a third of the length of the segment in its current direction. It then turns 60 degrees and draws another third of the length of the segment. Next, it turns -120 degrees and draws another third. Finally, it turns 60 degrees again and draws yet another third of the length of the original segment. This is why you define the dTheta in the form:

C#
double[] dTheta = new double[4] { 0, Math.PI / 3, -2 * Math.PI / 3, Math.PI / 3 };

Recall from basic calculus that when y = f(x), then y, the dependent variable, is a function of the independent variable x. Y prime, or y', or f'(x), is the derivative of the equation set equal to f(x). If y = f(x) = x raised to the 3rd power, then f'(x) is 2x squared. It also might help to think of a recursive function as one that calls itself. The SnowFlakeEdge method recursively draws a segment (by adding the point to the polyline's point collection) that starts at snowflakePoint and moves in the direction theta by a length of distance. When it is finished, it leaves the value of the snowflakePoint to indicate the endpoint of the segment. This makes it easier to perform all of the necessary recursive calls one after another. The DrawSnowFlake method calls the SnowFlakeEndge method to draw each of the sides of the initial triangle. Inside this method, the Atan2 function takes the parameters dy and dx, which are the changes in a line segment's Y and X coordinates, respectively. It returns the angle with the tangent of dy/dx. Now, if the Start button is pressed, the program starts drawing the snowflake.

This article's content contains material that has been referenced from the works of Jack Xu, a leader in advanced .NET graphics and WPF development.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionhave you a code about an else geometrical fractals?? Pin
surenedilyan10-May-13 4:15
surenedilyan10-May-13 4:15 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey22-Feb-12 20:50
professionalManoj Kumar Choubey22-Feb-12 20:50 
GeneralMy vote of 5 Pin
Mamta D9-Nov-10 1:20
Mamta D9-Nov-10 1:20 
Questionwhere is the ZIP file code so people can run it themselves? Pin
Sacha Barber4-Nov-10 22:42
Sacha Barber4-Nov-10 22:42 
AnswerRe: where is the ZIP file code so people can run it themselves? Pin
logicchild8-Nov-10 16:07
professionallogicchild8-Nov-10 16:07 
GeneralRe: where is the ZIP file code so people can run it themselves? Pin
Sacha Barber8-Nov-10 21:28
Sacha Barber8-Nov-10 21:28 
GeneralGreat Article Pin
Nerevar4-Nov-10 19:30
Nerevar4-Nov-10 19:30 
GeneralRecursive Whitespace Pin
AspDotNetDev4-Nov-10 19:20
protectorAspDotNetDev4-Nov-10 19:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.