Histogram

Dataviz logo representing a Histogram chart.

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

This example explains how to plot several groups on the same histogram, by overlapping them on the same X axis. It can be useful to compare the distribution of several items in a dataset.

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

Useful links

Plot and code

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

The distribution of several groups are displayed on the same figure, allowing to easily compare them. Please note that this kind of visual works well when there is a clear distinction between groups. Otherwise, bars will overlap each other resulting in an unreadable chart.

Histogram representing the distribution of 3 groups in a dataset. Made with react (rendering) and d3.js (scales)

The Data

The dataset used here is slightly different as the one used for the simple 1 group histogram.

An array is used, with each object in it providing the name (group property here) and the values of a group.

Here is a minimal example of the data structure:

const data = [
  {
    group: "A",
    values: [0, 0, 2, 2, 2, 0]
  },
  {
    group: "B",
    values: [0, 0, 2, 2, 2, 0]
  },
  ...
];

Color scale

There is a finite number of groups here. We need to assign a specific color to each group. This is called an ordinal scale and is implemented in the d3 scaleOrdinal function.

What's needed here is thus a list of colors to use (the range) and a list of group names: the domain.

To put it in a nutshell, that's how the color scale is implemented:

// List of arbitrary colors
const COLORS = ["#e0ac2b", "#e85252", "#6689c6", "#9a6fb0", "#a53253"];

// List of all group names
const allGroups = data.map((group) => group.group);

// Color scale
const colorScale = d3.scaleOrdinal<string>()
  .domain(allGroups)
  .range(COLORS);

It's fundamental to understand that with this code organization, d3.js will be used to prepare the svg circle, but it's react that will render them in the return() statement. We won't use d3 methods like append that you can find in usual d3.js examples.

Building the histogram buckets

The exact same logic as the one used on the 1 group histogram must be used here. But the bucketGenerator must be run on each group of the dataset.

Once it is done we'll have to map twice to render the rectangles. Once for each group, and a second time for each bar in the group.

// Create a function that creates buckets from a blob of data
const bucketGenerator = useMemo(() => {
  return d3
    .bin()
    .value((d) => d)
    .domain(xScale.domain())
    .thresholds(xScale.ticks(BUCKET_NUMBER));
}, [xScale]);

// Use the function for all groups of the dataset, one by one
// The result is an array with bucket details of each group
const groupBuckets = useMemo(() => {
  return data.map((group) => {
    return { group, buckets: bucketGenerator(group.values) };
  });
}, [data]);

// render the rects: group by group, bar by bar
const allRects = groupBuckets.map((group, i) =>
  group.buckets.map((bucket, j) => (
    <rect
      key={i + "_" + j}
      fill={colorScale(group.group)}
      opacity={0.7}
      x={xScale(bucket.x0) + BUCKET_PADDING / 2}
      width={xScale(bucket.x1) - xScale(bucket.x0) - BUCKET_PADDING}
      y={yScale(bucket.length)}
      height={boundsHeight - yScale(bucket.length)}
    />
  ))
);

Distribution

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!