Categories
Visx

Add Lines with Multiple Styles into a 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/curve @visx/gradient @visx/responsive @visx/scale @visx/shape

to install the packages.

Create the Multi-Styled Line

We can create the chart by adding the items provided by the modules.

To add the line, we write:

import React, { useMemo } from "react";
import { scaleLinear } from "@visx/scale";
import { curveCardinal } from "@visx/curve";
import { LinePath, SplitLinePath } from "@visx/shape";
import { LinearGradient } from "@visx/gradient";

const getX = (d) => d.x;
const getY = (d) => d.y;
const background = "#045275";
const backgroundLight = "#089099";
const foreground = "#b7e6a5";

function generateSinPoints({
  width,
  height,
  numberOfWaves = 10,
  pointsPerWave = 10
}) {
  const waveLength = width / numberOfWaves;
  const distanceBetweenPoints = waveLength / pointsPerWave;
  const sinPoints = [];

  for (let waveIndex = 0; waveIndex <= numberOfWaves; waveIndex += 1) {
    const waveDistFromStart = waveIndex * waveLength;

    for (let pointIndex = 0; pointIndex <= pointsPerWave; pointIndex += 1) {
      const waveXFraction = pointIndex / pointsPerWave;
      const waveX = pointIndex * distanceBetweenPoints;
      const globalX = waveDistFromStart + waveX;
      const globalXFraction = (width - globalX) / width;
      const waveHeight =
        Math.min(globalXFraction, 1 - globalXFraction) * height;
      sinPoints.push({
        x: globalX,
        y: waveHeight * Math.sin(waveXFraction * (2 * Math.PI))
      });
    }
  }

  return sinPoints;
}

function Example({
  width,
  height,
  numberOfWaves = 10,
  pointsPerWave = 100,
  numberOfSegments = 8
}) {
  const data = useMemo(
    () => generateSinPoints({ width, height, numberOfWaves, pointsPerWave }),
    [width, height, numberOfWaves, pointsPerWave]
  );

  const dividedData = useMemo(() => {
    const segmentLength = Math.floor(data.length / numberOfSegments);
    return new Array(numberOfSegments)
      .fill(null)
      .map((_, i) => data.slice(i * segmentLength, (i + 1) * segmentLength));
  }, [numberOfSegments, data]);

  const getScaledX = useMemo(() => {
    const xScale = scaleLinear({ range: [0, width], domain: [0, width] });
    return (d) => xScale(getX(d)) ?? 0;
  }, [width]);

  const getScaledY = useMemo(() => {
    const yScale = scaleLinear({ range: [0, height], domain: [height, 0] });
    return (d) => yScale(getY(d)) ?? 0;
  }, [height]);

  return width < 10 ? null : (
    <div>
      <svg width={width} height={height}>
        <LinearGradient
          id="visx-shape-splitlinepath-gradient"
          from={background}
          to={backgroundLight}
          fromOpacity={0.8}
          toOpacity={0.8}
        />
        <rect
          x={0}
          y={0}
          width={width}
          height={height}
          fill="url(#visx-shape-splitlinepath-gradient)"
          rx={14}
        />

        <g transform={`rotate(${0})translate(${-0}, ${-height * 0.5})`}>
          <LinePath
            data={data}
            x={getScaledX}
            y={getScaledY}
            strokeWidth={8}
            stroke="#fff"
            strokeOpacity={0.15}
            curve={curveCardinal}
          />

          <SplitLinePath
            segments={dividedData}
            x={getScaledX}
            y={getScaledY}
            curve={curveCardinal}
            styles={[
              { stroke: foreground, strokeWidth: 3 },
              { stroke: "#fff", strokeWidth: 2, strokeDasharray: "9,5" },
              { stroke: background, strokeWidth: 2 }
            ]}
          >
            {({ segment, styles, index }) =>
              index === numberOfSegments - 1 || index === 2 ? (
                segment.map(({ x, y }, i) =>
                  i % 8 === 0 ? (
                    <circle
                      key={i}
                      cx={x}
                      cy={y}
                      r={10 * (i / segment.length)}
                      stroke={styles?.stroke}
                      fill="transparent"
                      strokeWidth={1}
                    />
                  ) : null
                )
              ) : (
                <LinePath
                  data={segment}
                  x={(d) => d.x || 0}
                  y={(d) => d.y || 0}
                  {...styles}
                />
              )
            }
          </SplitLinePath>
        </g>
      </svg>
    </div>
  );
}

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

