How To Draw A Fractal Tree By Hand
A fractal tree drawn with PureScript
Last week, I attended Mathias Brandewinder's F# (and Clojure) dojo in Copenhagen, and had great fun drawing a fractal tree in F# together with other attendees. Afterwards, I started thinking that it'd be fairly easy to port the F# code to Haskell, but then I reconsidered. The combination of Haskell, Windows, and drawing sounded intimidating. This seemed a good opportunity to take PureScript for a spin, because that would enable me to draw the tree on an HTML canvas.
In case you're wondering, a fractal tree is simply a tree that branches infinitely in (I suppose) a deterministic fashion. Here's an example of the output of the code of this article:
           
        
This is my first attempt at PureScript, and I think I spent between five and ten hours in total. Most of them I used to figure out how to install PureScript, how to set up a development environment, and so on. All in all I found the process pleasing.
While it's a separate, independent language, PureScript is clearly a descendant of Haskell, and the syntax is similar.
Separating data from effects #
In functional programming, you routinely separate data from effects. Instead of trying to both draw and calculate branches of a tree in a single operation, you figure out how to first define a fractal tree as data, and then subsequently you can draw it.
A generic binary tree is a staple of functional programming. Here's one way to do it:
dataTree a=Leaf a|Node a (Tree a) (Tree a)
Such a tree is either a leaf node with a generically typed value, or an (intermediate) node with a value and two branches, which are themselves (sub)trees.
In a sense, this definition is an approximation, because a 'real' fractal tree has no leafs. In Haskell you can easily define infinite trees, because Haskell is lazily evaluated. PureScript, on the other hand, is eagerly evaluated, so infinite recursion would require jumping through some hoops, and I don't think it's important in this exercise.
          While the above tree type can contain values of any type, in this exercise, it should contain line segments. One way to do this is to define a          Line          record type:        
dataLine=Line {x ::Number,y ::Number,angle ::Number,length ::Number,width ::Number }
          This is a type with five labelled values, all of them numbers.          x          and          y          are coordinates for the origin of the line,          angle          defines the angle (measured in radians) from the origin, and          length          the length of the line. In a similarly obvious vein,          width          denotes the width of the line, although this data element has no impact on the calculation of the tree. It's purely a display concern.        
          Given the first four numbers in a          Line          value, you can calculate the endpoint of a line:        
endpoint :: forall r. { x::Number , y::Number , angle::Number , length::Number| r }->TupleNumberNumber endpoint line=-- Flip the y value because Canvas coordinate system points down from upper -- left corner Tuple (line.x+ line.length* cos line.angle) (-(-line.y+ line.length* sin line.angle))
          This may look more intimidating than it really is. The first seven lines are simply the (optional) type declaration; ignore that for a minute. The function itself is a one-liner, although I've formatted it on several lines in order to stay within an 80 characters line width. It simply performs a bit of trigonometry in order to find the endpoint of a line with an origin, angle, and length. As the code comment states, it negates the          y          value because the HTML canvas coordinate system points down instead of up (larger          y          values are further towards the bottom of the screen than smaller values).        
          The function calculates a new set of coordinates for the endpoint, and returns them as a tuple. In PureScript, tuples are explicit and created with the          Tuple          data constructor.        
          In general, as far as I can tell, you have less need of tuples in PureScript, because instead, you have          row type polymorphism. This is still a new concept to me, but as far as I can tell, it's a sort of static duck typing. You can see it in the type declaration of the          endpoint          function. The function takes a          line          argument, the type of which is          any          record type that contains          x,          y,          angle, and          length          labels of the          Number          type. For instance, as you'll soon see, you can pass a          Line          value to the          endpoint          function.        
Creating branches #
In a fractal tree, you calculate two branches for any given branch. Typically, in order to draw a pretty picture, you make the sub-branches smaller than the parent branch. You also incline each branch an angle from the parent branch. While you can hard-code the values for these operations, you can also pass them as arguments. In order to prevent an explosion of primitive function arguments, I collected all such parameters in a single data type:
dataFractalParameters=FractalParameters {leftAngle ::Number,rightAngle ::Number,shrinkFactor ::Number }
          This is a record type similar to the          Line          type you've already seen.        
          When you have a          FractalParameters          value and a (parent) line, you can calculate its branches:        
createBranches ::FractalParameters->Line->TupleLineLine createBranches (FractalParameters p) (Line line)=Tuple left rightwhereTuple x y= endpoint line left=Line { x: x, y: y, angle: pi* (line.angle/ pi+ p.leftAngle), length: (line.length* p.shrinkFactor), width: (line.width* p.shrinkFactor) } right=Line { x: x, y: y, angle: pi* (line.angle/ pi- p.rightAngle), length: (line.length* p.shrinkFactor), width: (line.width* p.shrinkFactor) }
          The          createBranches          function returns a tuple of          Line          values, one for the left branch, and one for the right branch. First, it calls          endpoint          with          line. Notice that          line          is a          Line          value, and because          Line          defines          x,          y,          angle, and          length          labels, it can be used as an input argument. This type-checks because of row type polymorphism.        
          Given the endpoint of the parent line,          createBranches          then creates two new          Line          values (left          and          right) with that endpoint as their origins. Both of these values are modified with the          FractalParameters          argument, so that they branch off to the left and the right, and also shrink in an aesthetically pleasing manner.        
