The real point, of course, is that FPIC can do more. That is, it is not merely a special purpose language for one type of picture, but is infinitely extensible to a variety of picture-drawing domains.
Our last example is a collection of functions to draw pie charts. This package contains the function pieChart, which takes a list of pairs, each consisting of a percentage and a slice-drawing function, and draws the slices. A slice-drawing function is a function from an angle (in degrees) to a picture; that picture will normally be a wedge of a circle centered at (0, 0) and starting at the given angle. The package contains a variety of functions for creating slice-drawing functions. They are shown in Figure 1.
The function slice takes a collection of arguments and returns a slice-drawing function. The arguments are: an external label to be drawn outside the slice; the percentage of the pie that this slice should occupy; and a color with which to fill the slice.
fun pieChart radius pieList = let fun pieBuilder n [(a,pfun)] = pfun radius n | pieBuilder n ((a,pfun)::slices) = let val newangle = n+((a/100.0) * 360.0) in (pfun radius n) seq (pieBuilder newangle slices) end in pieBuilder 0.0 pieList end; fun slice lab percent color = let fun makeslice radius startAngle = let val endAngle = startAngle + ((percent/100.0) * 360.0) val pieSlice = (wedge radius startAngle endAngle) val filledPie = pieSlice withFillColor color val midAngle = (startAngle + endAngle)/2.0 val labelDist = radius/4.0 val xPt = (radius+labelDist)*(dcos midAngle) val yPt = (radius+labelDist)*(dsin midAngle) val extLabel = (text lab) centeredAt (xPt, yPt) in filledPie seq extLabel end in (percent, makeslice) end; fun explodeSlice (percent, picfun) = (percent, fn rad => (fn startAngle => let val angleDelta = ((percent/100.0) * 360.0)/2.0 val centerAngle = startAngle + angleDelta val centerUnitVec = (dcos centerAngle, dsin centerAngle) in (picfun rad startAngle) offsetBy (scaleVec 1.0 centerUnitVec) end)); fun triangleSlice lab percent color = let fun makeslice radius startAngle = let val endAngle = startAngle + ((percent/100.0) * 360.0) val unitVec1 = (dcos startAngle, dsin startAngle) val unitVec2 = (dcos endAngle, dsin endAngle) val pieSlice = (triangle (0.0,0.0) (scaleVec radius unitVec1) (scaleVec radius unitVec2)) val filledPie = pieSlice withFillColor color val midAngle = (startAngle + endAngle)/2.0 val unitVec3 = (dcos midAngle, dsin midAngle) val bisect = midpoint (scaleVec radius unitVec1) (scaleVec radius unitVec2) val labelLoc = bisect ++ (scaleVec (radius/4.0) unitVec3) val extLabel = (text lab) centeredAt labelLoc in filledPie seq extLabel end in (percent, makeslice) end;
Here is an example:
pieChart 2.0 [(slice "A's" 20.0 cyan), (slice "B's" 25.0 green), (slice "C's" 30.0 blue), (slice "D's" 10.0 red), (slice "F's" 15.0 yellow)];
The definition of a slice-drawing function leaves a good deal of flexibility. The function explodeSlice takes a slice and moves it a certain distance away from the center of the pie:
pieChart 2.0 [(slice "A's" 20.0 cyan), explodeSlice (slice "B's" 25.0 green), (slice "C's" 30.0 blue), explodeSlice (slice "D's" 10.0 red), (slice "F's" 15.0 yellow)];
We can also change the shape of a slice. The function triangleSlice draws triangular slices.
pieChart 2.0 [(triangleSlice "A's" 20.0 cyan), (triangleSlice "B's" 25.0 green), (triangleSlice "C's" 30.0 blue), (triangleSlice "D's" 10.0 red), (triangleSlice "F's" 15.0 yellow)];
explodeSlice works for any slice-drawing function:
pieChart 2.0 [(triangleSlice "A's" 20.0 cyan), explodeSlice (triangleSlice "B's" 25.0 green), (triangleSlice "C's" 30.0 blue), explodeSlice (triangleSlice "D's" 10.0 red), (triangleSlice "F's" 15.0 yellow)];