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”
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.
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:
- It allowed us to leverage the same design system components other product teams use.
- 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.
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.