Making D3 fun again with React
I have long been a fan of d3.js for building beautiful, informative and engaging data displays. D3 was the first tool that allowed me to express myself visually on the web in a way that matched what I felt in my soul when working with data. Beautiful transitions, elegant data handling and a wonderful community; D3 felt like home immediately. It was the singular inspiration that drove me to learn javascript and become a web developer.
I’ve recently transitioned from from a career at the Broad Institute building genomics research tools to a new one building a next generation health care management platform at Wellframe. A funny thing happened when I landed at Wellframe. I began to evangelize D3 amongst other developers and it fell totally flat. I was shocked to hear that since I had fallen so hard for D3. The library had been literally life changing for me. How could it not be at least interesting to others?
I set out to find out why other developers where just not that into D3. The other Wellframe devs found the D3 API hard to understand and time consuming to learn. If you poke around and find D3 examples on the web they are often nice and clean and that tends to draw people in. As an example, the following is a chunk of code taken from a charting library I wrote early on at Wellframe
There are a few instance variables in there and an arrow function, but for the most part this thing looks like the kinds of things you find in D3 example code. However, when you set out to write anything non-trivial in D3 you often get code that looks more like the following
The above code, taken from a few lines above the first snippet, has a lot more going on. We are using more instance variables and arrow functions. On top of that, we are doing a ton of work in inside of the .text function. All of this is in service of creating a simple bar chart that handles x-axis labels gracefully
While very supportive and enthusiastic about what could be done with D3, my new colleagues could not justify the time it would take to fully learn and leverage D3 with all of its automatic collection handling and intense method chaining. Simply put, D3 requires a shift in thinking (and sometimes syntax) that would take most developers a few weeks to get comfortable with. Taking a few weeks to grapple with the ins and outs of D3 would mean not shipping new features and that would be a non-starter at our little startup.
Given that background it is no surprise that folks on our team were turning to tools built as wrappers around D3 to build charts on the web. There are many wonderful projects out there that aim to simplify the process of working with D3 and abstract away a lot of the pain points in writing raw D3 code. Our developers had explored DC, C3, NVD3, D3.chart and others. All of these libraries abstract the core D3 API to various levels and add additional functionality, but we were still frustrated.
We found that we could quickly stand up prototypes with these libraries, but the devil is in the details when building robust products. We found ourselves bumping into edge cases that were hard to tackle using a pre-made library. In particular, we found that putting the level of polish and branding on these charts we would like forced us out of the library du jour. As an example, Take the following chart
The kinds of custom hover and transition effects we wanted were hard to find in tools we could grab off the shelf.
So the game changed. Instead of converting all of the other devs into expert D3 hackers, I set about building custom charts in D3 that would be consumable by the rest of the folks working on our front end. Wellframe has made heavy investments into React so the goal was to create a nice comfortable React component that gave us all of the sweet D3 bits baked in. In this way, I was able to expose custom charts that adhere to the aesthetic of our company and avoid forcing others to learn low level D3.
The React lifecycle sits at the core of the library and is the natural place to start when writing any kind of integration that will play nice in the React ecosystem. Taking some direction from a wonderful Sift Science blog post, I built out the scaffold for a React charting component. The component creates a mapping between React lifecycle methods and D3 patterns.
After pulling in a chunk of mystery code called ChartFactory (more on this later), we give developers some hints by declaring displayName, propTypes and getDefaultProps. Next we hook into the componentDidMount method and bootstrap a chart using our chart factory. The type of chart to build, the data to display and any options are all passed to our factory. The place in the DOM where the React component is mounted becomes the entry point for all of our D3 code. We also add a handy resize listener to the window object so the chart knows when a user fiddles with the space available to it. To complete the hook up between React and D3 we map componentDidUpdate to an update method and componentWillUnmount to a remove method on the generated chart. Finally, we render out a single empty div element to for our chart to build into.
As for the ChartFactory, we put together a function that attempts to render whatever type of chart our developers specify in props.type passed to the chart component. By default, it renders no data into the chart and would render that chart to the body element in the DOM with no special options. When the factory is called, a check is run to see if the developer asked for a known chart type. In addition, a few checks are run to ensure that the chart about to be rendered has update, remove and resize methods. If these checks are passed, We initialize the chart and return it.
In order to make ChartFactory aware of a new chart type we take advantage of the fact that functions are just objects in javascript. Like any other object we can add attributes to it and those attributes can be functions. If we register a functions that generate D3 charts on the ChartFactory object we now have a system in which developers can ask for any kind of chart they want. If it exists, we go ahead and create it. If it doesn’t exist they get an error telling them so (in practice, that error tells them to go bug “the D3 guy” to implement it). Further, the expected API of the D3 chart is enforced so we avoid any errors when wrapping the chart in our React component.
In terms of the API of the D3 chart itself, we just require that D3 charts do what D3 enthusiasts like myself do all the time. The chart must implement initialize, update and remove methods. As an added creature comfort we also ensure that the chart implements a resize
For those of us that are used to writing D3 code, this looks really familiar and comfortable. We can write the same kinds of D3 code that we have been writing for the last few years and have tons of fun doing it. If we adhere to these four methods we can expose any chart we write via the React component and expect that it will work just like any other component.
The React side of the API is also familiar and comfortable for folks that have been building in React recently. So long as they pass in type and data as props they are good to go.
As an example, the following invocation
might yield this chart
With all of this in place, a wonderful thing has happened at Wellframe. Our developers no longer shy away from displaying the kinds of interactive charts produced by D3. We have made charting easier at our company by abstracting away much of what makes it hard. Instead, placing charts under a standardized React API has inspired us to prototype faster, be more aggressive about getting data into our projects and have a lot more fun doing it.
Further, by integrating two amazing technologies in a way that lets developers work without friction we are able to impact a portion of the health care industry that has be data starved for far too long. Ultimately, this whole exercise has allowed us to give more context to the fantastic people using our care management platform to improve the health of high risk patients.
At the end of the day I believe in that outcome and am honored to be able to work in service of it.