Barplot

Dataviz logo representing a Bar chart.

This tutorial is a variation around the general introduction to barplot with react and d3.js. You should probably understand the concepts described there before digging into animation.

This example focus on how to transition between datasets. It explains how to animate the change thanks to the react-spring library.

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

Useful links

Plot and code

If you are in a hurry, this is what we're trying to achieve here 🙇‍♂️. The value of several individuals is represented, with one bar per individual. It's just a horizontal barplot.

It is possible to switch from one dataset to another using the buttons above the chart. A few notes on the 3 usual animation patterns:

  • update: bars smoothly update their rank and size when the data changes. So does their label.
  • enter: when the chart first loads, bar starts from 0 and grows to its real size. This is also true for items that are available in the new dataset but not in the previous. Check Christophe when you switch to data 2.
  • exit: when an item is not available in the next dataset, it disappears with no animation (see Paul when switching to data2).

Barplot with smooth transition between dataset

The Data

The dataset used here is exactly the same as the one used for the simple barplot. Note that 2 similar datasets are used: data1 and data2.

Animation

Most of the code is similar to the basic barplot component. But instead of building one rect per item in the dataset, a BarItem component is called to render a rectangle that supports animation.

The react-spring library is used to create a spring animation. The rectangle properties are passed to a useSpring hook that will build the animation for us.

This is how the BarItem component looks like:

import { useSpring, animated } from "@react-spring/web";

type BarItemProps = {
  name: string;
  value: number;
  barHeight: number;
  barWidth: number;
  x: number;
  y: number;
};

type AnimatedProps = {
  barWidth: number;
  value: number;
  valueOpacity: number;
  y: number;
};

export const BarItem = (props: BarItemProps) => {
  const { name, value, barHeight, barWidth, x, y } = props;

  const springProps = useSpring<AnimatedProps>({
    // the 'from' properties will be used only to animate the initialization of the component
    // if you put nothing it will be initialized with the first prop that is provided
    from: {
      value: 0,
      barWidth: 0,
      valueOpacity: 0,
    },
    to: {
      value: value,
      barWidth: barWidth,
      valueOpacity: barWidth > 80 ? 1 : 0,
      y,
    },
    config: {
      friction: 100,
    },
  });

  return (
    <g>
      <animated.rect
        x={x}
        y={springProps.y}
        width={springProps.barWidth}
        height={barHeight}
        opacity={0.7}
        stroke="#9d174d"
        fill="#9d174d"
        fillOpacity={0.3}
        strokeWidth={1}
        rx={1}
      />
      <animated.text
        x={springProps.barWidth?.to((width) => width - 7)}
        y={springProps.y?.to((y) => y + barHeight / 2)}
        textAnchor="end"
        alignmentBaseline="central"
        fontSize={12}
        opacity={springProps.valueOpacity}
      >
        {springProps.value?.to((value) => value.toFixed(0))}
      </animated.text>
      <animated.text
        x={x + 7}
        y={springProps.y?.to((y) => y + barHeight / 2)}
        textAnchor="start"
        alignmentBaseline="central"
        fontSize={12}
      >
        {name}
      </animated.text>
    </g>
  );
};

Ranking

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!