Creating a tree #
Now that you can calculate the branches of a line, you can create a tree using recursion:
createTree :: Int->FractalParameters->Line->TreeLine createTree depth p line=if depth<=0thenLeaf lineelseletTuple leftLine rightLine= createBranches p line left= createTree (depth-1) p leftLine right= createTree (depth-1) p rightLineinNode line left right
          The          createTree          function takes a          depth          argument, which specifies the depth (or is it the height?) of the tree. The reason I called it          depth          is because          createTree          is recursive, and          depth          controls the depth of the recursion. If          depth          is zero or less, the function returns a          Leaf          node containing the input          line.        
          Otherwise, it calls          createBranches          with the input          line, and recursively calls          createTree          for each of these branches, but with a decremented          depth. It then returns a          Node          containing the input          line          and the two sub-trees          left          and          right.        
This implementation isn't tail-recursive, but the above image was generated with a recursion depth of only 10, so running out of stack space wasn't my biggest concern.
Drawing the tree #
          With the          createTree          function you can create a fractal tree, but it's no fun if you can't draw it. You can use the          Graphics.Canvas          module in order to draw on an HTML canvas. First, here's how to draw a single line:        
drawLine ::Context2D->Line->Eff (canvas ::CANVAS)Unit drawLine ctx (Line line)=doletTuple x' y'= endpoint line void$ strokePath ctx$do void$ moveTo ctx line.x line.y void$ setLineWidth line.width ctx void$ lineTo ctx x' y' closePath ctx
While hardly unmanageable, I was surprised that I wasn't able to find a pre-defined function that would let me draw a line. Perhaps I was looking in the wrong place.
With this helper function, you can now draw a tree using pattern matching:
drawTree ::Context2D->TreeLine->Eff (canvas ::CANVAS)Unit drawTree ctx (Leaf line)= drawLine ctx line drawTree ctx (Node line left right)=do drawLine ctx line drawTree ctx left drawTree ctx right
          If the input tree is a          Leaf          value, the first line matches and the function simply draws the line, using the          drawLine          function.        
          When the input tree is a          Node          value, the function first draws the          line          associated with that node, and then recursively calls itself with the          left          and          right          sub-trees.        
Execution #
          The          drawTree          function enables you to draw using a          Context2D          value, which you can create from an HTML canvas:        
mcanvas<- getCanvasElementById"canvas" let canvas= unsafePartial (fromJust mcanvas) ctx<- getContext2D canvas
          From where does the          "canvas"          element come? Ultimately, PureScript compiles to JavaScript, and you can put the compiled script in an HTML file together with a          canvas          element:        
< body >< canvasid = "canvas"width = "800"height = "800" ></ canvas >< scriptsrc = "index.js"type = "text/javascript" ></ script > </ body >
          Once you have a          Context2D          value, you can draw a tree. Here's the whole entry point, including the above canvas-finding code:        
main ::Eff (canvas ::CANVAS)Unit main=do mcanvas<- getCanvasElementById"canvas"let canvas= unsafePartial (fromJust mcanvas) ctx<- getContext2D canvaslet trunk=Line { x:300.0, y:600.0, angle: (pi/2.0), length:100.0, width:4.0 }let p=FractalParameters { leftAngle:0.1, rightAngle:0.1, shrinkFactor:0.8 }let tree= createTree10 p trunk drawTree ctx tree
          After it finds the          Context2D          value, it hard-codes a          trunk          Line          and a set of          FractalParameters. From these, it creates a tree of size 10 and draws the tree in the beginning of this article.        
You can fiddle with the parameters to your liking. For example, you can make the right angle wider than the left angle:
let p=FractalParameters { leftAngle:0.1, rightAngle:0.2, shrinkFactor:0.8 }
This produces an asymmetric tree:
           
        
In order to compile the code, I used this command:
$ pulp browserify -t html/index.js
          This compiles the PureScript code to a single          index.js          file, which is output to the          html          directory. This directory also contains the HTML file with the canvas.        
You can find the entire PureScript file in this Gist.
Summary #
It was fun to try PureScript. I've been staying away from JavaScript-based development for many years now, but if I ever have to do some client-side development, I may consider it. So far, I've found that PureScript seems viable for drawing. How good it is if you need to interact with 'normal' web-pages or SPAs, I don't know (yet).
If you have some experience with Haskell, it looks like it's easy to get started with PureScript.
How To Draw A Fractal Tree By Hand
Source: https://blog.ploeh.dk/2017/06/06/fractal-trees-with-purescript/
Posted by: hoffmanvalinarts.blogspot.com

0 Response to "How To Draw A Fractal Tree By Hand"
Post a Comment