Buster 2.2 - Application Orchestration redux

Written by Jeff Heard on May 28th, 2009

I’ve been working heavily on the Big Board lately, and now I’m finally basically done with it.  As a result, I’ve modified, updated, and upgraded buster considerably since the first release, and it is now a pretty mature, high performance, and powerful engine for application orchestration.  There are a few things about it that bug me; mainly that there tends to be a lot more IO code than I usually like to see, but on the other hand, this tends to be in the behaviour part and the actual orchestration in terms of events and behaviour relations are purely functional and their semantics are well exposed.  One thing I am thinking of changing is the way that events are named, to an EventName type rather than relying on group, source, and name, since some of these seem to be irrelevant in certain cases.

One thing I am particularly satisfied with is that behaviours seem to be self-segmenting and I’ve not had to wedge anything into the form of a behaviour or worry about having to wedge something non-behaviour like into the framework because of poor forethought.   The metaphor seems to work well.  I have found that since I use strings to name events rather than enumerations, it’s helpful to have a module in the code somewhere that ties constants to the names to encourage consistency and type-safety.

Anyway, in a nutshell, here’s how application orchestration via Buster works by example:

iwidth = 1024
iheight = 1024

programBehaviour =
       mouseSelectionBehaviour
   >~> selectionBehaviour
   >~> zoomAndPanBehaviour
   >~> (uiResponses
        |~| translateVisibles
        |~| mouseCoordinateAdjustmentBehaviour)
   >~> dialogueRaisingBehaviour
   >~> dialogueResponseBehaviour
   >~> annotationCreationBehaviour
   >~> annotationFinishingBehaviour
   >~> annotationsTouchedBehaviour
   >~> loadMapMetadataBehaviour
   >~> (loadTilesBehaviour |~| loadAnnotationsBehaviour)
   >~> renderBehaviour

widgets = [
  initializeBus "The Big Board, by the Renaissance Computing Institute. v3.0a" iwidth iheight
, configFileWidget "thebigboard.ini"
, paletteGeometryWidget]

main = boilerplateGLMain widgets programBehaviour

Yes.  That plus the imports is the entire main module of The Big Board.  In point of fact the entirey of the Big Board, despite its complexity, is just a little under 800 lines of application-specific Haskell code.  I’ve highlighted in blue all the behaviours and widgets that are part of Hieroglyph and in green the ones that are part of buster.  Here we declare the dependency structure of the component program behaviours in the function programBehaviour.  The >~> operator designates that the right operand depends on the left operand.  The |~| operator declares that the two operands are independent and are thus functionally concurrent.   So we perform mouse-based selection, select items, and go through the normal chain of UI behaviours and then finally hit the render behaviour, which detects if anything’s changed in the scene and tells Hieroglyph to rerender if it has.  Main is a single line that calls the boilerplate Hieroglyph main, which initializes the Gtk and OpenGL, creates a window (which can be accessed later and modified for those who want more than a single main window in their application), and starts the bus a’-running.

Now let’s delve into the anatomy of a behaviour…

