Traditional object-oriented patterns for creating charts result in complex, expansive and inflexible APIs. In contrast, D3 has a relatively simple API that gives tremendous flexibility. However, creating conventional charts with D3 can be tricky and time consuming, the low-level nature of the API is very verbose, speaking the language of SVG elements, rather than the charting vocabulary of series, gridlines and annotations.
This article is the first of a two-part series, with this post looking at the technical problem of designing charts, and where object-oriented design falls short. The second article takes a look at how d3fc, an open source charting library, takes a very different approach to its peers.
If you just want to find out about d3fc and see it in action, feel free to skip this first post. But if you’d like to find out more about our motivations for creating that library, read on …
Object Oriented Charting
Developing charts from scratch is a time consuming process. Whether your application needs a simple pie chart, grouped bar, or something more specialised like a candlestick, it makes sense to try and find a suitable charting library which has the potential to save you weeks, or even years of development effort (yes, I’ve worked on a project where the chart was so complex it took years to develop!).
Have you ever created a charting library before? No? Well it’s really quite simple.
A standard Cartesian chart has two axes (x and y) and one or more series:
Using object-oriented design, we have three classes and a couple of relationships. Simple.
Only, things aren’t really as simple as that. If you want your charting library to be flexible it’s going to need to support different types of axis, like category, linear or logarithmic. Now linear and logarithmic are both numeric, so would likely share some of the same logic.
The obvious solution to this problem is object oriented inheritance:
Although, the above diagram is still an over-simplification. What about date / time axes, and radial?
Time to add more classes, and more base classes in order to support shared logic. We’ve already got an AxisBase class, so need the somewhat contrived AxisBaseCommon class to share logic between the radial and non-radial axes:
There, that’s axis sorted out. But what about series?
Oh dear, that’s only five concrete series types and it’s already starting to get out of hand!
It’s not uncommon for charting libraries to have hundreds of classes, with a confusing array of abstract base classes.
But this is only the tip of the iceberg. If you consider just one of these classes, a BarSeries perhaps. This needs to support different widths, so might expose a setBarWidth method, and what about styling? Better add setBarFill and setBarStroke. But what if the user wants to vary these on a per-bar basis? Time to add setBarFillForIndex, setBarStrokeForIndex methods which invoke callbacks. And what about if you want to add a text label to a bar?
Oh … the charting library developer didn’t think of that. Better just grab a reference to the SVG or canvas, try and work out the coordinate system transformation, and render that yourself.
Hopefully you can see the problem. Find yourself a versatile charting library and you’ve likely found a library with hundreds of classes and thousands of methods. Have fun reading the API documentation, and cross your fingers that if you want to add text labels to bars, or invert the labels on an axis, that the library developers already thought of that and has provided you with a suitable configuration method!
The crux of the problem is that charts are monoliths, and the only way the library author is able to tease the monolith apart is via these complex class hierarchies.
By the way, this isn’t a class hierarchy for some fictional charting library, it’s actually one from a real product. One that I helped develop (a long while back). I am truly sorry.
And the solution is … D3
By now you’re probably shouting at your computer, or smartphone, “D3 is the answer!!!!”. And yes, you’re right, D3 does solve many of these problems.
While D3 can be used to render charts, it is very different from your typical object-oriented charting library. With D3 you render charts, or other forms of visualisation, by using a powerful binding language called a data-join.
If you are creating a highly bespoke visualisation, you just can’t beat D3 for its power and flexibility. However, for more standard charting types, the low-level nature of D3 results in quite a bit of extra work.
Put simply, D3 speaks the language of SVG, with data-joins creating text, groups and path elements. For a charting library you really want something with a different vocabulary, a library that speaks in series, legends, labels and annotations.
There are numerous charting libraries that are built using D3. Do these allow you to use the powerful concepts of data-join, with a higher level charting vocabulary?
Unfortunately, no. All the ones I have found so far do not. Almost invariably D3 is an implementation detail, where D3 is contained within the little boxes that make up a more standard, and often object-oriented, charting design:
By putting D3 inside these little boxes, its power is lost.
Is this such a bad thing? Not always. If all you want is a quite standard bar, pie or line chart, and you are happy with the configuration options that your D3-based charting library present, that is absolutely fine.
It’s only if you want to modify the chart in some way that the original author had not considered, that placing D3 inside these little boxes starts to look like a bad idea.
Interesting Mike Bostock, the creator of D3, left a few hints about charting library design in his article on charting components — a pattern that D3 uses throughout.
Citing the book the Grammar of Graphics Mike asks:
Is a traditional chart typology the best choice? […] Even with traditional chart types, should you expose the underlying scales and axes, or encapsulate them with chart-specific representations? Should your chart support interaction and animation automatically? Should the user be able to reach into your chart and tweak some aspect of its behavior?
While these are presented as questions, he is more than hinting at the correct answers!
OK, time to come clean, this article is actually about a charting library I have been involved in, called d3fc. We genuinely do believe that it is different from all the other charting libraries out there, but of course I’d say that … I’m biased!
So how does d3fc differ from these other charting libraries? If you’re interested in finding out more, head over to the second article in this series.