Today, there are a lot of data visualization frameworks. Even better, they exist at various levels of abstraction. From D3’s foundational framework to Highchart’s pre-built juggernaut, amazing tools are available to developers today. Today, we are going to dive through the best ones.
I distinctly remember when I first discovered Chart.js. I don’t precisely recall what I needed a graph library for, but I remember clicking on Chart.js’s listing on Google’s recommendation, finding the documentation fairly intuitive, and spinning up some bar charts within the hour. That was around a decade ago—but even today, Chart.js holds up weight. For a lot of hobbyists looking to craft a simple line graph or pie chart, it is a simple, straightforward solution.
However, this piece is a deep dive into leading Javascript visualization frameworks, and Chart.js is not in the running. Chart.js, like many other “lite” libraries, is packed with useful features but has distinct limitations. Specifically, Chart.js is opinionated. It takes a stance on how a chart should look, feel, and animate; while you can personalize some things, they are only personalizable within Chart.js’s opinionated design.
In contrast to Chart.js, there exists a different league of libraries designed to visualize data via open-ended configurations. Libraries in this niche include D3, Vega, Vega-Lite, and Highcharts, which exist across a wide range of abstraction. For instance, D3 is very bare-bones; conversely, Highcharts is quite pre-configured.
One thing that is tricky about this topic is navigating the levels of abstraction. Some libraries (like D3) are the low-level building blocks behind other libraries’ building blocks. At Explo, we spent weeks understanding this space so that we could make an educated choice; a graph visualization library was critical to our product (we simplify building and embedding client-data analytics).
The godmother of most visualization libraries is D3. D3, which translates to Data-Driven Documents, is a Javascript library for showcasing data through visual interfaces. D3 is broad—it isn’t tuned for graphs, charts, or diagrams, but instead any arbitrary component that dynamically responds to data.
D3 creates visualizations using the native components that form the web—HTML, CSS, SVGs. And, since version 4, D3 also supports HTML Canvas. Given SVG and Canvas perform better on different browsers (don’t get me started on the number of times an SVG has vomited on me on Safari), the cross-support is quite important.
At first glance, D3 looks a lot like jQuery. There are helper functions to select elements or sets of elements; there are also HTML attribute functions that alter an element’s properties. But D3 also works like React—D3’s data object, similar to React State, re-renders elements through callback functions. However, D3 manipulates the actual DOM, unlike React which relies on a virtual DOM.
An example D3 snippet would look like this:
var data = [5, 10, 15, 20, 25];
var svg = d3.select("svg");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return d * 2; })
.attr("y", function(d) { return d; })
.attr("width", 3)
.attr("height", function(d) { return 30 - d; });
To be clear, D3 isn’t an ugly love child of jQuery and React. It ships with a trove of helper functions specific to working with data, such as functions that tackle proportional scaling, data mapping, and auto-sizing. It also does include some limited UI components such as labeled axes.
var svg = d3.select("#area")
var x = d3.scaleLinear()
.domain([0, 100])
.range([0, 400]);
svg.call(d3.axisBottom(x)); // create a labeled axes
svg.append("circle")
.attr("cx", x(10)).attr("cy", 50).attr("r", 40).style("fill", "red");
svg.append("circle")
.attr("cx", x(50)).attr("cy", 50).attr("r", 40).style("fill", "blue");
svg.append("circle")
.attr("cx", x(100)).attr("cy", 50).attr("r", 40).style("fill", "orange");
However, D3 out-of-the-box doesn’t provide structure for laying out a bar chart. Such stylistic work needs to be done through CSS or SVG styling. D3 just focuses on connecting data with the DOM, making it easy to manipulate that data. Remember, the name is data-driven documents, not data-driven visualizations.
Typically, raw applications don’t implement D3 directly. Rather, D3 is extended by other frameworks; for example, Vega and Vega-Lite use D3 for data visualization. But there are a few exceptions where a developer may opt to use D3. For instance, if an application needs to personalize every aspect of a data visualization, then D3 may be the best option.
Before discussing Vega, it is important to understand the Grammar of Graphics. Coined by Leland Wilkinson in a book of the same title, the Grammar of Graphics is a set of principles that define a data visualization.
Specifically, Wilkinson defined seven degrees of freedom:
The Grammar of Graphics is a framework for providing guardrails and structure on how to visualize data; something that open-ended packages like D3 don’t care about. It directly inspired R’s popular ggplot2 visualization library.
For Chart.js from the introduction, a lot of these grammar principles are pre-defined, immutable, and opinionated. For instance, Chart.js has a developer choose a pie graph, not make a choice between radial and cartesian coordinates. Chart.js is an opinionated ********library on how to visualize data. Accordingly, it has many un-opinionated corollaries—the biggest one being Vega.
Vega is a visualization grammar based on the Grammar of Graphics. Specifically, Vega ingests a JSON specification and renders it. For the browser, it uses a Web SDK (which bundles D3). For other arbitrary use-cases, including mobile devices, it exports a PDF, PNG, or SVG through a command line utility.
While more constrained than D3, Vega remains fairly un-opinionated. Vega compiles a visualization grammar into an interactive graphic. This differs from D3 as D3 doesn’t create the visual elements. However, Vega still defers the styling and the layout to the user.
For example, take the following Vega JSON blob that renders a bar graph (the full blob has been abbreviated for brevity).
{
"$schema": "<https://vega.github.io/schema/vega/v5.json>",
"description": "A basic bar chart example, with value labels shown upon mouse hover.",
"width": 400,
"height": 200,
"padding": 5,
/* ... */
"data": [
{
"name": "table",
"values": [
{"category": "A", "amount": 28},
{"category": "B", "amount": 55},
{"category": "C", "amount": 43},
{"category": "D", "amount": 91},
{"category": "E", "amount": 81},
{"category": "F", "amount": 53},
{"category": "G", "amount": 19},
{"category": "H", "amount": 87}
]
}
],
"scales": [
{
"name": "xscale",
"type": "band",
"domain": {"data": "table", "field": "category"},
"range": "width",
"padding": 0.05,
"round": true
},
{
"name": "yscale",
"domain": {"data": "table", "field": "amount"},
"nice": true,
"range": "height"
}
],
/* ... */
"axes": [
{ "orient": "bottom", "scale": "xscale" },
{ "orient": "left", "scale": "yscale" }
],
/* ... */
}
Using the full grammar specification, which is nearly triple as long, Vega will render a familiar-looking bar graph. Vega is a close implementation of Wilkinson’s Grammar of Graphics (and is inspired by R’s ggplot2); however, it deviates from the Grammar of Graphics by taking advantage of the web’s interactivity. Vega has specific parameters known as signals, which enable developers to write interactive functions using Vega’s own simplified language. Vega’s logic language isn’t Javascript-based, which is why some developers consider Vega its own language.
Take this snippet of signals from Vega’s example of a re-orderable matrix of co-occurrences of Les Miserables characters:
"signals": [
{ "name": "cellSize", "value": 10 },
{ "name": "count", "update": "length(data('nodes'))" },
{ "name": "width", "update": "span(range('position'))" },
{ "name": "height", "update": "width" },
{
"name": "src", "value": {},
"on": [
{"events": "text:mousedown", "update": "datum"},
{"events": "window:mouseup", "update": "{}"}
]
},
{
"name": "dest", "value": -1,
"on": [
{
"events": "[@columns:mousedown, window:mouseup] > window:mousemove",
"update": "src.name && datum !== src ? (0.5 + count * clamp(x(), 0, width) / width) : dest"
},
{
"events": "[@rows:mousedown, window:mouseup] > window:mousemove",
"update": "src.name && datum !== src ? (0.5 + count * clamp(y(), 0, height) / height) : dest"
},
{"events": "window:mouseup", "update": "-1"}
]
}
],
The specification is relatively short, but the interactivity of the graphic is astounding. You can drag, preview, and drop row-column pairs with the mouse. However, this functionality is not defined in Javascript, instead Vega’s own logic syntax. For many developers, this isn’t ideal as it breaks an application’s syntactical consistency (à la it doesn’t use the one-true-mighty language we know as Javascript).
Regardless, Vega’s open-ended nature makes it great for crafting personalized, interactive graphics. The bigger issue is that learning Vega is demanding; simple graphs take long, tedious specifications. To account for this, Vega’s team developed Vega-Lite.
Just like Vega, Vega-Lite is a high-level grammar for interactive graphics. Vega-Lite is often misconstrued to be a set of templates for Vega; that isn’t the case. Vega-Lite is its own grammar (albeit being inspired by Vega). Notably, unlike its parent library, Vega-Lite infers common properties automatically, such as axes, legends, and scales. At runtime, Vega-Lite compiles to Vega, filling in the gaps in the rendered Vega specification that the user didn’t specify.
Vega-Lite was undoubtedly created to address some of the criticism that Vega was too difficult to learn or too overkill for simple visualizations. And since Vega-Lite can compile into Vega, it also offers strong interoperability between Vega-Lite and Vega.
Unsurprisingly, Vega-Lite JSON files tend to be much shorter than Vega. For instance:
{
"$schema": "<https://vega.github.io/schema/vega-lite/v5.json>",
"description": "Google's stock price over time.",
"data": {"url": "data/stocks.csv"},
"transform": [{"filter": "datum.symbol==='GOOG'"}],
"mark": "line",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "price", "type": "quantitative"}
}
}
This is the entire specification. That’s it. Vega-Lite infers the rest, filling in the blanks for things such as scales or axes.
Originally, when Vega-Lite was first launched, a lot was missing. With some basic searching, you’ll find plenty of articles advocating against Vega-Lite because it felt like an incomplete project that simplified Vega but limited the developer by too much.
Today, however, Vega-Lite is fairly extensible, and given its interoperability with Vega, is a fantastic starter framework to utilize that ecosystem. Vega-Lite + Vega is reminiscent of other parent-child relationships found in software, such as Expo CLI + React Native CLI or NextJS + ReactJS. However, even today, some interactive functionality is difficult to implement on Vega-Lite because it requires a very strong understanding of Vega-Lite’s logic grammar; many Vega ecosystem developers use Vega-Lite as a primary visualization framework and then fall back to Vega for anything complex.
The leading graph framework among enterprise companies is Highcharts. Highcharts is used by the likes of Samsung, Sony, and American Express—according to Highcharts, 80 out of the world’s 100 largest companies work with them. Even at Explo, we use Highcharts to power our own application, having chosen it over Vega after a very long internal debate.
Unlike Vega and Vega-Lite, Highcharts does not consider itself a grammar. Highcharts is structured more as an opinionated toolkit to implement charts. However, the JSON inputs that go into Highcharts often look like inputs fed into Vega-Lite. Notably, Highcharts combines some of the better features pooled from both Vega and Vega-Lite: (i) Highcharts is very customizable with diverse chart-types (like Vega), but (ii) has simple presets that make it easy to get started (like Vega-Lite).
But the biggest differences between Highcharts and the Vega ecosystem aren’t in the design. They are in the distribution.
Shocker, Highcharts is Not Open Source!
Unlike any other framework in this list, Highcharts is not open source. It is source available—anybody can peruse the Highcharts codebase as well as try to implement Highcharts for free. But before launching a Highcharts-enabled app into production, a Highcharts license legally needs to be purchased.
Typically being closed source in a competitive space rife with open-source projects would be considered a negative. But Highcharts classifies its proprietary license as a marketing plus. I spoke with Highcharts engineers while writing this piece; they are very cognizant of the free, open-source alternatives to their product. It’s rather that, at the end of the day, Highcharts being for-profit works to the advantage of Highchart’s customers.
The reason why is rather simple. Because Highcharts has real revenue—unlike open source projects which depend on donations—they are able to maintain a dedicated customer support team. And support does matter in this space: implementing charts isn’t a big enough task to hire a specialized engineer for, but is tricky enough to often demand assistance. Accordingly, dedicated support is a real value-add for Highchart’s customers, often playing a role in purchasing decisions.
The Structure of Highcharts
Highcharts is broken down into a flagship library (Highcharts Core) mixed with a few powerful extensions that include Highcharts Maps, Highcharts Gantt, and Highcharts Stock. The main library has all the functionality for rendering common charts like line or bar charts, the stuff taught in grade school math. The extensions provide functionality for niche charts, such as a stock market candlesticks chart for a financial product.
Highcharts also can be implemented via packages form-fitted to popular Javascript libraries like React and Vue. And while Highcharts is built in Javascript, there are popular third-party libraries that scaffold it for Python and R as well as operating systems like iOS and Android.
Example of Highcharts
Highcharts input is often short if users want to use Highcharts’ default settings. For instance, this is the Highcharts code that is analogous to the first Vega example:
Highcharts.chart('container', {
chart: {
type: 'column'
},
title: {
text: 'Title'
},
xAxis: {
title: {
text: null
}
},
yAxis: {
title: {
text: null
}
},
legend: {
enabled: false
},
series: [{
name: 'Series',
data: [
['A', 28],
['B', 55],
['C', 43],
['D', 91],
['E', 81],
['F', 53],
['G', 19],
['H', 87]
]
}]
});
Even when using Highchart’s extension, such as Highcharts Stock, the input JSON is typically very concise:
Highcharts.getJSON('<https://cdn.jsdelivr.net/gh/highcharts/highcharts@v7.0.0/samples/data/new-intraday.json>', function (data) {
Highcharts.stockChart('container', {
title: {
text: 'AAPL stock price'
},
series: [{
name: 'AAPL',
data: data,
}]
});
});
A is for Accessibility
Another unique advantage of Highcharts, notably for enterprise customers, is its accessibility support. While Vega and Vega-Lite only support bare-bones accessibility features like aria (captions), Highcharts has a nifty feature that makes charts accessible for visually-impaired/blind users. Using a technique called sonification, Highcharts includes a synthesizer that renders a melody that is a function of the chart’s data.
For instance, for a line chart with a bell curve, sonification will render a melody that starts at a low note, accelerates to a high note, and then gracefully falls back to a low note.
It is no surprise that Highcharts focused on accessibility from the early days. Large organizations typically care about web accessibility standards such as WCAG. Given that data visualizations are among the most visually-dependent features in an application, Highchart’s accessibility gauntlet helps enterprise software achieve end-to-end accessibility. That is some achievement!
The Highcharts Editor
One of Highcharts’s unique sub-products is the Highcharts Editor. The Highcharts Editor is a GUI that enables both developers and product managers to build Highcharts without writing any code whatsoever.
Highcharts Editor is especially ideal for non-technical product designers that may want to prototype chart design without depending on engineering. After a chart is designed, the analog Highcharts code is available for download, which can be handed off to engineering to implement into the final interface.
The Highcharts Editor is particularly great for teams that need to design custom charts for enterprise clients. Typically those bespoke chart designs fall into the hands of non-technical team members; Highcharts Editor enables those teammates to create something codebase-compatible without accessing the codebase whatsoever.
Syntax
Unlike Vega and Vega-Lite, Highcharts uses Javascript for everything, including any interactive, conditional logic. This makes Highcharts particularly attractive to developers that don’t want to deviate beyond Javascript’s callback-friendly design paradigm.
You might be wondering why we’re writing this article—or, more specifically, why we are so passionate about Javascript-based dashboard visualization tools. Well, we had to care about this topic from Day 0 of building our product.
Explo is a customer-facing embedded analytics solution—we simplify charting at another level. In short, developers link Explo via a read-only connection with their data sources. Then developers write raw SQL to pull data into Explo’s editor. Explo’s editor handles the rest—it’s a low-code UI that enables anyone to design and manage customer-facing dashboards.
A lot of the pros and cons discussed in this article are reminiscent of Explo’s value props. For instance, many organizations need custom analytics for a specific client; with Explo Architect, that client-specific dashboard is available out-of-the-box. We’ve been able to deliver such a fantastic platform by using Highcharts; their declarative, organized syntax made it possible to scale Explo features very quickly. Granted, some components were built from scratch, such as trend visualizations, funnels, and custom tables.
Curiously, Highcharts (or even Vega, Vega-Lite, D3, or any other arbitrary framework), can be used within Explo itself. Knowing that some customers will have to craft an occasional, highly-specific visualization, we made it possible to embed any arbitrary custom components within an Explo dashboard.
Today, the space is rife with data visualization frameworks. Even better, there are frameworks that live at every level of abstraction. If a developer needs something bare-bones and very opinionated, D3’s foundational system may be ideal. If a developer needs something that provides structure but doesn’t dictate visualization design, Vega is likely great. Or if a developer needs a framework that makes implementing charting easy—without compromising core customizability—Highcharts and Vega-Lite are great picks.
Of course, sometimes it is less about code and more about management. For teams where non-technical folks are involved with creating dashboards, or teams where engineering teams are over-burdened with the roadmap, solutions like Explo or Highcharts Editor are great.