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.
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.
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 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
Selects the existing element and draws the axis inside.
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
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?
Another way to get the job done is to get ticks — split of data into axis ready array — and render them as usual components.
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.
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