DEV Community

Cover image for #1.  Building Color Wheel ๐ŸŽฏ visualisation  (visx cookbook ๐Ÿ“š)
Bouzaine Chamsddine
Bouzaine Chamsddine

Posted on

#1. Building Color Wheel ๐ŸŽฏ visualisation (visx cookbook ๐Ÿ“š)

Hi ๐Ÿ‘‹

Welcome to the data visualization cookbook here you will learn how to play with primitive SVG shapes and d3 functions to build magical visualizations.

Our new start Visx ๐ŸŒŸ

Visx Airbnb is a collection of expressive, low-level visualization primitives for React built on top of the famous D3 library, Visx provide us with the fundamentals pieces that we need to build our data visualization, and our library their goal is to empower devs to build their data visualization component libraries.

Why I built this series ๐Ÿง ?

I was working with d3 inside of react to build some custom charts and DataViz for my client and having the shittest experience ever trying to combine d3 and react together, and like that, I stumbled into VISX my savior this library it's all that I wanted it gave me freedom and power of d3 while keeping my boy react in charge of the dom, one problem I found with Visx there is not enough documentation and examples especially for people that don't master d3 so I took it upon my self to change this fact and create this series of 20 recipes to teach people how to use this amazing tool also while building a data viz library with 19 components, follow along with reader at the end of this series you will have learned Visx, some new charts and built a react visualization library ๐Ÿฅ.

Visualizing-Meme

Go!

#1. Building Color Wheel ๐ŸŽฏ

In this first recipe we are going to build a Color Wheel it's simple and beginner-friendly and at the same time will help us get a good grasp of Visx and its capabilities.

This piece will be useful to those starting with Visx. Weโ€™ll be looking over methods for creating and manipulating color, with examples included. Note that no prior knowledge of Visx is required to follow this article.

Key visx ingredient of our recipe ๐Ÿ—๏ธ :

1. Prepare Data

To visualize data, we'll want to represent data points as shapes. so to start we need to look at our data first our data is a multidimensional array each array represents colors of the rainbow at a certain brightness, so to build our color wheel all we need to do is to map each array to a pie of arcs and like that we will have a wheel of multiple pies.

const data = [
// a pie  
[
    'rgb(76, 110, 219)', // a single arc
    'rgb(110, 64, 170)',
    'rgb(191, 60, 175)',
    'rgb(254, 75, 131)',
    'rgb(255, 120, 71)',
    'rgb(226, 183, 47)',
    'rgb(175, 240, 91)',
    'rgb(82, 246, 103)',
    'rgb(29, 223, 163)',
    'rgb(35, 171, 216)'
  ],
// 2 nd pie 
 [
    'rgb(76, 110, 219)', // a single arc
    'rgb(110, 64, 170)',
    'rgb(191, 60, 175)',
    ...
  ],
    .....
];
Enter fullscreen mode Exit fullscreen mode

color wheel data visualization data structure /data.js

2. Build a Color Grid

Let's first build something easy to give is a better idea of our data, we are going to build a color matrix each row represents an array of our matrix and each rectangle represents a single data point with its color as that data point value.


import React from "react";
import { colorMatrix } from "./data";
import { color as d3Color } from "d3-color";

const convertRgbToHex = (rgb) => d3Color(rgb).formatHex();

