D3 Path
About
The d3-path
module is used to define how shapes are drawn inside of a <path>
element. It has one main method, path
, which returns an object that has methods for defining how to render the path.
npm install d3-path
Path Holders
Before we look at what the d3-path
module does, it is important to understand the HTML elements that can have paths rendered in them. Both the <canvas>
and the <svg>
contain similar ideas about how to define shapes using lines and curves.
Canvas
In order to render a path to a <canvas>
element, you need a context. The context contains a number of methods for drawing a path. When you begin to draw a path, you must start with moving to a certain pixel, which is done by calling the context's moveTo
method. Then, you call a method to define how to move to another point and if a line or curve should be rendered between the two. You continue to call context methods until the path has been completed. Because we can programmatically define a path in JavaScript, we don't need to create a context when drawing a path inside of a canvas.
SVG
Rendering a path inside of a <svg>
element is done using its d
attribute. This attribute is a string of commands concatenated together to define how the shape should be drawn. Each command starts with a letter and then a series of numbers. For example, M 75,25
specifies that the path should move to the mixel with coordinates <75,25>
. The SVG's rendering engine will parse this string to determine how to render the path. Manually creating the d
attribute string would involve lots of string formatting and concatenation. d3-path
exists to do this work for us, while using the same API as the canvas context.
Path
The path
method creates a new path object. This object acts as a context and has the same methods for drawing a path as the canvas's context. Because this object replicates the canvas context's method, we can write code that will render to either and svg or a canvas depending on the context.
import { path } from 'd3-path';const context = path();context.moveTo(20,20);context.lineTo(30,20);// ...
The context object returned by the path
method contains an array. Every time one of the object's rendering methods is called, the values representing how to render that step are added to the context object's array. Once every step needed to describe the path has been called, we can call the context object's toString
method. This will concatenate the results together into a string that we then can use as the d
attribute to a <path>
element.
// ...const d = context.toString();// d === 'M20,20L30,20'
Context Agnostic Code
As mentioned above, the context object created by d3-path
replicates the path rendering methods of a canvas context. That means that we can write code that takes a generic context object and it will render our path correctly regardless of whether it is to a canvas or an svg.
When using a canvas, we get a context object using the canvas's getContext
method. When using an svg, we use d3-path
's path
method.
// given a canvas and svg element:const canvas = document.querySelector('canvas');const svg = document.querySelector('svg');const path = svg.querySelector('path');const canvasContext = canvas.getContext('2d');const svgContext = path();// we can then write element agnostic code// to define how to draw the pathfunction drawSquare(context) { context.moveTo(10,10); context.lineTo(40,10); context.lineTo(40,40); context.lineTo(10,40); context.closePath();}
This doesn't contain any magic that gets rid of other rendering related code that differs between the two types. For canvas elements, we will still need to call the context's beginPath
method to begin the drawing and either its stroke
or fill
method to finish it. For svg elements, the context will need to be output as a string and set as the d
attribute. Still, all of the steps in between can be shared.
// render to the canvascanvasContext.beginPath();drawSquare(canvasContext);canvasContext.stroke();// render to the svgdrawSquare(svgContext);path.setAttribute('d', svgContext.toString());