mouseCoordinateAdjustmentBehaviour b =
 pollFullyQualifiedEventWith b "Mouse" "Hieroglyph.KeyboardMouseWidget" "Position" $ \evt -> do
    let [x,y] = fromEDoubleL . fromJust . lookup "coords" . fromEAssocL . head . eventdata $ evt
        [ox,oy] = fromEDoubleL . fromJust . lookup "origin" metaL
        scale = fromEDouble . fromJust . lookup "scale" metaDataL
        metaL = maybe ([("origin",EDoubleL [0,0]),("scale",EDouble 1)])
                      (fromEAssocL . head . eventdata)
                      (eventForQName "Environment" "Map" "Metadata" b)
    glcoords <- reverseMouseCoords b x y
    let Point x' y' = reprojectPoint (Point ox oy) scale glcoords
    rerender <- produce "Hieroglyph" "Hieroglyph" "Rerender" Persistent []
    coords <- produce "Visible" "Hieroglyph" "coords text" Persistent [EOther (Geometry (coordsText x' y')]
    return [rerender, coords]

Again, the buster and Hieroglyph highlighting rules apply.  Here, we poll events named (Mouse,Hieroglyph.KeyboardMouseWidget,Position) that sit on the bus and apply the associated behavioural thunk to them.  Polling means that the event will stay on the bus even after the behaviour is applied.  We then reverse the mouse coords to fit GL coordinates and  offset it from the origin by the proper amount.  The coordinates are then displayed in a box (the coordsText function maps the x and y values to a visible representation and this visible representation is put on the bus as an event in the group “Visible”, which indicates to Hieroglyph that it should be rendered.

There are times where the poll/consume event helper functions aren’t appropriate.  In this case, instead of returning a list of bus differences, we have to return a “future” of a list of bus differences.  This is done by replacing the last line of the behaviour with:

future bus . return $ [list of Insertion evt | Deletion evt]

From there, most everything else about buster can be figured out based on this and the previous posts on the buster model and the Haddock documentation.

Major updates to Buster

Written by Jeff Heard on April 3rd, 2009

The Big Board coding is going well, and as a side effect, I’ve got quite a few new widgets and behaviours for Buster that will interest people who want to use it.  The newest version is 0.99.5, and a brief breakdown of what’s changed since the initial release is:

  • added a safeShow function to EData that prints the first n characters of an EData, hiding EOther and EByteStrings from being shown.
  • moved the EData type out of Event so that people who don’t want to use any builtin events can do so without polluting the type structure.  Also makes for more explicit type signatures. Now requires Event/Behaviour/Widget [EData a] where it used to just require Event a/Behaviour a/Widget a.
  • added App.Widgets.Environment - get environment data as persistent events
    • commandLineArgsWidget - get the command line args as an event
    • configFileWidget - make variables from a config file events
    • programNameWidget - make the program name an event
    • environmentWidget - make all environment variables events
  • added App.Behaviours.FileOps - Generic file operations.  Probably should add file appending sometime.
    • readFileBehaviour - make the contents of a file an event.  Uses “read” for contents.
    • readFileBehavourNR - make the contents of a file an event, but for events where EData a’s a isn’t an instance of Read
    • writeFileBehaviour - make a file on disc, using “show” for contents.
    • writeFileBehaviourNR - make a file on disc, using “show” for all contents but EOther
    • encodeFileBehaviour - make a file on disc using Data.Binary.encodeFile
    • encodeFileBehaviourNB - same as above, but throwing an exception event on EOther
    • decodeFileBehaviour - read a file from disc using Data.Binary.decodeFile
    • decodeFileBeahviourNB - same as above, but throwing an exception event on EOther
  • added App.Behaviours.Exception - Generic exception handling that provides more information to the user than Haskell’s
    • unhandledExceptionBehaviour - kill the program gracefully and print all exceptions to stdout.
    • disregardExceptionsFromSource - ignore all exceptions from source
    • disregardExceptionsNamed - ignore all exceptions with a certain name
    • printAndDisregardExceptionsFromSource - print and then consider the exceptions from a particular source handled.
    • printAndDisregardExceptionsNamed - print and then consider the exception with a particular name handled.
    • logAndDisregardExceptionsFromSource - print to file and then consider the exceptions from a source handled .
    • logAndDisregardExceptionsNamed -  print to file and then consider the exception with a particular name handled.

ANN: Buster, the not quite entirely unlike FRP library.

Written by Jeff Heard on April 1st, 2009

Note: I just released an update to 0.99.2.  Now included are Widgets for getting environment vars and command line args and the EData type has been extracted from Event to be separate.

People seem to like the idea so far, so I’m releasing the thing I posted about yesterday on Hackage and we’ll see who uses it.  I’m calling it Buster for now, because I’m bad at naming my own projects.  Haddock documentation is here. The following is a brief example of how to use the library:

module Main where

import App.Widgets.GtkMouseKeyboard
import App.Behaviours.PrintEvents
import App.EventBus

import Control.Concurrent
import qualified Graphics.UI.Gtk as Gtk
import qualified System.Glib.MainLoop as Gtk

main = do
    Gtk.unsafeInitGUIForThreadedRTS -- buster is multithreaded, so you will need this.
    window <- Gtk.windowNew -- create a window to bind the mouse keyboard widget to
    Gtk.windowSetDefaultSize window 800 600
    Gtk.widgetShowAll window

    let mk = bindMouseKeyboardWidget (Gtk.castToWidget window) -- bind the widget

    -- bus is our main loop
    bus [mk]                                                   -- add the widget to the bus
        (Gtk.mainContextIteration Gtk.mainContextDefault True) -- the bus executes this thunk to collect data from widgets, if necessary (to support the various main loops out there, like GLUT and Gtk)
        printEventsBehaviour                                   -- add the behaviour to the bus

Compiling this after install buster will get you a single Gtk window which tracks your mouse position, scroll wheel direction, button press releases and keyboard and outputs it via the printEventsBehaviour, which is pretty useful overall for debugging. Output looks like this:

name:     Position
source:   GtkWindow.KeyboardMouseWidget
group:    Mouse
ttl:      Iterations 1
emitTime: 20:10:26
580.0
85.0

By inspecting the source of GtkMouseKeyboard and PrintEvents you can see in a pretty straightforward manner (I think anyway) how to code your own behaviours and widgets.  I reccommend sticking to the various default datatypes and not depending on the “a” of “Event a” to be anything in particular, because that means that your widgets will be compatible with everyone else’s.  I intend the “a” to be determined by the actual application developer to do special purpose things.  If people are interested, I’ll open  up a space on here where you can upload your own widgets (as cabalized source dists) and I will compile them into one library called buster-widgets-community which I’ll update whenever other people add widgets.  If not, there will be some basic support for GL and XML-RPC webservices that i’ll add as I need it for The Big Board.

Almost, but not quite entirely unlike FRP.

Written by Jeff Heard on March 31st, 2009

Yes, it’s to solve a particular problem.  And yes, this is a rough draft of an explanation of how it works.  I’ve not even really solidified the vocabulary yet, but I have this module, EventBus, which couches a large, abstract, interactive (both with the user and the system), multicomponent application in terms of a bus, inputs, behaviours, and events.

  • Time is continuous and infinite.
  • An event is a static, discrete item associated with a particular time.
  • The bus is the discrete view of event in time at an instant.
  • A widget is an IO action that assigns events to a particular time based only upon sampling the outside world (other events and behaviours are irrelevant to it).  e.g. a Gtk Button is a widget, a readable network socket is an widget, the mouse is an widget, the keyboard is an widget, a multitouch gesture engine is a widget.
  • A behaviour is a continuous item — it exists for the entire program and for all times — which maps events on the bus to other events on the bus.  It is an IO action as well — where widgets only sample the outside world and are in a sense read only, behaviours encapsulate reading and writing.

In a sense a behaviour is an application by itself. Examples of behaviours are: writing a file (the file itself is an Event), opening and reading a file, rendering data to the screen, and so on.  Behaviours also encompass more mundane tasks: translating mouse button up and down events that are close together in time to click, double click, and triple click events.  Behaviours are composable and with the presence of a passthrough behaviour form a monoid.  An application is a composition of 1 or more behaviours and a “running” application are those behaviours applied to a sample of time (as we have defined it earlier) and the real world.

Behaviours are attached to the bus.  They are composed by relating their sample of time, their bus, to the sample that other behaviours receive. Thus we define three combinators that relate events: behind, infront, and beside (given by operators (<~<, >~>, and |~| respectively)

If behaviour A is “behind” behaviour B, then behaviour A will see the time on the bus right after behaviour B.  Behaviour B’s filtering of events happens before behaviour A’s sample of time.

If behaviour A is “in front of” behaviour B, then behaviour A will see the time on the bus right before behaviour B.  Behaviour A’s filtering will happen before behavour B’s sample of time.

If behaviour A is “beside” behaviour B, then their order is undefined, and they may filter the bus concurrently.  This leads to a well defined merge of the two filtered buses as defined in the famous paper that inspired Unix diff(1).

So what’s the type of behaviour?

type Behaviour a = Bus a -> Future [Diff a]
data Diff a = Insertion (Event a) | Deletion (Event a)

Future encapsulates the notion that the behaviour doesn’t define where in time the events it introduces are, only that they exist in some time after the time the Behaviour samples.  Diffs encode the notion that time after the Behaviour’s sample can be described by insertions or deletions of events that exist in time before the Behaviour’s sample.

Now let’s enumerate what exists in an event.

data Event a = Event {
    name :: String   -- a name that is unique within the group of events
  , group :: String  -- together with name and src, part of the fully qualified name of the event.
  , src :: String    -- the source of the event
  , eventdata :: [EData a]  -- the data associated with the event
  , timespan :: TimeSpan    -- how long after the event appears on the bus
  , time :: UTCTime }       -- the time that the event appeared on the bus

data TimeSpan = Persistent | Time DiffTime | Iterations Int
data EData a = EString String | EChar Char | EStringL [String] | EInt Int | EIntL [Int] | EDouble Double | EDoubleL [Double] | EOther a

The various names give the behaviours various ways of sampling events on the bus.  The event data defines some basic types and allows for a user defined type as well, and the data itself is a collection (I chose a list, but in fact the ordering doesn’t have to be defined).  The timespan defines the width on the timeline that the event is visible from its start.  Some events are persistent until deletion.  Some events, like mouse clicks, can be defined using Iteration so that they disappear after it’s clear no behaviour will consume them.  Other events exist for some discrete time over the continuous time stream.

Here’s a diagram of how the system works as a whole:

bus1

If people don’t recoil in terror over this post, I might even release it to the wild.  So far I’ve incorporated a behaviour that encapsulates the Gtk mouse/keyboard event gamut and one that handles Renci’s own multitouch library, and I plan to integrate it with other things as I continue developing the Big Board application.  It should be relatively straightforward, even to write a function that generically wraps any simple Gtk Widget and several of the more complex ones into the bus.  Then other things like network collections, web service clients, and the like could become behaviours as well.

Anyway, let me know what you think…

New version of Hieroglyph released.

Written by Jeff Heard on February 24th, 2009

I’ve changed colors so that they’re now handled by Russell O’Connor’s excellent Data.Colour package, and changed Interactive up a bit to make it a bit easier to get at the canvas and data structures, point being to allow you to use Hieroglyph inside of a larger Gtk GUI more easily.  There shouldn’t be any code changes required at this time, though.  Can’t guarantee I won’t break the interface a bit in the future, but for now, you should see slightly better performance and have a lot more control over how color is displayed.  Thanks!

Heiroglyph: Interactively graph a spreadsheet in 99 lines of code.

Written by Jeff Heard on February 5th, 2009

screenshot-sparklines-for-breast-cancer-wisconsin-conttab-1

99 lines of code, even with imports. The app is simple, but it builds upon the sparkline example of the other day. In this app, we import an entire spreadsheet of tabular data and create sparklines for each column of data where the data is floating point. The user can sort the data by a column by clicking on the left side of a sparkline. This doesn’t do a lot of error checking, and only works on spreadsheets where the columnar data type is homogenous, but that’s a large class of datasets, and it simplifies our code considerably. First, our boring imports:

> module Main where
>
> import Graphics.Rendering.Hieroglyph
> import Graphics.Rendering.Hieroglyph.Scaffolds.Interactive
> import qualified Data.Map as Map
> import Data.Map (Map)
> import qualified Data.Set as Set
> import Data.Set (Set)
> import qualified Data.ByteString.Lazy.Char8 as C
> import Data.List (foldl',transpose,mapAccumL,sort)
> import System.Environment (getArgs)
> import Data.Maybe (fromJust)
> import qualified Data.IntMap as IntMap

Now we setup a bit of color for our visualization: attribute sets for describing thin black lines (the sparklines), thicker black lines (for the text), and the background color.

> blackstroke = plain{ strokeRGBA=black, linewidth=0.5 }
> blacktext = plain{ strokeRGBA=black }
> whitefill = plain{ fillRGBA=white, filled=True, outlined=False }

Then we setup our datatype that we’re visualizing. Note that we’re using the interactive scaffolding, so this will be an interactive application.

> data MyData = MyData {
>     interactiveScaffolding :: Scaffolding
>   , spreadsheet :: Map String [Double]
>   , ncolumns :: Int
>   }

Next we setup the data so that it’s responsive to the user input.

> instance Interactive MyData where
>     getInteractiveScaffolding = interactiveScaffolding
>     setInteractiveScaffolding a b = a{ interactiveScaffolding = b }
>
>     interactionFold dat geom

Yes, the 80 is arbitrary. It seems to work well, and to some extent, this is a draft application and isn’t meant to be perfect. Here, if the user clicks on the left side of the application, we check to see which sparkline the user is over and sort that column in ascending order.

>         | xcoord < 80 && getMouseLeftButtonDown dat = dat { spreadsheet = sortSpreadsheet dat }

Then we ignore all other inputs.

>         | otherwise = dat
>                 where Point xcoord ycoord = getMousePosition dat

Next is our familiar sparkline function from my blog post the other day. As a review, we remap all the values in the line to be inside the height of a sparkline and we take those and create a path out of them.

> sparkline width height (Point startx starty) values = path{ begin=point0 , segments=map Line points , attribs=blackstroke }
>    where (point0:points) = zipWith Point xvals yvals
>          xvals = iterate (+(width/n)) startx
>          yvals = map (remap mn mx starty (starty+height)) values
>          (mx,mn,_,_,_,n) = stats values

To visualize a whole spreadsheet, we start at the origin and work our way down using Map.mapAccum to create a visualization that is the same structure as our spreadsheet. We could simply pull the columns out of the spreadsheet and return a list of primitives instead of a map from name to primitives, but this way, we could filter the output from this function based on the structure of the original data. To me, this is closer to the intent of visualization; it’s less about the drawing aspect and more about the data, and manipulations on visualizations are based on the data, not the geometry or the screen.

> visSpreadsheet width height (Point startx starty) mp = snd . Map.mapAccum stackSparklines starty $ mp
>     where stackSparklines starty' values = (starty'+height, sparkline width height (Point startx starty') values)

This function places the names of the columns to the left of the sparklines.

> visSpreadsheetNames height starty names = snd . mapAccumL stackNames starty $ names
>     where stackNames starty' name = (starty'+height, text{ str=name, bottomleft = Point 5 starty', attribs=blacktext })

This merely defines our background rectangle

> background w h = rectangle{ width = w , height = h , attribs=whitefill }

And finally, vis calls our vis functions and also sets up the occlusion order, putting the names and sparklines on top of the background. The height of each sparkline is the number of sparklines divided by the height of the window.

> vis dat = visSpreadsheetNames (sy/ncf) 10 names
>           `over` visSpreadsheet (sx-offset) (sy/ncf) (Point offset 0) sheet
>           `over` background sx sy
>     where sx = getSizeX dat
>           sy = getSizeY dat
>           nc = ncolumns dat
>           ncf = fromIntegral nc
>           sheet = spreadsheet dat
>           Point xcoord ycoord = getMousePosition dat
>           offset = 6 * (fromIntegral . maximum . map length $ names)
>           names = Map.keys sheet

This is a simple function to read a spreadsheet and filter out columns that aren’t numeric. Note that it only checks the first item of each column, so it really doesn’t do sufficient error checking, however it’s faster than loading the entire spreadsheet to check to make sure each is numeric. Yes, we could have used Parsec here instead of defining our own isNumeric, but I wanted to keep down the number of libraries I was importing for tutorial’s sake.

> readSpreadsheet name = do
>      sheet <- (transpose . map (C.split '\t') . C.lines) `fmap` C.readFile name
>      return $ foldl' go Map.empty sheet
>   where go m (x:xs) | isNumeric xs = Map.insert (C.unpack x) (map (read . C.unpack) xs) m
>                     | otherwise = m
>         isNumeric = C.all isNumeral . head
>         isNumeral = (flip Set.member) nums
>         nums = Set.fromList "0123456789."

Remap a range of values to a different range. This is used in the sparkline function, and since our origin is at the top left and values go down, the seeming reversal of the first two arguments of the function is correct for our purposes.

> remap mx mn mn' mx' a = (mx'-mn') * (a-mn) / (mx-mn) + mn'

Once again, you should recognize this stats function from the blog post from the other day.

> stats (x:xs) = finish . foldl' stats' (x,x,x,x*x,1) $ xs
>     where stats' (mx,mn,s,ss,n) x = ( max x mx , min x mn , s + x , ss + x*x , n+1 )
>
> finish (mx,mn,s,ss,n) = (mx,mn,av,va,stdev,n)
>    where av = s/n
>          va = ss/(n-1) - n*av*av/(n-1)
>          stdev = sqrt va

This sorts the spreadsheet based on which sparkline the mouse pointer is on using the mapsort algorithm below.

> sortSpreadsheet dat = mapsort sheet (names !! item)
>     where sy = getSizeY dat
>           nc = ncolumns dat
>           ncf = fromIntegral nc
>           item = round $ (ycoord / sy) * ncf
>           Point _ ycoord = getMousePosition dat
>           names = Map.keys sheet
>           sheet = spreadsheet dat

This function is a little more elegant than it looks. It’s not terribly useful to do things this way here, but this is a lazy version of sorting the entire map. The more straightforward way to sort the map uses transpose to turn columns into rows, but also makes the whole thing head strict on the first element retrieved from the map. This is only head strict per column.

> mapsort mp key = Map.map reorder mp
>     where sort' vs = sort . zip vs $ indices
>           ordering = map snd . sort' $ (mp Map.! key)
>           nitems = (length ordering :: Int)
>           indices = iterate (+1) (0::Int)
>           reorder vs = (let arr = IntMap.fromList . zip indices $ vs in map (arr IntMap.!) ordering)

Now finally our very simple main. Take the argument from the command line that is our spreadsheet and create the visualization from it. Note that this GUI is not motion sensitive, as it really doesn’t need to be.

> main = do
>     [fname] <- getArgs
>     sheet <- readSpreadsheet fname
>     let dat = MyData scaffold sheet (Map.size sheet)
>     simpleGui dat vis ("Sparklines for " ++ fname)

Snippet: sorting the elements of a map

Written by Jeff Heard on February 5th, 2009

So in an application where you have a lot of data columns indexed by name, it’s often necessary to sort that data based on one or more of the columns.  You might be tempted to do this the straightforward way of

  1. Extract the data from the map using toList
  2. unzip and transpose to create rows instead of columns
  3. sort the resultant list using Data.List.sort or Data.List.SortBy
  4. transpose, zip, and put the data into a new map.

Now that’s a reasonable way to do things, but it raises one minor issue, which might not be so minor if you’ve got a really large dataset.  It’s “head-strict”.  The second you access one of the elements of the map, the entire sort procedure takes place for all the data in the map.  If you’re only needing to access a column or two of a large multicolumn dataset once it’s sorted and on top of that, are only needing a few elements of those columns, this is SERIOUSLY overkill.  Here’s a better way:

module MapSort where

import qualified Data.Map as Map
import qualified Data.List as List
import qualified Data.IntMap as IntMap

sort mp key = Map.map reorder mp
    where sort' vs = List.sort . zip vs $ indices
          ordering = map snd . sort' $ (mp Map.! key)
          indices = enumFrom (0::Int)
          reorder vs = IntMap.toAscList . IntMap.fromList $ zip ordering vs

This solution is head-strict per column instead of for the whole map.  How did we achieve that?  We created an ordering out of the sort key column and then applied that ordering on each one of the lists stored in the map individually.  Simple and efficient.  There are probably even more efficiencies to be had here, but this one works for me quite well.  From here, it should be straightforward to implement sortBy and even use a list of keys instead of a single key as the sorting key for the map.

Hieroglyph Haddock docs

Written by Jeff Heard on February 4th, 2009

Since Hackage is building with GHC 6.10 now and we’re still waiting on a final release of Gtk for 6.10, it’s not generating the Haddock docs for Hieroglyph.  I’m posting them here for the time being. That’s all!

Hieroglyph Part II, basic mouse/keyboard interactivity

Written by Jeff Heard on February 3rd, 2009
screenshot-sample-interactive-app
> module Main where
> import Graphics.Rendering.Hieroglyph
> import Graphics.Rendering.Hieroglyph.Scaffolds.Interactive

This is a very very simple demonstration of how to use Hieroglyph in an interactive setting. We draw a red circle under the mouse point, a blue triangle which will cover the circle if the mouse goes over it, a line from the origin to the mouse, and a bit of text showing us where the mouse is at the moment.

Heiroglyph uses records to simulate labeled functions, where parameters can be given in any order or omitted with a sensible default. Here, we change attributes from the Cairo default, called “plain”, which is imported from Graphics.Rendering.Hieroglyph to give us some sensible stroke and fill colors.

The first is black and sets the outline color only

> blackstroke = plain{ strokeRGBA = black }

Next is blue, where the blue is slightly transparent (faded).

> bluefill    = plain{ fillRGBA = fade 0.8 blue, filled=True, outlined=False }

Next is red, and sets the fill color only

> redfill     = plain{ fillRGBA = red, filled=True, outlined=False }

Finally white, for our background rectangle, and sets our fill color only.

> whitefill   = plain{ fillRGBA = white, filled=True, outlined=False }

The next bit is our visualization. A visualization in Hieroglyph is a function that maps data to a collection of primitives. In this case, the data is simply the interactive scaffolding, an element from an infinite list of user interactions. The visualization function is then folded over the input data. More complicated data can be used by implementing the class Graphics.Rendering.Hieroglyph.Scaffolds.Interactive.Interactive, but for our purposes, we don’t care to track anything else but the mouse Position.

This is our type signature. Note that I go ahead and generalize the vis to all Interactive types rather than the Scaffolding, even though we’re using it. This is more portable, and more correct, because really the vis only depends on the mouse position, which can be gotten from any instance of Interactive.

Note that I use BaseVisual as the return type. This is the return type of “over,” which takes any two Visuals and returns a BaseVisual where the left operand is declared to be possibly occluding the right operand.

> vis :: Interactive a => a -> BaseVisual
> vis a =

Note that at no time do we draw anything in this function, nor does the order in which I declare these items actually matter. The only thing that matters here to order is the combinator `over`, which takes two Visuals and returns a new Visual that contains both operands, but the left operand occludes the right where there is overlap.

First, our line from the origin:

>     [path{ segments = [Line (getMousePosition a)], attribs = blackstroke }

Next our text, and you note that we don’t declare the over-ness here, meaning that the occlusion between these two primitives is undefined.

>     ,text{ str = "mousePosition" ++ (show . getMousePosition $ a), bottomleft = Point 10 (getSizeY a-40), attribs = blackstroke }]

Now we declare that these two primitives occlude the next primitive, a blue triangle.

>     `over` polygon{ begin = (Point 100 100), segments = [Line (Point 200 300), Line (Point 300 100)], attribs = bluefill}

And then we declare a mouse pointer tracking red dot that is below the triangle when the triangle and dot interact with each other.

>     `over` arc{ center = getMousePosition a, radius = 4, attribs = redfill}

And finally, we declare the background rectangle.

>     `over` rectangle{ width = getSizeX a, height = getSizeY a, attribs = whitefill }

Main is very simple in Hieroglyph, generally. Since we’re not reading any input files, all we do is create a basic 800 by 600 motion sensitive GUI. Note that the motion-sensitive part does hurt performance a little, so if you don’t need it, use the simpleGui function instead. Both take initial input data, the scene function from the data to the scene, and a title for the window.

> main = simpleMotionSensitiveGui scaffold vis "sample interactive app"

Voila, it’s an app! Just to recap, here’s the program without all the commentary in between:

module Main where

import Graphics.Rendering.Hieroglyph
import Graphics.Rendering.Hieroglyph.Scaffolds.Interactive 

blackstroke = plain{ strokeRGBA = black }
bluefill    = plain{ fillRGBA = fade 0.8 blue, filled=True, outlined=False }
redfill     = plain{ fillRGBA = red, filled=True, outlined=False }
whitefill   = plain{ fillRGBA = white, filled=True, outlined=False }

vis :: Interactive a => a -> BaseVisual
vis a =
     [path{ segments = [Line (getMousePosition a)], attribs = blackstroke }
     ,text{ str = "mousePosition" ++ (show . getMousePosition $ a), bottomleft = Point 10 (getSizeY a-40), attribs = blackstroke }]
     `over` polygon{ begin = (Point 100 100), segments = [Line (Point 200 300), Line (Point 300 100)], attribs = bluefill}
     `over` arc{ center = getMousePosition a, radius = 4, attribs = redfill}
     `over` rectangle{ width = getSizeX a, height = getSizeY a, attribs = whitefill }

main = simpleMotionSensitiveGui scaffold vis "sample interactive app"

Introducing Hieroglyph. Purely functional 2D visualization on top of Cairo.

Written by Jeff Heard on February 3rd, 2009

As a followup to my tutorial last year on Beautiful Code, Compelling Evidence, the tutorial on doing infovis and visual analytics in Haskell, I set about trying to do a pure-functional wrapper to Cairo.  After a month or so of banging my and Conal’s head against it, I am ready to (with many MANY thanks to Conal Elliott for his help in getting me to think about this in a semantic way) release a first pass at purely functional 2D drawing, called Hieroglyph.

My original goal was to come up with something that simply produced no IO when creating a scene and was fast enough to create interactive visualizations.  What I was eventually directed toward was semantic design and trying to figure out what a 2D visualization means. Hieroglyph is a first cut at that — a compromise between strict semantic design and staying close enough to Cairo to not make things easy in Hieroglyph that create performance problems in Cairo.  Future versions of Hieroglyph or forks will be less based on Cairo and more based on semantic design, but for now, I’m satisfied with what I have.

So the first question is: What is a visualization?  My answer is that it’s a function from data to a visual.  A visual is an unstructured collection (bag) of primitives.  The data can be any data that is interesting to the user doing the visualizing.

To encapsulate the Visual, we have:

type BaseVisual = [Primitive] 

class Visual a where
  primitives :: a -> BaseVisual

In Graphics.Rendering.Hieroglyph.Visual, I define instances on each of the basic collection types in the Haskell standard library, Visual v =>  Data.Set.Set v, Data.IntMap.IntMap v, Data.Map.Map a v,  [v], and v itself.  This allows the programmer to be unconcerned with how the collection of visual primitives is structured, allowing in many cases a straightforward map from data to primitives with the same structure.  In addition, there are two relationships between Visuals that are important to describe.  One is occlusion.  To describe the occlusion characteristics of two visuals, I have defined the combinator “a `over` b” to describe a composed visual in which a occludes b.  The other is specificity.  There is a concept in visualization of level-of-detail, and this is encapsulated by the combinator “a `moreSpecific` b”, which declares that a is more specific than b.  Both over and moreSpecific describe partial orderings over primitives, which can be used for filtering, or used by the renderer to order and constrain drawing.

Now for the primitives.  A primitive is nothing more than a declaration of attributed geometry.  Our primitives in Hieroglyph are Arc, Path, Rectangle, Text, Union, Image, and Hidden.  I chose to implement primitives as “labeled” functions using the Haskell record modification syntax.  While you can declare a primitive using the type constructor, it is easier and more expedient to use the lower case version of each of the primitive constructors and modify them using the record modification syntax.  The Haddock documentation describes what each record field is for every primitive.

Over the next few days, I’ll be giving examples of Hieroglyph and talking a little bit more about my design process as I do.  In the meantime, enjoy Hieroglyph and let me know about any bugs!