Facet-Plot - a 'Grammar of Graphics' implementation for Elm


I’ve been working on an implementation of the ‘Grammar of Graphics’ inspired by a number of similar libraries including ggplot and vega (sorry for leaving out links - I’m a new user and apparently can only put two in this post).

The repository is here and the examples are currently on the docs page.

The main idea behind the library is that a wide variety of different plots can be created by composing a small set of primitive visual marks and that data can be encoded as some visual attribute of those marks. This is in contrast to more conventional plotting libraries which offer a fixed set of chart ‘types’.

As the name suggests, the library supports faceting i.e. dividing the data and rendering a series of ‘small multiples’ (plots that share the same axis and scale), allowing easy comparison between subsets of your data.

The linked library deals with the declaration and ‘compilation’ of a plot to a scenegraph. There are separate libraries for the scenegraph itself, themes and for rendering.

The scenegraph is intended to be general purpose target for any future visualization libraries I might need thereby reducing the scope of writing one to generating the scenegraph.

At the moment there is only an SVG renderer but I would love to extend this to Canvas and WebGL in the future. The SVG renderer makes use of @folkertdev one-true-path-experiment and svg-path-lowlevel library - I would like to express my thanks for all your work if you happen to read this, it shortened the development time by weeks.

There is still quite a lot to do especially in tidying the implementation and writing useful documentation. I have made this public now as I actually want to use it in a work project so things should improve rapidly, time permitting!

I am particularly interested in feedback on the API at the moment.





I do happen to read this and man is this awesome!

If you have any feedback on using my packages, please let me know.

As to the API, reading through the example here, I think it would make sense to use records more, to make immediately clear what an (otherwise random) value is doing.

Using records doesn’t work everywhere (it limits function composition) and may take some more typing, but I think it would help with clarity, especially to newcomers. I think the opensolid/geometry package has found a nice balance between using function arguments and records.

This is a really cool project, and I’m exited to see where it will go.


Whoa, haven’t looked at it yet but sounds great!

Looks really cool. I hope to find the time to do some experiments with it and to see how awesome it really it. Nice to see that such high quality libraries are emerging in the Elm realm.

1 Like

Hi Michael,

Thanks for sharing. Great to see datavis packages with a sound theoretical basis being developed.

Have you seen elm-vega, which is also based on the grammar of graphics and vega? Full disclosure: I am the author.

The current stable release published as an elm-package represents the entire Vega-Lite specification, so includes faceting and other view composition operators. I have also been working on representing the Vega spec, which you can see on the vega branch of the elm-vega repo. That is about two-thirds complete and should hopefully be published as a package in the new few weeks.

What elm-vega doesn’t do is the direct rendering – this is handled by passing the JSON spec generated by the package to the Vega runtime via an Elm port. Perhaps pure Elm rendering of the Vega/Vega-Lite spec might be a basis for some collaboration in the future?

1 Like

Thank you for the feedback - I think you are spot on with using records for the required channels for the various Encodings.

I will write up my thoughts on one-true-path-experiment and pass them to you in the next couple of weeks.



Hi Jo,

I had seen the elm-vega package - it looks like you had as much fun deciphering the intent of the vega specification as me!

One of the reasons I started this project was to see what barriers I would run into when trying to build something like vega / ggplot / gadfly in a statically typed language and have it deeply embedded i.e. using the host languages type system rather than a DSL.

At some points, early on, I was convinced I would need more complex type machinery but I managed to express most of what I needed with Elm’s type system. Row polymorphism turned out to be a great help with the library both internally and when defining a plot.

I haven’t solved everything yet - It’s not immediately clear to me how I would extend to some of the other composition primitives vega has introduced (like ‘concatenation’) and whether it is even desirable to do so.

I have nascent versions of Facet in both F# and ReasonML and each type systems has advantages and disadvantages - polymorphic variants (which are described halfway through this page https://realworldocaml.org/v1/en/html/variants.html) in ReasonML have been the biggest eye opener and something I would love to have in other languages.

The take away was that it can be a little tricky to do what is needed in a static type system but the consequence is that adhering to some visualization best-practices (e.g. a single scale for a plot) became pretty much unavoidable.

As you may have noticed, the Scenegraph defined in the facet-scenegraph-alpha package is very similar to the vega scenegraph. If you can define your visualization in terms of a scenegraph then rendering is fairly straightforward since all the measurement is assumed to have been done by this point.

The main challenge for me was generating the scenegraph from the plot specification - this was the part the library that took the most time and still isn’t completely right. In particular, measuring axis labels has proved to be problematic when I have an axis with a continuous scale but the user hasn’t explicitly set the domain (i.e. I don’t know what the labels are yet!).

I would be interested if you have any insight as to how vega approaches this?




This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.