How we built a component API for interactive data visualizations with Ember

Main illustration: Hannah Lock

At Intercom, one of our core principles is to run less software, which involves using standard technology. By doing so, we all become experts in the same tools, allowing us to increase our speed of building new product and reduce the cost of engineering decisions.

An example of this is our admin app, which is built with Ember.js. We have invested in building and documenting an internal design system made of reusable Ember components, so that any Intercom product engineer can skim the markup in an unfamiliar part of Intercom’s product and quickly make changes or additions without needing to learn new patterns.

“Any engineer wanting to add data visualization to part of the product doesn’t need to become an expert on a new technology”

When it comes to building rich data visualizations, we previously used D3, which is the industry standard tool for data visualization on the web. However, D3 requires thinking about UI in a different way to building components with Ember in HTML, CSS, and JavaScript. Because D3 isn’t one of our standard technologies, and because it requires engineers to think about UI differently, D3 visualizations created a sort of barrier for engineers in our codebase between product features and data visualization.

Product engineers shouldn’t all have to be D3 experts. Instead, when we build rich data visualizations, for instance in displaying reports, we built a composable component library of simple Ember components, and only use D3 to draw SVGs when we want to make shapes that HTML and CSS aren’t equipped for. As a result, we’re now in a position where any engineer wanting to add data visualization to part of the product doesn’t need to become an expert in a new technology, like D3, and doesn’t have to learn new best practices before getting started.

Constructing a component architecture

When setting out to simplify how engineers build data visualizations at Intercom, we had a few considerations:

  • Building data visualizations should feel like building product anywhere else in our codebase.
  • Product performance should not be impacted, and hopefully should improve.

Ideally, we wanted to end up with a simple, shared component architecture – just like any good design system – so our visualizations could maintain consistency in behavior and appearance. Making it easier and faster for any product engineer to implement data visualizations should improve productivity and increase adoption of data visualizations.

So how did we do this?

Previously, the visualizations we used were single files that controlled the entire visualization. We broke those singles files down into multiple components that can be grouped into two distinct categories:

  • Presentational components: To be used generically in any visualization.
  • Data components: To be used to interpolate different data types and connect to all of our generic presentational components.

Critically, the two different types of components operate independently of one another, which allows us to use the entire system of components together in any context.

Presentational Components

We built our presentational component parts – like bars, labels, plots, and grid lines – in plain HTML and CSS, rather than SVG elements. These simple HTML components allow us to easily share and compose visualizations from a shared set of core visual tools. We can use the same simple component anytime it’s needed and maintain a single source of truth for how it should look and behave. These also allow us to compose more complex visualizations from the same, reusable core elements.

Here’s a simple <Bar> component.

Here’s a <BarGroup>, that’s composed of multiple <Bar> components.

Composing with shared data components

Once we had defined a set of presentational components, we could compose them together with our data components. We componentized D3’s excellent library of data interpolation helpers and isolated this part of building visualizations from our presentation components. This allowed us to keep our presentational components generic and make any number of visualizations by composing data components and presentational components together.

This means that for a horizontal bar chart, like we used to display conversation ratings in our overview report, we can just use:

  • <XScale> – to map the data to an x coordinate.
  • <BarGroup> – to visualize the data we mapped to an x coordinate.
  • <XAxis> – to represent the scale for the data.

We still need to use SVGs and D3 for complex visualization shapes, such as in a line chart. However, we preserved our composable component system here too. Rather than build the entire line chart with D3, we created a component that only drew lines – everything else we continued to build with our component library. This meant that when we wanted to create a new sparkline chart, for instance, we were able to simply strip down our existing line chart by recomposing only the components needed, rather than by passing a complex configuration or creating forking logic in a pure D3 line chart.

Why this works for us

We could have done all of the above using Ember components, while still rendering SVG elements. However, we opted to use plain HTML components for two reasons:

  1. It allowed us to leverage the same design system components other product teams use.
  2. It eliminated the need to deeply understand SVG layout, and instead, use the same layout tools we use in the rest of the product.

This approach won’t work for everyone. Because we’re positioning HTML elements with transforms, rather than SVG elements inside an SVG as D3 does, we’re creating “layers” in the browser. Even though these layers are offloaded to the GPU, a lot of layers can use more memory and have performance issues on lower-end devices. Since our product prioritizes readability and interactivity, this means that we avoid having many data points in a single visualization, and we avoid the issue of having too many layers.

Since we’re no longer locked in to animating SVG elements, we can offset potential performance impact by doing our animations with CSS transitions and keyframe animations, specifically leveraging the power of CSS 3D transforms, rather than using D3’s normal approach of using JavaScript to animate SVG elements frame by frame. This has a huge positive impact on the ability to smoothly animate visualization between data points.

Moving our process away from individually created visualizations using D3 towards visualizations composed of standard building block components – with the same web technology all Intercom engineers use to build product – has made building visualizations more accessible to all engineers at Intercom, improved the quality of our product, and increased our speed and iteration of development. We’ve done all this without tanking performance – in fact, we’ve improved it. And because we’re using components, we get improvements and bug fixes automatically. The benefits of using components are manifold, and the system operates as a small but telling example of how “run less software” is a philosophy that keeps on giving.

Careers CTA - Work with us