We add the getX and getY functions to get the data for the x and y axes.

background is one of the colors of the background gradient.

backgroundLight has the lighter color of the background gradient.

foreground has one of the line colors.

We generate the points for the line with the generateSinPoints function.

globalX has the x coordinates for the sine curve.

And we use the Math.sin method to create the y axis value.

In the Example component, we render the line with various styles.

We divide the line into segments with the dividedData variable.

This is done by creating an array of values and slicing the values created from generateSinPoints with slice .

Then we create the scales for the graph with the getScaledX and getScaledY variables.

We create them with the scaleLinear function since everything is in linear scale.

Then we render the left line segment with the LinePath component.

And we use the SplitLinePath component to render the remaining segments.

We get the styles from the styles prop and render them styles with the LineSegment component we return in the render prop of SplitLinePath .

Conclusion

We can create a line with multiple segments with their own styles in our React app with the Visx library.

Categories
Visx

Add Marker Icons onto Lines 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 line with markers into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

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

to install the packages.

Create Lines with Markers

We can create lines with markers with the @visx/glyph module to add the markers.

To do this, we write:

import React from "react";
import { Group } from "@visx/group";
import {
  Glyph as CustomGlyph,
  GlyphCircle,
  GlyphCross,
  GlyphDiamond,
  GlyphSquare,
  GlyphStar,
  GlyphTriangle,
  GlyphWye
} from "@visx/glyph";
import { LinePath } from "@visx/shape";
import genDateValue from "@visx/mock-data/lib/generators/genDateValue";
import { scaleTime, scaleLinear } from "@visx/scale";
import { curveMonotoneX, curveBasis } from "@visx/curve";

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

export const primaryColor = "#8921e0";
export const secondaryColor = "#00f2ff";
const contrastColor = "#ffffff";

const Glyphs = [
  GlyphCircle,
  GlyphCross,
  GlyphDiamond,
  GlyphStar,
  GlyphTriangle,
  GlyphSquare,
  GlyphWye,
  ({ left, top }) => (
    <CustomGlyph left={left} top={top}>
      <circle r={12} fill={secondaryColor} />
      <text fontSize={16} textAnchor="middle" dy="0.5em">
        {"?"}
      </text>
    </CustomGlyph>
  )
];

const data = genDateValue(Glyphs.length * 2);

const date = (d) => d.date.valueOf();
const value = (d) => d.value;

const xScale = scaleTime({
  domain: [Math.min(...data.map(date)), Math.max(...data.map(date))]
});
const yScale = scaleLinear({
  domain: [0, Math.max(...data.map(value))]
});

const getX = (d) => xScale(date(d)) ?? 0;
const getY = (d) => yScale(value(d)) ?? 0;

function Example({ width, height, margin = defaultMargin }) {
  if (width < 10) return null;
  const innerWidth = width - margin.left - margin.right;
  const innerHeight = height - margin.top - margin.bottom;
  xScale.range([0, innerWidth]);
  yScale.range([innerHeight, 0]);

return (
    <svg width={width} height={height}>
      <rect
        x={0}
        y={0}
        width={width}
        height={height}
        fill={secondaryColor}
        rx={14}
      />
      <Group left={margin.left} top={margin.top}>
        <LinePath
          data={data}
          x={getX}
          y={getY}
          stroke={primaryColor}
          strokeWidth={2}
          strokeDasharray="2,2"
          curve={curveBasis}
        />
        <LinePath
          data={data}
          x={getX}
          y={getY}
          stroke={primaryColor}
          strokeWidth={2}
          curve={curveMonotoneX}
        />
        {data.map((d, i) => {
          const CurrGlyph = Glyphs[i % Glyphs.length];
          const left = getX(d);
          const top = getY(d);
          return (
            <g key={`line-glyph-${i}`}>
              <CurrGlyph
                left={left}
                top={top}
                size={110}
                stroke={secondaryColor}
                strokeWidth={10}
              />
              <CurrGlyph
                left={left}
                top={top}
                size={110}
                fill={i % 2 === 0 ? primaryColor : contrastColor}
                stroke={i % 2 === 0 ? contrastColor : primaryColor}
                strokeWidth={2}
              />
            </g>
          );
        })}
      </Group>
    </svg>
  );
}

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

