How we implemented SVG Arrows in React: The Basics (1/3)

At Productboard we developed a tool that helps product managers prioritize functionality and work planning. One of the important parts of our app is the roadmap. A roadmap helps you visualize your future work plan, and you can share it with others. We can visualize this plan in many different modes — for example with dependency on time, product releases, or as a simple Kanban board.

A roadmap with dependency on time can look like this:

Roadmap example

We can see four cards in this roadmap. They represent product features. Some of them may be related to each other, but they may not be obvious without additional context.

During the Product Discovery phase, we regularly interview our customers to understand their needs better and help them prioritize important features and issues. Seeing how features are dependent on each other directly from the roadmap is one of our customers’ top requirements.

To streamline this process, we decided to visualize roadmap dependencies between individual features using arrows:

Roadmap with dependencies marked by arrows

How we implemented these arrows is what we are going to describe in this three-part series.

If you want to skip the nuts and bolts, you can go directly to our code on CodeSandbox or Github.

Interested in joining our growing Engineering team? Check out our careers page for the latest vacancies.

Use case

Before we jump into the implementation process, we first have to specify several characteristics of our arrow:

  • It’s a Cubic Bezier curve with four points
  • It’s rendered as an SVG element in React
  • We have the start and end coordinates of the arrow
  • We want to be able to detect mouse events like onClick , onMouseEnter , onMouseLeave

Direction matters

The orientation of the arrow is always one direction. The arrow will start with the circle on the right side of the card and end with the arrowhead on the left side of the other card.

We distinguish two states:

Feature A blocks Feature B. Feature A needs to be done first because Feature B depends on it.
Feature A is blocked by Feature B. In this view, time matters. The X-axis can be thought of as a timeline. Feature A depends on the completion of Feature B, which is scheduled for the future. This is a faulty condition that should not occur. Therefore, we display it in red in the system. At the same time, this direction of the arrow can occur without red highlighting, because we do not have to have the X-axis given by time.

Inspiration

Not wanting to reinvent the wheel, we looked at Github first to see if there was a solution we could use or be inspired by.

We went through the repositories, and we were interested in react-archer and react-xarrows. However, none of the solutions completely met our needs. Both were unnecessarily complicated and dealt with use cases that we do not have. For this reason, we decided to write our own component for drawing arrows, giving us full control over what is going on inside and allowing us to learn something new.

Naive implementation

Let’s go step by step through the implementation process in real-time. First, we’ll start off with a completely simple component that will show only a straight line between two different points (or “features” as we say at Productboard).

For this, we only need the X and Y coordinates of the two points and to determine how big the whole SVG element should be. We can also easily find out from these coordinates what width and height the SVG element should have.

import React from "react";

type Point = {
  x: number;
  y: number;
};

type ArrowProps = {
  startPoint: Point;
  endPoint: Point;
};

const Arrow = ({ startPoint, endPoint }: ArrowProps) => {
  
  // Getting info about SVG canvas
  const canvasStartPoint = {
    x: Math.min(startPoint.x, endPoint.x),
    y: Math.min(startPoint.y, endPoint.y),
  };
  const canvasWidth = Math.abs(endPoint.x - startPoint.x);
  const canvasHeight = Math.abs(endPoint.y - startPoint.y);

  return (
    <svg
      width={canvasWidth}
      height={canvasHeight}
      style={{
        backgroundColor: "#eee",
        transform: `translate(${canvasStartPoint.x}px, ${canvasStartPoint.y}px)`,
      }}
    >
      <line
        stroke="#aaa"
        strokeWidth={1}
        x1={startPoint.x - canvasStartPoint.x}
        y1={startPoint.y - canvasStartPoint.y}
        x2={endPoint.x - canvasStartPoint.x}
        y2={endPoint.y - canvasStartPoint.y}
      />
    </svg>
  );
};

function App() {
  const featureAPosition = {
    x: 300,
    y: 0,
  };

  const featureBPosition = {
    x: 400,
    y: 200,
  };

  return <Arrow startPoint={featureAPosition} endPoint={featureBPosition} />;
}

export default App;

This is the first step in the implementation of the arrow. The result in the roadmap might look like this:

The basic version of the arrow represents dependency between features

Follow-up

In this first article, we described the basic specifics of the component we want to create, and we wrote a very naive implementation of an SVG line connecting two points.

It doesn’t look like an arrow yet. There are two more parts we have to finish:

  • curving
  • arrowhead

We will take a look at curving in the SECOND PART of this series.

The whole implementation of SVG arrows in React is available on CodeSandbox or Github.

Interested in joining our growing team? Well, we’re hiring across the board! Check out our careers page for the latest vacancies.

You might also like

Productboard expanding San Francisco engineering presence
Life at Productboard

Productboard expanding San Francisco engineering presence

Jiri Necas
Jiri Necas
Refactoring Productboard’s design system to the next level
Life at Productboard

Refactoring Productboard’s design system to the next level

Jonathan Atkins
Jonathan Atkins
The Role of Growth Engineering at Productboard: Significance, key skills, and responsibilities
Life at Productboard

The Role of Growth Engineering at Productboard: Significance, key skills, and responsibilities

Stuart Cavill
Stuart Cavill