Line charts

Dataviz logo representing a Line chart.

This tutorial is a variation around the general introduction to line chart with react and d3.js. You should probably understand the concepts described there before reading here.

This example explains how to create several line charts and add asynchronized cursor on all of them. Hovering over one graph will display a cursor on all of them, easing the understanding of synchronized patterns.

A code sandbox is provided for the final result, but explanations target what's different compared to an usual line chart.

Useful links

Plot and code

If you are in a hurry, this is what we're trying to achieve here.🙇‍♂️

Two line charts are displayed one next to each other. It shows the evolution of 2 numeric valriables.

Hovering over a chart will display a cursor that is synced with the other chart. This helps finding a relationship between them.

Hover over a chart to see a cursor on both of them, easing the time comparison.

The Data

Two dataset are used here. Both with the same format as described in theline chart section of the gallery.

Here is a minimal example of the data structure:

const data = [
  {x:1, y: 90},
  {x: 2, y: 12},
  {x: 3, , y: 34},
  {x: 4, , y: 53},
  {x: 5, , y: 98},
]

Shared state

The parent component calling the 2 LineChart components stores a shared state. This state can be created as follow using the react useState hook:

const [cursorPosition, setCursorPosition] = useState<number | null>(null);

cursorPosition will be null if no charts is hovered over, and a number that provides a position in pixel otherwise. setCursorPosition is a setter: a function that updates the value of cursorPosition

Both cursorPosition and setCursorPosition must be passed to the line chart component as props.

Trigger state update

When the mouse moves over the chart area, the state must be updated using setCursorPosition

To do so, a SVG rect element is added on top of the chart area. It will catch the onMouseMove events. This rect looks like:

<rect
  x={0}
  y={0}
  width={boundsWidth}
  height={boundsHeight}
  onMouseMove={onMouseMove}
  onMouseLeave={() => setCursorPosition(null)}
  visibility={"hidden"}
  pointerEvents={"all"}
/>

There are several strategies available when it comes to write the onMouseMove function. Here I suggest to

  • use getBoundingClientRect() to find the position of the mouse in the chart area
  • find the closest data point in the dataset. This requires the invert function of the d3 linear scale
  • update the state after using the linear scale again
const onMouseMove = (e: React.MouseEvent<SVGRectElement>) => {
  const rect = e.currentTarget.getBoundingClientRect();
  const mouseX = e.clientX - rect.left;

  const closest = getClosestPoint(mouseX);

  setCursorPosition(xScale(closest.x));
};

Animated cursor

I used react spring to animate the cursor transition. When the mouse moves, the cursor position does not change instantly but moves smoothly to the new position.

Animation in dataviz using React is a big topic. It's impossible to go in-depth here! I will publish a dedicated blog post on the topic soon. Please subscribe to the newsletter if you want to be notified.

Hover over a chart to see a cursor on both of them, easing the time comparison.

Evolution

Contact

👋 Hey, I'm Yan and I'm currently working on this project!

Feedback is welcome ❤️. You can fill an issue on Github, drop me a message on Twitter, or even send me an email pasting yan.holtz.data with gmail.com. You can also subscribe to the newsletter to know when I publish more content!