Check out the new USENIX Web site.

5. FPIC Examples

The examples of this section show how the features of Standard ML, when combined with the primitives of FPIC, create a powerful language for constructing pictures.

In a functional language, some fancy drawings are relatively easy to do. For example, here is the "Sierpinski gasket" of order 3:


fun sierpinski 0 = dtriangle
  | sierpinski n =
       let val s = sierpinski (n-1) scaleWithPoint
                          ((0.5,0.5),(0.0,0.0))
       in s hseq s seq (s at ((width s)/2.0, height s))
       end;
sierpinski 3;

(pic scaleWithPoint (s, point) scales picture pic by a factor s while keeping point fixed.)

However, of more interest in practice is the ability to create reusable pieces of pictures to ease the programming burden. Here is where functional programming shines.

5.1 Defining lines

In FPIC, a line is simply a function from two points to a picture. Any drawing can be parameterized by a line to create a variety of effects.

Here is a simple function to draw trees. Its arguments are a picture representing the root of the tree and a list of pictures representing its children.

fun drawtree root subtrees =
   let val bottom = hseqtopsplist 1.0 subtrees
       val top = placePt root "s"
                  (bottom pt "n" ++ (0.0,1.0))
       val rootsouth = top pt "s"
   in group (top seq bottom seq
       (seqlist
          (map (fn p =? line rootsouth (p pt "n"))
          (pics bottom))))
   end;

It draws a tree with its nodes connected by lines:


let val t = drawtree dcircle [dbox, dbox]
  in drawtree dbox [t, t] end;

We can define a function drawTreeWithArrow that would draw arrows instead of lines, simply by replacing "line" by "arrow." However, we can do better in ML, making the line-drawing function a parameter. drawTree becomes

fun drawtree root subtrees linefun =

...exactly the same, until the end...
    (seqlist
      (map (fn p =? linefun rootsouth (p pt "n"))
      (pics bottom))))
end;

Then the tree above would be written as


let val t = drawtree dcircle [dbox, dbox] line
  in drawtree dbox [t, t] line end;

and we could also write


let val t = drawtree dcircle [dbox, dbox] arrow
  in drawtree dbox [t, t] arrow end;

Moreover, we can define our own line-drawing functions. We have seen earlier, in the cons-cell example, how to draw a curvy line. We use the same idea, except that here we want our curvy line to begin with a vertical leg rather than a horizontal one. Again, keep in mind that a line-drawing function is just a function from two points to a picture, nothing more or less:

fun curvedvline pt1 pt2 =
   bezier pt1 (pt1--(0.0,1.0))
   (pt2++(0.0,1.0)) pt2;


let val t =
    drawtree dcircle [dbox, dbox] curvedvline
in drawtree dbox [t, t] curvedvline end;

Two more examples are a line-drawing function that uses a "Manhattan geometry":

fun manline pt1 pt2 =
   let val ymid = (snd pt1 + snd pt2)/2.0
   in seqlist
        [line pt1 (fst pt1, ymid),
         line (fst pt1, ymid) (fst pt2, ymid),
         line (fst pt2, ymid) pt2]
   end;


let val t = drawtree dcircle [dbox, dbox] manline
  in drawtree dbox [t, t] manline end;

and a function that draws short lines of whatever kind:

fun shortline linefun pt1 pt2 =
   let val diff = pt2 -- pt1;
       val pt1' = pt1 ++ diff**(0.25,0.25);
       val pt2' = pt2 -- diff**(0.25,0.25)
   in linefun pt1' pt2'
   end;


let val t = drawtree dcircle [dbox, dbox]
                            (shortline arrow)
  in drawtree dbox [t, t] (shortline arrow) end;


let val t = drawtree dcircle [dbox, dbox]
                            (shortline manline)
  in drawtree dbox [t, t] (shortline manline) end;

5.2 Defining Sequencing Operators

The functions that put pictures together into larger pictures are of key importance. In FPIC, these are generally binary operations with infix syntax. The basic sequencing operation is seq, which simply draws two pictures without prejudice; hseq and vseq, among others, combine seq with some translation of the second picture.

Again, the ability to define new sequencing operations is of the greatest interest. A sequencing operation is a function from a pair of pictures to a picture. The constructed picture should include the two pictures.

A simple example is cseq, which aligns the centers of two pictures:

infix 6 cseq;
fun (p1 cseq p2) = (p1, center p1) align
                   (p2, center p2);


doval cseq (doval rotate 45.0)
      cseq (doval rotate 90.0)
      cseq (doval rotate 135.0);

Given a sequence operation seq, we often use the related operation seqlist, which applies to lists of pictures. Specifically:

The function mkseqlist creates the list version of a sequencing operation from the ordinary binary version.


val cseqlist = mkseqlist (op cseq);

val bullseye = cseqlist (map
       (fn rad => circle rad withFillColor
                      (1.0/rad, 1.0/rad, 1.0/rad))
     [5.0, 4.0, 3.0, 2.0, 1.0]);

The function mkseqfun is provided to facilitate the creation of new sequencing functions. Given two functions f and g from pictures to points, it creates the sequencing operation that, given two pictures p and q, draws the two so that fp and gq coincide. For example, in the tree-drawing function presented earlier, we used the sequencing operation hseqtopsplist, the list version of the sequencing operation hseqtopsp. The latter sequences two pictures horizontally with their tops aligned, adding some space between them. It is not built-in, but is defined as follows:

fun hseqtopsp gap =
   mkseqfun (fn p => northeast (p right 1.0))
            northwest;

The cons-cell example suggests another kind of sequencing: sequencing with an arrow. cellseq has as its arguments two cons cells--that is, two pictures that are presumed to have subpictures called cdr and car, respectively--and draws them a bit separated, with a curvy arrow. To allow more than one cell to be sequenced in this way, the combination of cells is defined to have car and cdr subpictures itself.

infix 7 cellseq;
fun cell1 cellseq cell2 =
   let val c = (group cell1)
                hseq (hspace 1.0)
                hseq (group cell2)
       val cells = c seq curvedharrow
                    (c nthpic 1 pic "cdr" pt "c")
                    (c nthpic 3 pic "car" pt "w")
   in addNamedPics cells
             [("car", cells nthpic 1 pic "car"),
              ("cdr", cells nthpic 3 pic "cdr")]
   end;

For this example, we have defined labelledCell, which draws a cons cell with a label in its car:


fun labelledCell L =
   cell seq (L centeredAt (cell pic "car" pt "c"));

labelledCell (text "A")
  cellseq
labelledCell (dcircle scaleTo (0.5, 0.5))
  cellseq
labelledCell (cell scale 0.3);


[ Prev | Top | Next ]