Categories
Visx

Add a Dendrogram into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add a dendrogram into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/gradient @visx/group @visx/hierarchy @visx/responsive @visx/shape

to install the packages.

Create the Dendrogram

We can create the dendrogram by writing:

import React, { useMemo } from "react";
import { Group } from "@visx/group";
import { Cluster, hierarchy } from "@visx/hierarchy";
import { LinkVertical } from "@visx/shape";
import { LinearGradient } from "@visx/gradient";

const citrus = "#ddf163";
const white = "#ffffff";
const green = "#79d259";
const aqua = "#37ac8c";
const merlinsbeard = "#f7f7f3";
const background = "#306c90";

const clusterData = {
  name: "$",
  children: [
    {
      name: "A",
      children: [
        { name: "A1" },
        { name: "A2" },
        {
          name: "C",
          children: [
            {
              name: "C1"
            }
          ]
        }
      ]
    },
    {
      name: "B",
      children: [{ name: "B1" }, { name: "B2" }]
    },
    {
      name: "X",
      children: [
        {
          name: "Z"
        }
      ]
    }
  ]
};

function Node({ node }) {
  const isRoot = node.depth === 0;
  const isParent = !!node.children;

  if (isRoot) return <RootNode node={node} />;

  return (
    <Group top={node.y} left={node.x}>
      {node.depth !== 0 && (
        <circle
          r={12}
          fill={background}
          stroke={isParent ? white : citrus}
          onClick={() => {
            alert(`clicked: ${JSON.stringify(node.data.name)}`);
          }}
        />
      )}
      <text
        dy=".33em"
        fontSize={9}
        fontFamily="Arial"
        textAnchor="middle"
        style={{ pointerEvents: "none" }}
        fill={isParent ? white : citrus}
      >
        {node.data.name}
      </text>
    </Group>
  );
}

function RootNode({ node }) {
  const width = 40;
  const height = 20;
  const centerX = -width / 2;
  const centerY = -height / 2;

  return (
    <Group top={node.y} left={node.x}>
      <rect
        width={width}
        height={height}
        y={centerY}
        x={centerX}
        fill="url('#top')"
      />
      <text
        dy=".33em"
        fontSize={9}
        fontFamily="Arial"
        textAnchor="middle"
        style={{ pointerEvents: "none" }}
        fill={background}
      >
        {node.data.name}
      </text>
    </Group>
  );
}

const defaultMargin = { top: 40, left: 0, right: 0, bottom: 40 };