export default function Example({ width, height }) {
  return width < 10 ? null : (
    <div className="chords">
      <svg width={width} height={height}>
        {colorMatrix.map((arr, id) => {
          return arr.map((color, key) => {
            return (
              <>
                <rect
                  fill={color}
                  width={width / 8}
                  x={(width / 8) * key}
                  y={50 * id}
                  height={"50"}
                ></rect>
                <text y={50 * id} fill="white" x={(width / 8) * key}>
                  {convertRgbToHex(color)}
                </text>
              </>
            );
          });
        })}
      </svg>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here the code is very straightforward we are iterating over the matrix and then iterating on the individual array to build rectangles all we are manipulating is using the index to change the position of the rectangles, quite easy isn't it, I think with this we are pretty sure we understand our data structure, let's map this idea to the color wheel in the color wheel each array represents a single pie and each data point (color) represents an arc of that pie,
An Arc is a primitive shape of the Visx shapes module "@visx/shape" it's built on D3 arc shape, this arc component can build us an arc path using D3 magic.

3. Build Arcs

We can see here we can use an arc component by itself without any data and the props are pretty easy to understand bounding angles, pad between arcs, the radius of the arc corners and by the way, the arc also takes any SVG props like (fill, opacity, ....)

import React from "react";
import { Arc } from "@visx/shape";
import { Group } from "@visx/group";

export default function Example({ width, height }) {
  return width < 10 ? null : (
    <div className="chords">
      <svg width={width} height={height}>
        <Group top={height / 2} left={width / 2}>
          <Arc
            startAngle={0}
            endAngle={2}
            outerRadius={20}
            innerRadius={150}
            padAngle={0.1}
            cornerRadius={3}
            fill={"#F28F38"}
          />
          <Arc
            startAngle={2}
            endAngle={6.3}
            outerRadius={20}
            innerRadius={150}
            padAngle={0}
            cornerRadius={3}
            fill={"#F25757"}
            opacity={1}
          />
        </Group>
      </svg>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In our color wheel, our arcs had text in the middle of them representing the colors in HEX we need to create a custom arc component to have that capability, Visx provides a pretty reactive way of overriding the arc render using "React Render prop pattern" basically Vsix pass all the SVG path generated by d3.arc function to the children component of component as we can see here from source code.

const path = arc({
    innerRadius,
    outerRadius,
    cornerRadius,
    startAngle,
    endAngle,
    padAngle,
    padRadius,
  });

  // eslint-disable-next-line react/jsx-no-useless-fragment
  if (children) return <>{children({ path })}</>;
Enter fullscreen mode Exit fullscreen mode

Build custom arc with text (render props)

const CustomArc = ({ path, color }) => {
  return (
    <>
      <path d={path()} fill={color} />
      <text
        fill="white"
        x={path.centroid(path)[0]}
        y={path.centroid(path)[1]}
        dy=".33em"
        fontSize={8}
        textAnchor="middle"
        pointerEvents="none"
      >
        {color}
      </text>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

To build our custom arc we use the path function we pass it to the path SVG element and we pass our color value to the text element we use the path.centroid to center our text on top of the arc

we use our custom arc like this :

<Arc
    startAngle={5}
    endAngle={6.3}
    outerRadius={20}
    innerRadius={150}
    padAngle={0.1}
    cornerRadius={3}
    fill={"#F25757"}
    opacity={1}
    >
    {({ path }) => <CustomArc color={"#F56754"} path={path} />}
</Arc>
Enter fullscreen mode Exit fullscreen mode

Now we learned how to build an arc next is how to build a pie with arcs?

4. Build a Pie

Pie is a primitive shape of the Visx shapes module "@visx/shape" it's built on top of d3.pie shape, this pie component generates Arcs shapes data based on provided data prop

So in this example, we can see that the pie component take as prop data as an array that data will be used to create arcs and the size of the arcs will follow the data in the array


<Pie
  data={[1, 1, 2, 3, 5, 8, 13, 21]}
  cornerRadius={3}
  padAngle={0.005}
  >
  {(pie) => <PieArc {...pie} radius={radius} pieNumber={0} />}
</Pie>
Enter fullscreen mode Exit fullscreen mode
const PieArc = ({ arcs, radius, pieNumber }) => {
  return arcs.map(({ startAngle, endAngle }, key) => {
    return (
      <Arc
        key={key}
        startAngle={startAngle}
        endAngle={endAngle}
        outerRadius={(radius * (pieNumber + 1)) / 3 - PIES_SPACING}
        innerRadius={radius * (pieNumber / 3)}
        fill={COLORS[pieNumber + 1]}
        padAngle={ARCS_SPACING}
        cornerRadius={5}
      />
    );
  });
};
Enter fullscreen mode Exit fullscreen mode

We can also pass a prop "pieValue" that can be used as an accessor if the array contains objects we can pass a function to "pieValue" to select the value that we need for example if we have an array with

 [{name : 'beta', value:2}, {name : 'beta', value:5}] 
Enter fullscreen mode Exit fullscreen mode

we will need to pass this prop to Pie component

pieValue={(d) => d.value}
Enter fullscreen mode Exit fullscreen mode

If we want to ignore the values inside of the data prop and have all the arcs of the same size we need to pass this prop to the component

pieValue={(d) => 1}
Enter fullscreen mode Exit fullscreen mode

5. Let's build our color Wheel

Now we have all the ingredients for our main dish the color wheel let's now stir them up.

We start by creating our main component that will call the color wheel and will add to it "ParentSize" component from "@visx/responsive" module to make sure that our data visualization will be responsive.

import React from "react";
import { render } from "react-dom";
import { ParentSize } from "@visx/responsive";
import ColorWheel from "./ColorWheel";
import "./sandbox-styles.css";

render(
  <ParentSize>
    {({ width, height }) => <ColorWheel width={width} height={height} />}
  </ParentSize>,
   document.getElementById("root")
  );
Enter fullscreen mode Exit fullscreen mode

Now let's create our main component the colorWheel
1. Our main component takes ** height, width** as props and use is to calculate the radius of the wheel
2. We are using the <Group/> Visx component to wrap the components in a SVG element
3. Our data is a multidimensional array with each array containing colors of a single pie, we iterate over them and we pass each array of color to a component also we are providing an accessor prop pieValue to make sure all created arcs will have the same size
4. We pass the generated data to our custom Arcs component and also we pass the length and the radius of the pies also the pieNumber (index of the current pie) to calculate the with and radiuses of each pie.

export function ColorWheel({ width, height }) {
  const radius = Math.min(width, height) / 2;

  return (
    <div>
      <svg width={width} height={height}>
        <rect width={width} height={height} fill={BACKGROUND_COLOR} rx={14} />
        <Group top={height / 2} left={width / 2}>
          {colorMatrix.map((el, key) => (
            <g key={`wheels-${key}`}>
              <Pie
                key={key}
                data={colorMatrix[key]}
                cornerRadius={3}
                padAngle={0.005}
                pieValue={d => 1}
              >
                {pie => {
                  return (
                    <ColorPie
                      {...pie}
                      pieRadius={radius}
                      currentPieNumber={key}
                      numberOfPies={colorMatrix.length}
                    />
                  );
                }}
              </Pie>
            </g>
          ))}
        </Group>
      </svg>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We build the component that will be responsible for passing the arcs data to the custom arcs to build each Pie
1. here we are passing a constant arc spacing to separate the arcs.
2. we are using the utility functions to calculate the arcs outer and inner radius, cause by default the pie generate arcs that will build a full pie but here we need rings so for that we need to calculate each arc outer and inner radius.

const getArcOuterRadius = ({ pieRadius, numberOfPies, currentPieNumber }) =>
          (pieRadius * (currentPieNumber + 1)) / numberOfPies - PIE_SPACING;

const getArcInnerRadius = ({ pieRadius, numberOfPies, currentPieNumber }) =>
          pieRadius * (currentPieNumber / numberOfPies)
Enter fullscreen mode Exit fullscreen mode
const ColorPieArc = props => {
  const { arcs, pieRadius, currentPieNumber, numberOfPies } = props;
  return arcs.map(({ startAngle, endAngle, data }, key) => {
    return (
      <Arc
        key={key}
        startAngle={startAngle}
        endAngle={endAngle}
        outerRadius={getArcOuterRadius({
          pieRadius,
          numberOfPies,
          currentPieNumber
        })}
        innerRadius={getArcInnerRadius({
          pieRadius,
          numberOfPies,
          currentPieNumber
        })}
        padAngle={ARCS_SPACING}
        cornerRadius={5}
      >
        {({ path }) => (
          <CustomArc
            color={data}
            path={path}
            i={key}
            opacity={1}
            currentPieNumber={currentPieNumber}
          />
        )}
      </Arc>
    );
  });
};

Enter fullscreen mode Exit fullscreen mode

The last step is to build our as we did before

const CustomArc = ({ path, color }) => {
  return (
    <>
      <path d={path()} fill={color} />
      <text
        fill="white"
        x={path.centroid(path)[0]}
        y={path.centroid(path)[1]}
        dy=".33em"
        fontSize={8}
        textAnchor="middle"
        pointerEvents="none"
      >
        {convertRgbToHex(color)}
      </text>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Here it's the same component we built before the difference here is that our data is in RGB so we used a function convertRgbToHex to convert RGB to hex.

const convertRgbToHex = (rgb) => d3Color(rgb).formatHex();
Enter fullscreen mode Exit fullscreen mode

Bonus :

Generate color wheel data :

Creating the color data is a fun way to learn some d3 color techniques, here we are using interpolateRainbow a function that when we provide it with a number from 0 to 1 it gives an RGB color from the rainbow so what we do is iterate over the pies and we use visx/scale to get the map for the index of the element to a number between 0 and 1.

import { scaleLinear } from "@visx/scale";
import { _arrayOf } from "./utils";
import { interpolateRainbow } from "d3-scale-chromatic";

const numberOfArcs = 10;
const numberOfPies = 8;
let pies = _arrayOf(numberOfPies, []);

for (let i = 0; i < numberOfArcs; i++) {
  let scale = scaleLinear({
    domain: [0, numberOfPies],
    range: [(i - 1) / numberOfArcs, i / numberOfArcs]
  });
  pies = pies.map((d, i) => {
    return [...d, interpolateRainbow(scale(i))];
  });
}
export const colorMatrix = pies;
Enter fullscreen mode Exit fullscreen mode

Some Variation :

#1 Color wheel without using the pie component

#2 Color wheel with native SVG elements and react-spring animation

Top comments (0)