Parallel coordinates
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. 🙇♂️.
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]);
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
.
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!
visitContact
👋 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!