Parallel coordinates

Dataviz logo representing a Parallel1 chart.

A parallel coordinate chart is a type of visualization used to represent multivariate data on a two-dimensional plane by plotting each variable as a separate axis arranged in parallel, and then connecting the data points with lines.

This page is a step-by-step guide on how to build your own parallel coordinate chart for the web, using React (for rendering) and D3.js (to compute the axis, and shape coordinates).

It starts by describing how the data should be organized and how to initialize the parallel coordinate component. It then explains how to compute axis dynamically, and plot the lines and axis. Once this is done, it shows how to deal with scaling and how to add an interactive legend. 🙇‍♂️.

Useful links

The Data

The dataset provides several numeric values for a set of data points. It can also add some categorical variables that can be added to customize the marker colors.

The suggested data structure is an array of object, where each object is a data point. It can have as many numeric properties as needed.


Here is a minimal example of the data structure:

const data = [
  {var1: 5.1, var2: 3.5, ..., group: 'setosa'},
  {var1: 4.9, var2: 3.0, ..., group: 'setosa'},
  ...
]

Note: this is the same data format as for a correlogram

Component skeleton

The goal here is to create a ParallelCoordinate component that will be stored in a ParallelCoordinate.tsx file. This component requires 4 props to render: a width, a height, some data and an array providing the name of the variables to display.

The shape of the data is described above. The width and height will be used to render an svg element in the DOM, in which we will insert the histogram.

To put it in a nutshell, that's the skeleton of our ParallelCoordinate component:

import * as d3 from "d3"; // we will need d3.js

type DataItem = {
  [variable: string]: number;
} & { group: string };


type ParallelCoordinateProps = {
  width: number;
  height: number;
  data: DataItem[];
  variables: string[]
};

export const ParallelCoordinate = ({ width, height, data, variables }: ParallelCoordinateProps) => {

  // read the data & get a list of groups
  // build X scale
  // build Y scales: 1 per variable
  // build color scales
  // loop through variables to add axes
  // loop through data items and through variables to draw lines

  return (
    <div>
      <svg width={width} height={height}>
        // render all the <lines>
      </svg>
    </div>
  );
};

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.

Scales and Axes

Building a parallel coordinate charts requires several scales and axes.

D3.js comes with a handful set of predefined scales. scalePoint and scaleLinear are the ones we are goint to use here.

→ X Scale

We need only 1 X scale. This scale is gonna provide a position in pixels for each variable name of the dataset. Remember that a parallel coordinate chart displays several vertical lines, one per variable. The X scale is displayed horizontally. It covers the width of the svg container, and its domain goes from the min to the max of the dataset.

const xScale = d3
  .scalePoint<Variable>()
  .range([0, boundsWidth])
  .domain(variables)
  .padding(0);

→ Y Scale

The Y scale is displayed vertically. It shows how many items are available in each bin. To compute it you need to find the bucket with the highest number of items. Something like:

const yScale = useMemo(() => {

  const max = Math.max(...buckets.map((bucket) => bucket?.length));

  return d3.scaleLinear()
    .range([height, 0])
    .domain([0, max]);

  }, [data, height]);
petalLength012345678petalWidth012345678sepalLength012345678sepalWidth012345678

Values of the dataset as distributed into bins. Bins are represented as rectangles. Data wrangling is made with d3.js, rendering with react.

Drawing the lines

Finally! ✨

We can now map through the bucket object and draw a rectangle per bucket thanks to the scales computed above.

The code looks like this:

const allRects = buckets.map((bucket, i) => {
  return (
    <rect
      key={i}
      fill="#69b3a2"
      stroke="black"
      x={xScale(bucket.x0)}
      width={xScale(bucket.x1) - xScale(bucket.x0)}
      y={yScale(bucket.length)}
      height={height - yScale(bucket.length)}
    />
  );
});

Remember that the x and y attributes of the svg rect element provide the x and y position of the top left corner of the rectangle (see doc). This is why the rectangle height is computed by subtracting yScale(bucket.length) from the total height.


petalLength012345678petalWidth012345678sepalLength012345678sepalWidth012345678

Values of the dataset as distributed into bins. Bins are represented as rectangles. Data wrangling is made with d3.js, rendering with react.

Responsive Parallel with react

The component above is not responsive. It expects 2 props called width and height and will render a Parallel of those dimensions.

Making the Parallel responsive requires adding a wrapper component that gets the dimension of the parent div, and listening to a potential dimension change. This is possible thanks to a hook called useDimensions that will do the job for us.

useDimensions: a hook to make your viz responsive
export const useDimensions = (targetRef: React.RefObject<HTMLDivElement>) => {

  const getDimensions = () => {
    return {
      width: targetRef.current ? targetRef.current.offsetWidth : 0,
      height: targetRef.current ? targetRef.current.offsetHeight : 0
    };
  };

  const [dimensions, setDimensions] = useState(getDimensions);

  const handleResize = () => {
    setDimensions(getDimensions());
  };

  useEffect(() => {
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  useLayoutEffect(() => {
    handleResize();
  }, []);

  return dimensions;
}

I'm in the process of writing a complete blog post on the topic. Subscribe to the project to know when it's ready.




Parallel inspiration

If you're looking for inspiration to create your next Parallel, note that dataviz-inspiration.com showcases many examples. Definitely the best place to get ... inspiration!

dataviz-inspiration.com showcases hundreds of stunning dataviz projects. Have a look to get some ideas on how to make your Parallel looks good!

visit

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!