Better Programming

Advice for programmers.

Follow publication

React and D3 — Axis

The second chapter in the combination of React and D3 series

Illia Olenchenko
Better Programming
Published in
5 min readNov 5, 2019

--

Photo by John Schnobrich on Unsplash

Intro

This is the second part of the React and D3 series. You can find the first chapter here. And the source code.

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

https://react-d3.icrosil.now.sh/

Axis added 📊

Problem

When you create an axis with D3 it does a lot of good things for you.
It makes padding for labels, it calculates the needed label for the number of ticks you want to show, it renders the result right in a Node.

Unfortunately, rendering into a Node does not work well with our approach of combining React and D3. The problem occurs when you want a bit more from the axis than the standard function can 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 constrains 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 where 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, which is quite 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.

render

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

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

This selects the existing element and draws the axis inside.

lifecycles
  • componentWillMount creates an element when a component is going to be created and sets up virtual element for D3.
  • componentDidMount triggers render.
  • componentDidUpdate triggers render.
  • setRef is the ref setter.

This solution works for React 16 even if it looks that ugly. Obviously, after the release of React 17, lifecycles will be deprecated and that workaround will stop working for us.

Let’s try to build something similar with Hooks, so our axis will be ready to meet React 17.

AxisHook

Basically it is the same component as with lifecycles but written using Hooks. The renderASAP block is pretty similar to the previous implementation as we cannot make it so useEffect is called immediately inside render.

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

  • You can define the exact number of ticks, but D3 can shift it accordingly to the inner implementation of the closest guess. If you’d say you want four ticks to display, it can easily be either three or five, depending on data and view.
  • You cannot add anything except ticks and lines predefined in the D3 axis.
  • You need to add an htmlToReact solution like react-html-parser.
  • To style ticks, you have to use direct DOM manipulation.
  • To add extra text next to the axis, you would need to position it absolutely.
  • To allow SSR, a duplicate of the logic should be in the code to treat different approaches.

Both examples solve three out of the five criteria. Can we do better?

Prepare With D3 and Render With React

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

AxisReactFirst

As simple as a React component could be. All we need here is to take ticks from a scale. We don’t need extra D3 methods or to parse React to somewhere. We can control any style and format.

Although, 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 the things from the list above are usually made by D3 and you don’t need to bother with such details.

Of course, there are pros:

  • Don’t need a 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. Five out of five.

Though this granularity of control is not always needed, as it adds more overhead. One thing to think about — do you really need that level of granularity? You may stop on the first D3 solution if it works for your criteria.

Takeaways

We compared three approaches to render axes. Depending on your needs, choose what serves your criteria and don’t overcomplicate.

  • Avoid the lifecycle approach as methods are going to be deprecated.
  • The Hook solution is OK if you don’t mind the effects.
  • Pure SVG solution if the default axis component needs lots of restyling.

Happy coding.

Sign up to discover human stories that deepen your understanding of the world.

--

--

Responses (1)

Write a response