How to add axes to React and D3 components

In this article, we’ll investigate how we could add axes to a React component. Usually, any of your awesome charts would need something to describe how do you measure your data. This is where axes are helpful.

This is image title

Problem

When you create an axis with D3 it makes a lot of good things to you.
It makes padding for labels, it calculates the needed label for the number of ticks you want to show, it render result right in a node.
Unfortunately rendering into a node does not work well with our approach to combine React and D3. The problem occurs when you want a bit more of the axis than the standard function could give you. Let’s mention usual requests that may be on your product:

  • Define the exact number of ticks on dynamic data. Formally you can specify exact tick values but that constraint you to have data in the same range. Usually, that does not work for dynamic data.

  • Add a label next to the axis. For example, your designer might ask you to add a small text next to the axis. Or add text instead of the last element.

  • Style values that should be in axis differently. What if each style of the tick should be different?

  • Render axis with React. What if we want to control each element?

  • Render Axis for SSR or on converting to string. Hydration would work ok with lifecycles or hooks, but there are situations that not an option. For example when you are making a PNG from an SVG in the browser.

Render with D3

Let’s start our story with the default solution pretty well described across D3 and React articles. It may be not ideal and doesn’t solve all our requests but it is a good starting point.

To do this we need to define a node for the ref and link it with D3. To perform actions we would use lifecycles.

This is image title

This is a render method. It doesn’t do too much, though it always renders a React component.

  • this.renderAxis() asks d3 to render axis into an element

  • parser in renderASAP branch needed for SSR, we render axis into virtual element, then parsing its HTML into React component. After this, our component would be a React instance. All parsing actions are the only way to render axis with D3 and tell React how to treat it on render. To do so we have to add react-html-parser lib or any other html-to-react lib. This trick adds another 40 gzip kb into your project

  • return component for default case with ref set

This is image title

Selects the existing element and draws the axis inside.

This is image title

  • componentWillMount creates an element when a component is going to be created and setting up virtual element for d3

  • componentDidMount triggers render

  • componentDidUpdate triggers render

  • setRef ref setter

This solution works for React 16 even if looking that ugly. Obviously after the release of React 17 lifecycles will be deprecated that workaround will stop work for us.

Let’s try to build similar with hooks, so our Axis will be ready to meet React 17

This is image title

Basically it is the same Component as with lifecycles but written using hooks. renderASAP block pretty similar to the previous implementation as we cannot make useEffect to be called immediately inside render.

Both examples depend on the inner D3 implementation of an axis. It has caveats like:

  • you can define exact number of ticks, but D3 can shift it accordingly to inner implementation of closest guess. If you’d say you want 4 ticks to display it can easily be either 3 or 5 depending on data and view.

  • you cannot add anything except ticks and lines predefined in d3 axis

  • you need to add htmlToReact solution like react-html-parser

  • To style ticks, you have to use direct DOM manipulation

  • To add extra text next to Axis you would need to position it absolutely

  • To allow SSR duplicate of logic should be in code to treat different approaches

Both examples are solving 3 out of 5 criteria. Can we do better?

Prepare with D3 and render with React

Another way to get the job done is to get ticks — split of data into axis ready array — and render them as usual components.

This is image title

As simple React component as it could be. All we need here is take ticks from a scale. We don’t need extra d3 methods or parsing react to somewhere. We can control any style and format. Though here we have some extra things we need to think about:

  • format ticks manually

  • set flex style on a container to spread ticks

  • add translate, height, width and fontSize

All from list above usually made by D3 and you don’t need to bother of such details.

Of course, there are pros:

  • don’t need any hook to render

  • don’t need to think about SSR render as a component is pure

This solution reflects all of our tasks. Style, render with React, control elements. 5 out of 5. Though this granularity of control not always needed, as it adds more overhead. One moment to think about — do you really need that level of granularity? You may stop on the D3-first solution if it works for your criteria.

Conclusion

We did compare of 3 approaches to render Axes. Depending on your needs chose what serves your criteria and don’t overcomplicate

  • Avoid lifecycle approach as methods are going to be deprecated

  • Hook solution is ok if you don’t mind of effects

  • Pure SVG solution if default axis component need lots of restyling

Happy coding.

#react #javascript

How to add axes to React and D3 components
1 Likes7.30 GEEK