function Example({ width, height, margin = defaultMargin }) {
  const data = useMemo(() => hierarchy(clusterData), []);
  const xMax = width - margin.left - margin.right;
  const yMax = height - margin.top - margin.bottom;

  return width < 10 ? null : (
    <svg width={width} height={height}>
      <LinearGradient id="top" from={green} to={aqua} />
      <rect width={width} height={height} rx={14} fill={background} />
      <Cluster root={data} size={[xMax, yMax]}>
        {(cluster) => (
          <Group top={margin.top} left={margin.left}>
            {cluster.links().map((link, i) => (
              <LinkVertical
                key={`cluster-link-${i}`}
                data={link}
                stroke={merlinsbeard}
                strokeWidth="1"
                strokeOpacity={0.2}
                fill="none"
              />
            ))}
            {cluster.descendants().map((node, i) => (
              <Node key={`cluster-node-${i}`} node={node} />
            ))}
          </Group>
        )}
      </Cluster>
    </svg>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

We create the variables for the dendrogram at the top of the code.

The clusterData variable has the tree data for the dendrogram.

Next, we create the Node component to render the node of the dendrogram.

It has some text inside the Group component.

The root node is created with the RootBNode component.

It’s different from Node in that it has an extra rectangle surrounding the text.

The Example component is where the whole tree diagram comes together.

We render the tree nodes with the render prop of the Cluster component.

The data is set as the root prop’s value.

In the Group component of the render prop, we have the LinkVertical component to add the lines to link the parent and child nodes.

We get the child nodes wit the cluster.descendants method.

And we map them to Node s.

Conclusion

We can add a dendrogram into our React app with the Visx library.

Categories
Visx

Add Pie and Donut Charts into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add pie and donut charts into our React app

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/gradient @visx/group @visx/mock-data @visx/responsive @visx/scale @visx/shape

to install the packages.

Create the Pie and Donut Chart

We can create the pie and donut charts by writing:

import React, { useState } from "react";
import Pie from "@visx/shape/lib/shapes/Pie";
import { scaleOrdinal } from "@visx/scale";
import { Group } from "@visx/group";
import { GradientPinkBlue } from "@visx/gradient";
import letterFrequency from "@visx/mock-data/lib/mocks/letterFrequency";
import browserUsage from "@visx/mock-data/lib/mocks/browserUsage";
import { animated, useTransition, interpolate } from "react-spring";

const letters = letterFrequency.slice(0, 4);
const browserNames = Object.keys(browserUsage[0]).filter((k) => k !== "date");
const browsers = browserNames.map((name) => ({
  label: name,
  usage: Number(browserUsage[0][name])
}));

const usage = (d) => d.usage;
const frequency = (d) => d.frequency;
const getBrowserColor = scaleOrdinal({
  domain: browserNames,
  range: [
    "rgba(255,255,255,0.7)",
    "rgba(255,255,255,0.6)",
    "rgba(255,255,255,0.5)",
    "rgba(255,255,255,0.4)",
    "rgba(255,255,255,0.3)",
    "rgba(255,255,255,0.2)",
    "rgba(255,255,255,0.1)"
  ]
});
const getLetterFrequencyColor = scaleOrdinal({
  domain: letters.map((l) => l.letter),
  range: [
    "rgba(93,30,91,1)",
    "rgba(93,30,91,0.8)",
    "rgba(93,30,91,0.6)",
    "rgba(93,30,91,0.4)"
  ]
});

const defaultMargin = { top: 20, right: 20, bottom: 20, left: 20 };

function Example({ width, height, margin = defaultMargin, animate = true }) {
  const [selectedBrowser, setSelectedBrowser] = useState(null);
  const [selectedAlphabetLetter, setSelectedAlphabetLetter] = useState(null);

  if (width < 10) return null;

  const innerWidth = width - margin.left - margin.right;
  const innerHeight = height - margin.top - margin.bottom;
  const radius = Math.min(innerWidth, innerHeight) / 2;
  const centerY = innerHeight / 2;
  const centerX = innerWidth / 2;
  const donutThickness = 50;

  return (
    <svg width={width} height={height}>
      <GradientPinkBlue id="visx-pie-gradient" />
      <rect
        rx={14}
        width={width}
        height={height}
        fill="url('#visx-pie-gradient')"
      />
      <Group top={centerY + margin.top} left={centerX + margin.left}>
        <Pie
          data={
            selectedBrowser
              ? browsers.filter(({ label }) => label === selectedBrowser)
              : browsers
          }
          pieValue={usage}
          outerRadius={radius}
          innerRadius={radius - donutThickness}
          cornerRadius={3}
          padAngle={0.005}
        >
          {(pie) => (
            <AnimatedPie
              {...pie}
              animate={animate}
              getKey={(arc) => arc.data.label}
              onClickDatum={({ data: { label } }) =>
                animate &&
                setSelectedBrowser(
                  selectedBrowser && selectedBrowser === label ? null : label
                )
              }
              getColor={(arc) => getBrowserColor(arc.data.label)}
            />
          )}
        </Pie>
        <Pie
          data={
            selectedAlphabetLetter
              ? letters.filter(
                  ({ letter }) => letter === selectedAlphabetLetter
                )
              : letters
          }
          pieValue={frequency}
          pieSortValues={() => -1}
          outerRadius={radius - donutThickness * 1.3}
        >
          {(pie) => (
            <AnimatedPie
              {...pie}
              animate={animate}
              getKey={({ data: { letter } }) => letter}
              onClickDatum={({ data: { letter } }) =>
                animate &&
                setSelectedAlphabetLetter(
                  selectedAlphabetLetter && selectedAlphabetLetter === letter
                    ? null
                    : letter
                )
              }
              getColor={({ data: { letter } }) =>
                getLetterFrequencyColor(letter)
              }
            />
          )}
        </Pie>
      </Group>
    </svg>
  );
}

const fromLeaveTransition = ({ endAngle }) => ({
  startAngle: endAngle > Math.PI ? 2 * Math.PI : 0,
  endAngle: endAngle > Math.PI ? 2 * Math.PI : 0,
  opacity: 0
});

const enterUpdateTransition = ({ startAngle, endAngle }) => ({
  startAngle,
  endAngle,
  opacity: 1
});

function AnimatedPie({ animate, arcs, path, getKey, getColor, onClickDatum }) {
  const transitions = useTransition(arcs, getKey, {
    from: animate ? fromLeaveTransition : enterUpdateTransition,
    enter: enterUpdateTransition,
    update: enterUpdateTransition,
    leave: animate ? fromLeaveTransition : enterUpdateTransition
  });
  return (
    <>
      {transitions.map(({ item: arc, props, key }) => {
        const [centroidX, centroidY] = path.centroid(arc);
        const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.1;

        return (
          <g key={key}>
            <animated.path
              d={interpolate(
                [props.startAngle, props.endAngle],
                (startAngle, endAngle) =>
                  path({
                    ...arc,
                    startAngle,
                    endAngle
                  })
              )}
              fill={getColor(arc)}
              onClick={() => onClickDatum(arc)}
              onTouchStart={() => onClickDatum(arc)}
            />
            {hasSpaceForLabel && (
              <animated.g style={{ opacity: props.opacity }}>
                <text
                  fill="white"
                  x={centroidX}
                  y={centroidY}
                  dy=".33em"
                  fontSize={9}
                  textAnchor="middle"
                  pointerEvents="none"
                >
                  {getKey(arc)}
                </text>
              </animated.g>
            )}
          </g>
        );
      })}
    </>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

We create the letters variable to add the data for the pie chart.

browerNames and browsers have the data for the donut chart.

usage and frequency are accessor functions for the data.

getBrowserData is a function that returns the browser donut chart colors.

getLetterFrequencyColor is a function that returns the browser pie chart colors.

defaultMargin have the margins for the charts.

The Example component is the main chart containers.

We have the selectedBrowser and selectedAlphabetLetter states to keep track of the select donut and pie segments respectively.

They’re selected when we click on them.

Next, we define the innerWidth , innerHeight n radius , centerY , centerX , and donutThickness variables to compute the dimensions for the charts.

In the return statement, we haver the GradientPinkBlue component with the background.

In the Group component, we have the Pie components.

They define the pie and donut charts.

The first Pie has the pie donut chart.

And the 2nd Pie is the pie chart inside the donut chart.

Each of them has a render prop that returns the AnimatedPie component to add animation for the pie and donut charts.

They have the onClickDatum prop which lets us set the selectedBrowser and selectedAlphabetLetter states respectively.

The getColor prop has a function that returns the color of the pie and donut segments.

The AnimatedPie component is created by rendering the animated.path to render the pie or donut.

We use the useTransition hook to add transition effects.

Conclusion

We can add pie and donut charts easily into our React app with the Visx library.

Categories
Visx

Add a Map View into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add a map view into our React app

Install Required Packages

We have to install a few modules to create the map.

To get started, we run:

npm i @visx/geo @visx/responsive @visx/scale @visx/zoom

to install the packages.

Add the Map

We can add the map by writing:

import React, { useState } from "react";
import * as topojson from "topojson-client";
import { scaleQuantize } from "@visx/scale";
import { CustomProjection, Graticule } from "@visx/geo";
import { Projection } from "@visx/geo/lib/types";
import { Zoom } from "@visx/zoom";
import {
  geoConicConformal,
  geoTransverseMercator,
  geoNaturalEarth1,
  geoConicEquidistant,
  geoOrthographic,
  geoStereographic
} from "d3-geo";
import topology from "./world-topo.json";

export const background = "#252b7e";
const purple = "#201c4e";
const PROJECTIONS = {
  geoConicConformal,
  geoTransverseMercator,
  geoNaturalEarth1,
  geoConicEquidistant,
  geoOrthographic,
  geoStereographic
};

const world = topojson.feature(topology, topology.objects.units);
const color = scaleQuantize({
  domain: [
    Math.min(...world.features.map((f) => f.geometry.coordinates.length)),
    Math.max(...world.features.map((f) => f.geometry.coordinates.length))
  ],
  range: [
    "#019ece",
    "#f4448b",
    "#fccf35",
    "#82b75d",
    "#b33c88",
    "#fc5e2f",
    "#f94b3a",
    "#f63a48",
    "#dde1fe",
    "#8993f9",
    "#b6c8fb",
    "#65fe8d"
  ]
});

function Example({ width, height, events = true }: GeoCustomProps) {
  const [projection, setProjection] = useState("geoConicConformal");

  const centerX = width / 2;
  const centerY = height / 2;
  const initialScale = (width / 630) * 100;

  return width < 10 ? null : (
    <>
      <Zoom
        width={width}
        height={height}
        scaleXMin={100}
        scaleXMax={1000}
        scaleYMin={100}
        scaleYMax={1000}
        transformMatrix={{
          scaleX: initialScale,
          scaleY: initialScale,
          translateX: centerX,
          translateY: centerY,
          skewX: 0,
          skewY: 0
        }}
      >
        {(zoom) => (
          <div className="container">
            <svg
              width={width}
              height={height}
              className={zoom.isDragging ? "dragging" : undefined}
            >
              <rect
                x={0}
                y={0}
                width={width}
                height={height}
                fill={background}
                rx={14}
              />
              <CustomProjection
                projection={PROJECTIONS[projection]}
                data={world.features}
                scale={zoom.transformMatrix.scaleX}
                translate={[
                  zoom.transformMatrix.translateX,
                  zoom.transformMatrix.translateY
                ]}
              >
                {(customProjection) => (
                  <g>
                    <Graticule
                      graticule={(g) => customProjection.path(g) || ""}
                      stroke={purple}
                    />
                    {customProjection.features.map(({ feature, path }, i) => (
                      <path
                        key={`map-feature-${i}`}
                        d={path || ""}
                        fill={color(feature.geometry.coordinates.length)}
                        stroke={background}
                        strokeWidth={0.5}
                        onClick={() => {
                          if (events)
                            alert(
                              `Clicked: ${feature.properties.name} (${feature.id})`
                            );
                        }}
                      />
                    ))}
                  </g>
                )}
              </CustomProjection>
              <rect
                x={0}
                y={0}
                width={width}
                height={height}
                rx={14}
                fill="transparent"
                onTouchStart={zoom.dragStart}
                onTouchMove={zoom.dragMove}
                onTouchEnd={zoom.dragEnd}
                onMouseDown={zoom.dragStart}
                onMouseMove={zoom.dragMove}
                onMouseUp={zoom.dragEnd}
                onMouseLeave={() => {
                  if (zoom.isDragging) zoom.dragEnd();
                }}
              />
            </svg>
            {events && (
              <div className="controls">
                <button
                  className="btn btn-zoom"
                  onClick={() => zoom.scale({ scaleX: 1.2, scaleY: 1.2 })}
                >
                  +
                </button>
                <button
                  className="btn btn-zoom btn-bottom"
                  onClick={() => zoom.scale({ scaleX: 0.8, scaleY: 0.8 })}
                >
                  -
                </button>
                <button className="btn btn-lg" onClick={zoom.reset}>
                  Reset
                </button>
              </div>
            )}
          </div>
        )}
      </Zoom>
      <label>
        projection:{" "}
        <select onChange={(event) => setProjection(event.target.value)}>
          {Object.keys(PROJECTIONS).map((projectionName) => (
            <option key={projectionName} value={projectionName}>
              {projectionName}
            </option>
          ))}
        </select>
      </label>
      <style jsx>{`
        .container {
          position: relative;
        }
        svg {
          cursor: grab;
        }
        svg.dragging {
          cursor: grabbing;
        }
        .btn {
          margin: 0;
          text-align: center;
          border: none;
          background: #dde1fe;
          color: #222;
          padding: 0 4px;
          border-top: 1px solid #8993f9;
        }
        .btn-lg {
          font-size: 12px;
          line-height: 1;
          padding: 4px;
        }
        .btn-zoom {
          width: 26px;
          font-size: 22px;
        }
        .btn-bottom {
          margin-bottom: 1rem;
        }
        .controls {
          position: absolute;
          bottom: 20px;
          right: 15px;
          display: flex;
          flex-direction: column;
          align-items: flex-end;
        }
        label {
          font-size: 12px;
        }
      `}</style>
    </>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

world-topo.json can be found at https://codesandbox.io/s/github/airbnb/visx/tree/master/packages/visx-demo/src/sandboxes/visx-geo-custom?file=/world-topo.json

We add the background and purple to add the colors for the map.

PROJECTIONS have the types of map views that are available.

We transform the data into something that we can display by calling the topojson.feature method with the JSON.

The scaleQuantize method lets us add the colors to the countries in the map.

In the Example component, we have the projection state to display the map with the given projection type.

We set the center coordinatesd with the centerX and centerY variables.

And we set the initialScale variable to the initial zoom level.

To display the map, we add the Zoom component to add the zoom.

Then in its render prop, we add the CustomProjection component to add the map.

And the Graticule component lets us add the grid lines on the map.

Below that, we add the rectangle to let us drag to pan the map with the touch and mouse handlers.

And below that, we add the buttons to add zoom and reset zoom capabilities.

Then we add the select dropdown to let us select the project by changing the projection state.

We used that in the CustomProjection component.

Finally, we add the styles for the map.

Conclusion

We can add a map view with the components that are available with the Visx library.

Categories
Visx

Add a Mercator Map into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add a map view into our React app

Install Required Packages

We have to install a few modules to create the map.

To get started, we run:

npm i @visx/geo @visx/responsive @visx/scale

to install the packages.

Add the Map

We can add the map with the Mercator projection by writing:

import React from "react";
import { scaleQuantize } from "@visx/scale";
import { Mercator, Graticule } from "@visx/geo";
import * as topojson from "topojson-client";
import topology from "./world-topo.json";

const background = "#f9f7e8";
const world = topojson.feature(topology, topology.objects.units);

const color = scaleQuantize({
  domain: [
    Math.min(...world.features.map((f) => f.geometry.coordinates.length)),
    Math.max(...world.features.map((f) => f.geometry.coordinates.length))
  ],
  range: [
    "#ffb01d",
    "#ffa020",
    "#ff9221",
    "#ff8424",
    "#ff7425",
    "#fc5e2f",
    "#f94b3a",
    "#f63a48"
  ]
});

const Example = ({ width, height, events = false }) => {
  const centerX = width / 2;
  const centerY = height / 2;
  const scale = (width / 630) * 100;

  return width < 10 ? null : (
    <svg width={width} height={height}>
      <rect
        x={0}
        y={0}
        width={width}
        height={height}
        fill={background}
        rx={14}
      />
      <Mercator
        data={world.features}
        scale={scale}
        translate={[centerX, centerY + 50]}
      >
        {(mercator) => (
          <g>
            <Graticule
              graticule={(g) => mercator.path(g) || ""}
              stroke="rgba(33,33,33,0.05)"
            />
            {mercator.features.map(({ feature, path }, i) => (
              <path
                key={`map-feature-${i}`}
                d={path || ""}
                fill={color(feature.geometry.coordinates.length)}
                stroke={background}
                strokeWidth={0.5}
                onClick={() => {
                  if (events)
                    alert(
                      `Clicked: ${feature.properties.name} (${feature.id})`
                    );
                }}
              />
            ))}
          </g>
        )}
      </Mercator>
    </svg>
  );
};

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

world-topo.json can be found at https://codesandbox.io/s/github/airbnb/visx/tree/master/packages/visx-demo/src/sandboxes/visx-geo-custom?file=/world-topo.json

We add the color variable to add the colors of the countries on the map.

Then in the Example component, we add the centerX and centerY variables to set the center of the map.

scale has the initial zoom level.

Then to add a 2D map that uses the Mercator projection, we add the Mercator component.

The data prop is set to the world variable, which created from the topojson.feature method with th topology JSON as the argument.

Then in its render prop, we add the Graticule component to add the map grid lines.

And the path element draws the countries.

Conclusion

We can add a simple 2D map easily into our React app with the Visx library.

Categories
Visx

Add Drag and Drop Features into Our React App with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add curves with segments that have their own styles into our React app

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/drag @visx/gradient @visx/responsive @visx/scale

to install the packages.

Add Drag and Drop Feature

We add drag and drop by writing the following code:

import React, { useMemo, useState, useEffect } from "react";
import { scaleOrdinal } from "@visx/scale";
import { LinearGradient } from "@visx/gradient";
import { Drag, raise } from "@visx/drag";

const colors = [
  "#025aac",
  "#02cff9",
  "#02efff",
  "#03aeed",
  "#0384d7",
  "#edfdff",
  "#ab31ff",
  "#5924d7"
];

const generateCircles = ({ width, height }) =>
  new Array(width < 360 ? 40 : 185).fill(1).map((d, i) => {
    const radius = 25 - Math.random() * 20;
    return {
      id: `${i}`,
      radius,
      x: Math.round(Math.random() * (width - radius * 2) + radius),
      y: Math.round(Math.random() * (height - radius * 2) + radius)
    };
  });

function Example({ width, height }) {
  const [draggingItems, setDraggingItems] = useState([]);

  useEffect(() => {
    if (width > 10 && height > 10)
      setDraggingItems(generateCircles({ width, height }));
  }, [width, height]);

  const colorScale = useMemo(
    () =>
      scaleOrdinal({
        range: colors,
        domain: draggingItems.map((d) => d.id)
      }),
    [width, height]
  );

  if (draggingItems.length === 0 || width < 10) return null;

  return (
    <div className="Drag" style={{ touchAction: "none" }}>
      <svg width={width} height={height}>
        <LinearGradient id="stroke" from="#ff00a5" to="#ffc500" />
        <rect fill="#c4c3cb" width={width} height={height} rx={14} />

{draggingItems.map((d, i) => (
          <Drag
            key={`drag-${d.id}`}
            width={width}
            height={height}
            x={d.x}
            y={d.y}
            onDragStart={() => {
              setDraggingItems(raise(draggingItems, i));
            }}
          >
            {({ dragStart, dragEnd, dragMove, isDragging, x, y, dx, dy }) => (
              <circle
                key={`dot-${d.id}`}
                cx={x}
                cy={y}
                r={isDragging ? d.radius + 4 : d.radius}
                fill={isDragging ? "url(#stroke)" : colorScale(d.id)}
                transform={`translate(${dx}, ${dy})`}
                fillOpacity={0.9}
                stroke={isDragging ? "white" : "transparent"}
                strokeWidth={2}
                onMouseMove={dragMove}
                onMouseUp={dragEnd}
                onMouseDown={dragStart}
                onTouchStart={dragStart}
                onTouchMove={dragMove}
                onTouchEnd={dragEnd}
              />
            )}
          </Drag>
        ))}
      </svg>
      <style jsx>{`
        .Drag {
          display: flex;
          flex-direction: column;
          user-select: none;
        }

        svg {
          margin: 1rem 0;
        }
        .deets {
          display: flex;
          flex-direction: row;
          font-size: 12px;
        }
        .deets > div {
          margin: 0.25rem;
        }
      `}</style>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width={500} height={300} />
    </div>
  );
}

We have the generateCircles function to return an array of objects with id , radius , and x and y properties.

id is the unique ID for each circle.

And the other properties are used to render circles on the page.

In the Example component, we keep track of the items being tracked with the draggingItems state.

We watch the width and height and add the circles in the useEffect hook callback.

The colorScale state lets us compute the scale for the colors according to the colors array we created earlier.

In the return statement, we have the Drag component, which is the container for holding draggable items.

Then we render the draggable items with the render prop in the Drag component.

It renders the circles in the draggingItems array.

We pass in the drag event handlers from the object parameter as props of the circle to make them draggable.

Their positions will be updated with those event handler functions.

Finally, we have the styles in the style tag to change the styles for the container.

Now we should have circles on the screen that can be dragged around the box.

Conclusion

We can add drag and drop features into our React app with the Visx library.