We add the margins for the graph with the defaultMargin variable.

primaryColor , secondaryColor have the colors for the lines.

contrastColor have the background color for the markers.

The Glyphs array have the icons and a function that returns a custom component with the icon.

CustomGlyph takes the left and top properties to set its position.

data has the data for the line.

date and value are functions that return the value given the entry.

xScale has the scale for the x-axis.

And yScale has the scale for the y-axis.

The Example component is where we put the chart together.

We set the width and height of the chart with the innerWidth and innerHeight variables.

The Group component wraps around the parts of the chart.

LinePath has the lines for the graph.

We pass in the getX and getY functions to render the data as lines.

The first LinePath renders a dotted line.

And the 2nd one renders a solid line.

The markers are rendered with the data.map callback.

It returns the CurrGlyph components to render our marker.

We set the left and top props to set the position.

CurrGlyph is created from the Glyphs array by getting the icon to render by its index.

Now we should see 2 lines with markers for each.

Conclusion

We can create lines with markers easily in our React app with the Visx library.

Categories
Visx

Create a Simple React Whiteboard 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 simple whiteboard into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/curve @visx/drag @visx/gradient @visx/responsive @visx/shape

to install the packages.

Create the Whiteboard

We can create the whiteboard by adding the items provided by the modules.

To do this, we write:

import React, { useCallback, useState } from "react";
import { LinePath } from "@visx/shape";
import { useDrag } from "@visx/drag";
import { curveBasis } from "@visx/curve";
import { LinearGradient } from "@visx/gradient";

function Example({ data = [], width, height }) {
  const [lines, setLines] = useState(data);
  const onDragStart = useCallback(
    (currDrag) => {
      setLines((currLines) => [
        ...currLines,
        [{ x: currDrag.x, y: currDrag.y }]
      ]);
    },
    [setLines]
  );
  const onDragMove = useCallback(
    (currDrag) => {
      setLines((currLines) => {
        const nextLines = [...currLines];
        const newPoint = {
          x: currDrag.x + currDrag.dx,
          y: currDrag.y + currDrag.dy
        };
        const lastIndex = nextLines.length - 1;
        nextLines[lastIndex] = [...(nextLines[lastIndex] || []), newPoint];
        return nextLines;
      });
    },
    [setLines]
  );
  const {
    x = 0,
    y = 0,
    dx,
    dy,
    isDragging,
    dragStart,
    dragEnd,
    dragMove
  } = useDrag({
    onDragStart,
    onDragMove,
    resetOnStart: true
  });

  return width < 10 ? null : (
    <div className="DragII" style={{ touchAction: "none" }}>
      <svg width={width} height={height}>
        <LinearGradient id="stroke" from="#ff614e" to="#ffdc64" />
        <rect fill="#04002b" width={width} height={height} rx={14} />
        {lines.map((line, i) => (
          <LinePath
            key={`line-${i}`}
            fill="transparent"
            stroke="url(#stroke)"
            strokeWidth={3}
            data={line}
            curve={curveBasis}
            x={(d) => d.x}
            y={(d) => d.y}
          />
        ))}

        <g>
          {isDragging && (
            <rect
              width={width}
              height={height}
              onMouseMove={dragMove}
              onMouseUp={dragEnd}
              fill="transparent"
            />
          )}
          {isDragging && (
            <g>
              <rect
                fill="white"
                width={8}
                height={8}
                x={x + dx - 4}
                y={y + dy - 4}
                pointerEvents="none"
              />
              <circle
                cx={x}
                cy={y}
                r={4}
                fill="transparent"
                stroke="white"
                pointerEvents="none"
              />
            </g>
          )}
          <rect
            fill="transparent"
            width={width}
            height={height}
            onMouseDown={dragStart}
            onMouseUp={isDragging ? dragEnd : undefined}
            onMouseMove={isDragging ? dragMove : undefined}
            onTouchStart={dragStart}
            onTouchEnd={isDragging ? dragEnd : undefined}
            onTouchMove={isDragging ? dragMove : undefined}
          />
        </g>
      </svg>
      <style jsx>{`
        .DragII {
          display: flex;
          flex-direction: column;
          user-select: none;
        }

        svg {
          margin: 1rem 0;
          cursor: crosshair;
        }

        .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>
  );
}

In the Example component, we have the whiteboard.

We have the lines state to keep track of the lines.

The lines array is set by the onDragStart function.

Whenever we start dragging the mouse, we add to the lines array.

The line starts at the x and y coordinates of the mouse.

The onDragMove event handler function lets us add the line according to the coordinates of the mouse.

We pass those 2 functions into the useDrag hook to let us draw lines when dragging.

In the return statement, we render the LinePath by calling map on the lines array.

data is passed into the line function to render the line.

When isDragging is true , which is when we’re dragging our mouse, then the circle is shown to render the marker.

rect draws a transparent rectangle as we’re dragging.

The style tag has the styles we want to set for the whiteboard.

Conclusion

We can create a simple whiteboard in our React app easily with the Visx library.

Categories
Visx

Add Radial Line 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 radial lines into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

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

to install the packages.

Create the Radial Line

We can create the chart by adding the items provided by the modules.

We use the data from the @visx/mock-data module.

Then we can create the radial line chart by writing:

import React, { useRef, useState, useEffect } from "react";
import { Group } from "@visx/group";
import { LineRadial } from "@visx/shape";
import { scaleTime, scaleLog } from "@visx/scale";
import { curveBasisOpen } from "@visx/curve";
import appleStock from "@visx/mock-data/lib/mocks/appleStock";
import { LinearGradient } from "@visx/gradient";
import { animated, useSpring } from "react-spring";

const green = "#e5fd3d";
export const blue = "#aeeef8";
const darkgreen = "#dff84d";
export const background = "#744cca";
const darkbackground = "#603FA8";
const springConfig = {
  tension: 20
};

// utils
function extent(data, value) {
  const values = data.map(value);
  return [Math.min(...values), Math.max(...values)];
}
// accessors
const date = (d) => new Date(d.date).valueOf();
const close = (d) => d.close;

// scales
const xScale = scaleTime({
  range: [0, Math.PI * 2],
  domain: extent(appleStock, date)
});
const yScale = scaleLog({
  domain: extent(appleStock, close)
});

const angle = (d) => xScale(date(d)) ?? 0;
const radius = (d) => yScale(close(d)) ?? 0;

const firstPoint = appleStock[0];
const lastPoint = appleStock[appleStock.length - 1];

const Example = ({ width, height, animate = true }) => {
  const lineRef = useRef(null);
  const [lineLength, setLineLength] = useState(0);
  const [shouldAnimate, setShouldAnimate] = useState(false);

  const spring = useSpring({
    frame: shouldAnimate ? 0 : 1,
    config: springConfig,
    onRest: () => setShouldAnimate(false)
  });

  const effectDependency = lineRef.current;
  useEffect(() => {
    if (lineRef.current) {
      setLineLength(lineRef.current.getTotalLength());
    }
  }, [effectDependency]);

if (width < 10) return null;
  yScale.range([0, height / 2 - 20]);

const yScaleTicks = yScale.ticks();
  const handlePress = () => setShouldAnimate(true);

  return (
    <>
      {animate && (
        <>
          <button
            type="button"
            onClick={handlePress}
            onTouchStart={handlePress}
          >
            Animate
          </button>
          <br />
        </>
      )}
      <svg
        width={width}
        height={height}
        onClick={() => setShouldAnimate(!shouldAnimate)}
      >
        <LinearGradient from={green} to={blue} id="line-gradient" />
        <rect width={width} height={height} fill={background} rx={14} />
        <Group top={height / 2} left={width / 2}>
          {yScaleTicks.map((tick, i) => (
            <circle
              key={`radial-grid-${i}`}
              r={yScale(tick)}
              stroke={blue}
              strokeWidth={1}
              fill={blue}
              fillOpacity={1 / (i + 1) - (1 / i) * 0.2}
              strokeOpacity={0.2}
            />
          ))}
          {yScaleTicks.map((tick, i) => (
            <text
              key={`radial-grid-${i}`}
              y={-(yScale(tick) ?? 0)}
              dy="-.33em"
              fontSize={8}
              fill={blue}
              textAnchor="middle"
            >
              {tick}
            </text>
          ))}

          <LineRadial angle={angle} radius={radius} curve={curveBasisOpen}>
            {({ path }) => {
              const d = path(appleStock) || "";
              return (
                <>
                  <animated.path
                    d={d}
                    ref={lineRef}
                    strokeWidth={2}
                    strokeOpacity={0.8}
                    strokeLinecap="round"
                    fill="none"
                    stroke={animate ? darkbackground : "url(#line-gradient)"}
                  />
                  {shouldAnimate && (
                    <animated.path
                      d={d}
                      strokeWidth={2}
                      strokeOpacity={0.8}
                      strokeLinecap="round"
                      fill="none"
                      stroke="url(#line-gradient)"
                      strokeDashoffset={spring.frame.interpolate(
                        (v) => v * lineLength
                      )}
                      strokeDasharray={lineLength}
                    />
                  )}
                </>
              );
            }}
          </LineRadial>
          {[firstPoint, lastPoint].map((d, i) => {
            const cx = ((xScale(date(d)) ?? 0) * Math.PI) / 180;
            const cy = -(yScale(close(d)) ?? 0);
            return (
              <circle
                key={`line-cap-${i}`}
                cx={cx}
                cy={cy}
                fill={darkgreen}
                r={3}
              />
            );
          })}
        </Group>
      </svg>
    </>
  );
};

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

We set the color for the lines and background color with the green , blue and background variables.

darkBackground is used for the dark background.

The extent function returns an array of number with the min and max values of the data.

date and close are data accessors that return the data for the time and value respectively.

The xScale and yScale values are created from the domain and range .

xScale has both the domain and range so that the radial line can be created.

We also need the angle and radius functions to compute the points for the radial line.

We have the shouldAnimate state to set when to animate the filling of the line.

In the Group component, we have the circle element to create the concentric circles.

The text elements have the text with the values for each ring.

The LineRadial component renders the line from the values returned from the angle and radius function which we pass in as props.

The render prop returns the animated.path component, which is created from the d path string.

The 2nd animated.path component has the filled line which is generated by animating the stroke and strokeDashOffset .

The circle below that is added to the start and end of the line respectively as markers for the ends of the line.

Conclusion

We can add a radial line easily into our React app with the Visx library.

Categories
Visx

Add Annotations to Lines and Curves 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 into our React app.

Install Required Packages

We have to install a few modules.

To get started, we run:

npm i @visx/annotation @visx/mock-data @visx/responsive @visx/scale @visx/shape

to install the packages.

Create the Lines/Curves with Annotations

We can create the chart by adding the items provided by the modules.

We use the data from the @visx/mock-data module.

To create it, we write:

import React, { useMemo, useState } from "react";
import { Label, Connector, CircleSubject, LineSubject } from "@visx/annotation";
import { LinePath } from "@visx/shape";
import { bisector, extent } from "d3-array";
import { scaleLinear, scaleTime } from "@visx/scale";
import appleStock from "@visx/mock-data/lib/mocks/appleStock";
import { Annotation } from "@visx/annotation";

export const orange = "#ff7e67";
export const greens = ["#ecf4f3", "#68b0ab", "#006a71"];
const data = appleStock.slice(-100);
const getDate = (d) => new Date(d.date).valueOf();
const getStockValue = (d) => d.close;
const annotateDatum = data[Math.floor(data.length / 2) + 4];
const approxTooltipHeight = 70;

function findNearestDatum({ value, scale, accessor, data }) {
  const bisect = bisector(accessor).left;
  const nearestValue = scale.invert(value);
  const nearestValueIndex = bisect(data, nearestValue, 1);
  const d0 = data[nearestValueIndex - 1];
  const d1 = data[nearestValueIndex];
  let nearestDatum = d0;
  if (d1 && accessor(d1)) {
    nearestDatum =
      nearestValue - accessor(d0) > accessor(d1) - nearestValue ? d1 : d0;
  }
  return nearestDatum;
}

function Example({ width, height, compact = false }) {
  const xScale = useMemo(
    () =>
      scaleTime({
        domain: extent(data, (d) => getDate(d)),
        range: [0, width]
      }),
    [width]
  );
  const yScale = useMemo(
    () =>
      scaleLinear({
        domain: extent(data, (d) => getStockValue(d)),
        range: [height - 100, 100]
      }),
    [height]
  );

  const [editLabelPosition] = useState(false);
  const [editSubjectPosition] = useState(false);
  const [title] = useState("Title");
  const [subtitle] = useState(
    compact ? "Subtitle" : "Long Subtitle"
  );
  const [connectorType] = useState("elbow");
  const [subjectType] = useState("circle");
  const [showAnchorLine] = useState(true);
  const [verticalAnchor] = useState("auto");
  const [horizontalAnchor] = useState("auto");
  const [labelWidth] = useState(compact ? 100 : 175);
  const [annotationPosition, setAnnotationPosition] = useState({
    x: xScale(getDate(annotateDatum)) ?? 0,
    y: yScale(getStockValue(annotateDatum)) ?? 0,
    dx: compact ? -50 : -100,
    dy: compact ? -30 : -50
  });

  return (
    <svg width={width} height={height}>
      <rect width={width} height={height} fill={greens[0]} />
      <LinePath
        stroke={greens[2]}
        strokeWidth={2}
        data={data}
        x={(d) => xScale(getDate(d)) ?? 0}
        y={(d) => yScale(getStockValue(d)) ?? 0}
      />
      <Annotation
        width={width}
        height={height}
        x={annotationPosition.x}
        y={annotationPosition.y}
        dx={annotationPosition.dx}
        dy={annotationPosition.dy}
        canEditLabel={editLabelPosition}
        canEditSubject={editSubjectPosition}
        onDragEnd={({ event, ...nextPosition }) => {
          const nearestDatum = findNearestDatum({
            accessor:
              subjectType === "horizontal-line" ? getStockValue : getDate,
            data,
            scale: subjectType === "horizontal-line" ? yScale : xScale,
            value:
              subjectType === "horizontal-line"
                ? nextPosition.y
                : nextPosition.x
          });
          const x = xScale(getDate(nearestDatum)) ?? 0;
          const y = yScale(getStockValue(nearestDatum)) ?? 0;

          const shouldFlipDx =
            (nextPosition.dx > 0 && x + nextPosition.dx + labelWidth > width) ||
            (nextPosition.dx < 0 && x + nextPosition.dx - labelWidth <= 0);
          const shouldFlipDy =
            (nextPosition.dy > 0 &&
              height - (y + nextPosition.dy) < approxTooltipHeight) ||
            (nextPosition.dy < 0 &&
              y + nextPosition.dy - approxTooltipHeight <= 0);
          setAnnotationPosition({
            x,
            y,
            dx: (shouldFlipDx ? -1 : 1) * nextPosition.dx,
            dy: (shouldFlipDy ? -1 : 1) * nextPosition.dy
          });
        }}
      >
        <Connector stroke={orange} type={connectorType} />
        <Label
          backgroundFill="white"
          showAnchorLine={showAnchorLine}
          anchorLineStroke={greens[2]}
          backgroundProps={{ stroke: greens[1] }}
          fontColor={greens[2]}
          horizontalAnchor={horizontalAnchor}
          subtitle={subtitle}
          title={title}
          verticalAnchor={verticalAnchor}
          width={labelWidth}
        />
        {subjectType === "circle" && <CircleSubject stroke={orange} />}
        {subjectType !== "circle" && (
          <LineSubject
            orientation={
              subjectType === "vertical-line" ? "vertical" : "horizontal"
            }
            stroke={orange}
            min={0}
            max={subjectType === "vertical-line" ? height : width}
          />
        )}
      </Annotation>
    </svg>
  );
}

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

We create the orange and green variable to set the color for the marker and the line respectively.

data has the data for the line.

getDate and getStockValue are getter functions for the x and y-axis data respectively.

The findNearestDatum function lets us find the point to let us drag the annotation box to.

In the Example component, we create the xScale and yScale objects to let us create the scales for the line chart.

The Annotation component has the annotation for the line.

We set the x and y position to place the annotation for the line.

width and height sets the width and height.

dx and dy sets the position offset.

canEditLabel lets us set whether we can edit the label with a boolean.

canEditSubject lets us set whether we can edit the subject with a boolean.

The onDragEnd handler changes the annotation position when we drag the annotation box.

We add the Connection and the Label to add the marker and the label for the annotation box.

Now we should see a box displayed on the line with the text as set by the subtitle prop.

Conclusion

We can add an annotation box into our line with the Visx library.