Chapter 05Paths
In this section we discuss d3.path()
, a utility for creating strings that can be used to define paths
in svgs
.
D3.path
can be used by itself to define the value of the d
(path definition) attribute for a path
element in an svg
, as we show in this section, but is also used by all of the other shapes in this chapter to transform various input into path defininitions.
SVG Paths
SVG’s can contain various elements to help create a visualization. Of these elements, lines
and rects
are useful in creating some common data visualization patterns such as line graphs and bar graphs. When requiring complex shapes we rely on the path
element.
Paths
can be appended to svgs
like lines
and rects
and have similar attributes, such as stroke
and fill
. Paths
, however, have a d
attribute that defines its shape and position.
The d
attribute contains a string that specifies commands and numbers which define command parameters. Multiple commands can be chained together in a single string and are read from left to right.
For example, to create a path that starts at the point (25,25), draws a line to (75,25), then draws a line to (75,75) and finally closes the path we can use the following path definition.
"M 25 25 L 75 25 L 75 75 Z"
<script> d3.select("#demoSvgPath") .append("path") .attr("d", "M 25 25 L 75 25 L 75 75 Z") .attr("stroke", "black") .attr("fill", "red"); </script> <svg id="demoSvgPath" width="100" height="100"></svg>
The Mozilla documentation for all of the available SVG path commands can be found here.
D3.path
The d3.path()
method is convenient for defining paths, allowing us to avoid manually writing strings for the d
attribute of a path
. The d3.path
method returns a path serializer that implements the methods in the CanvasPathMethods interface. Below we provide links to the D3.js documentation for each method along with links to their equivalent context method and their equivalent SVG command. In the sections below we describe each method and provide examples of their usage.
Setting an SVG’s Path
In order to create a triangle, similar to the one above, using the methods describe above, we first have to create a path serializer by calling d3.path
:
var path = d3.path();
We can then call the path methods on the seriarlizer object.
path.moveTo(25,25); path.lineTo(75,25); path.lineTo(75,75); path.closePath();
We then retrive the SVG path definition string using path.toString
. This string is used as the value for an SVG path's
d
attribute. As you can see from the console output in the example, the path is similar to the path used in the example above. The only difference is the delimeter used between the command parameters. When defining the path manually we used spaces, whereas path.toString
uses commas.
<script> var path = d3.path() path.moveTo(25,25) path.lineTo(75,25) path.lineTo(75,75) path.closePath(); console.log(path.toString()); d3.select("#demoPath0") .append("path") .attr("d", path.toString()) .attr("stroke", "black") .attr("fill", "red"); </script> <svg id="demoPath0" width="100" height="100"></svg>
When setting the d
attribute for a path
element, we can also simply pass the path serializer as the second argument of selection.attr
as shown in the example below.
d3.select("#demoPath1") .append("path") .attr("d", path) .attr("stroke", "black") .attr("fill", "red");
Path.moveTo, Path.LineTo, and Path.close
In the example below, we use an array to define a set of six points and use a single d3.path
serializer along with path.moveTo
, path.lineTo
, and path.closePath
to create two separate triangles (left image).
-
path.moveTo(x, y) - Ends the current subpath and moves to the given point (x,y) without drawing a line.
-
path.lineTo(x, y) - Draws a straight line from the current point to the given point (x,y).
-
path.closePath() - Draws a line to the starting point of the current subpath, then ends the subpath.
We are able to create two separately filled shapes using a single path serializer due to the serializer’s ability to maintain subpaths withing a path. A subpath is a portion of a path that has a beginning and an end. When we call path.moveTo
or path.closePath
we both end the current subpath and begin a new subpath.
As can be seen, the top triangle is not closed and the bottom triangle is closed.
<script> points = [[50,50],[150,125],[200,25],[200,150],[100,250],[250,275]]; var path = d3.path(); path.moveTo(points[0][0], points[0][1]); //(50,50) path.lineTo(points[1][0], points[1][1]); //(150,125) path.lineTo(points[2][0], points[2][1]); //(200,25) path.moveTo(points[3][0], points[3][1]); //(200,150) path.lineTo(points[4][0], points[4][1]); //(100,250) path.lineTo(points[5][0], points[5][1]); //(250,275) path.closePath(); d3.select("#demoPath2") .append("path") .attr("d", path) .attr("stroke", "black") .attr("fill", "red"); d3.select("#demoPath2Outline") .append("path") .attr("d", path) .attr("stroke", "black") .attr("fill", "red"); addPathOutline(points, d3.select("#demoPath2Outline")); </script> <svg id="demoPath2" width= "300" height="300"></svg> <svg id="demoPath2Outline" width= "300" height="300"> </svg>
Curves
Below we draw Bézier curves using path.
(top) and path.
(bottom).
-
path.quadraticCurveTo(cpx, cpy, x, y) - Draws a quadratic Bézier curve to (x,y) using the specified control point.
-
path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y) - Draws a cubic Bézier curve to the specified point (x,y) using the specified control points.
The control points for the methods are depicted by the red dots that are positioned off of the curves.
<script> // path.quadraticCurveTo example var qCP = [25, 80]; var qPath = d3.path(); qPath.moveTo(10,10); qPath.quadraticCurveTo(qCP[0],qCP[1], 190, 10); d3.select("#demoCurve") .append("path") .attr("d", qPath) .attr("stroke", "black") .attr("fill", "none"); // path.bezierCurveTo example var bCP1 = [25, 120]; var bCP2 = [175, 190]; var bPath = d3.path(); bPath.moveTo(10,190); bPath.bezierCurveTo(bCP1[0],bCP1[1], bCP2[0], bCP2[1], 190, 120); d3.select("#demoCurveOutline") .append("path") .attr("d", qPath) .attr("stroke", "black") .attr("fill", "none"); // Draw control points and lines to control points d3.select("#demoCurve") .append("path") .attr("d", bPath) .attr("stroke", "black") .attr("fill", "none"); d3.select("#demoCurveOutline") .append("path") .attr("d", bPath) .attr("stroke", "black") .attr("fill", "none"); addCurveOutline(qCP, bCP1, bCP2, d3.select("#demoCurveOutline")); </script> <svg id="demoCurve" width="200" height="200"></svg> <svg id="demoCurveOutline" width="200" height="200"></svg>
Arcs
The example below uses path.arc
to draw the lower right quadrant of a circle.
-
path.arc(x, y, radius, startAngle, endAngle[, anticlockwise]) - Draws a line from the current point to the start of an arc that is drawn about a circle centered at (x,y) whose radius is specified by
radius
. The arc wraps around the circle from the angle specified instartAngle
to the angle specified inendAngle
. By default the arc is drawn clockwise. To draw the arc counterclockwise, we passtrue
as the final argument.
<script> var center = [100, 100]; var radius = 75; var startAngle = 0; var endAngle = Math.PI / 2; var arcPath = d3.path(); arcPath.moveTo(center[0], center[1]); arcPath.arc(center[0], center[1], radius, startAngle, endAngle) arcPath.closePath(); d3.select("#demoArc") .append("path") .attr("d", arcPath) .attr("stroke", "black") .attr("fill", "red"); d3.select("#demoArcOutline") .append("path") .attr("d", arcPath) .attr("stroke", "black") .attr("fill", "red"); addArcOutline(center, radius, d3.select("#demoArcOutline")); </script> <svg id="demoArc" width="200" height="200"></svg> <svg id="demoArcOutline" width="200" height="200"></svg>
Below we draw a shape using path.arcTo
.
-
path.arcTo(x1, y1, x2, y2, radius) - Draws a line from the current point, (x0,y0), to the start of an arc that is drawn about a circle. The circle about which the arc is drawn is defined as the circle whose radius is specified by
radius
and has a point that is tangent to the line between (x0,y0) and (x1,y1) and has a point that is that is tangent to the line between (x1,y1) and (x2,y2). The start and end of the arc are defined by the tangent points.
In our example, the point at the bottom right corner of the shape (160,190) is the current point from which the initial line and arc are drawn. We then draw a line from the end of the arc to the point at (185,25), and close the path which renders a line from (185,25) to our starting point (160,190).
<script> var points = [[160,190],[10,100],[185,25]]; var radius = 30; var arcToPath = d3.path(); arcToPath.moveTo(points[0][0], points[0][1]); arcToPath.arcTo(points[1][0], points[1][1], points[2][0], points[2][1], radius) arcToPath.lineTo(points[2][0], points[2][1]) arcToPath.closePath(); d3.select("#demoArcTo") .append("path") .attr("d", arcToPath) .attr("stroke", "black") .attr("fill", "red"); d3.select("#demoArcToOutline") .append("path") .attr("d", arcToPath) .attr("stroke", "black") .attr("fill", "red"); addArcToOutline(points[0], points[1], points[2], radius, d3.select("#demoArcToOutline")); </script> <svg id="demoArcTo" width="200" height="200"></svg> <svg id="demoArcToOutline" width="200" height="200"></svg>
Rectangles
Below are examples of rectangles drawn using path.rect
.
-
path.rect(x, y, w, h) - Creates a closed subpath, with width
w
, heighth
, and its bottom left point at (x,y).
<script> var origin = [25,25]; var width = 150; var height = 150; var path = d3.path(); path.rect(origin[0], origin[1], width, height); d3.select("#demoRect") .append("path") .attr("d", path) .attr("fill", "red") .attr("stroke", "black"); d3.select("#demoRectOutline") .append("path") .attr("d", path) .attr("fill", "red") .attr("stroke", "black"); addRectOutline(origin, width, height, d3.select("#demoRectOutline")); </script> <svg id="demoRect" width="200" height="200"></svg> <svg id="demoRectOutline" width="200" height="200"></svg>
Canvases
Since the d3.path
serializer has the same methods as in the CanvasPathMethods interface, then any function that only uses the methods in the CanvasPathMethods interface and that takes a canvas object as an argument, can be passed a path serializer.
Below we render a triangle twice, using the method named draw
. We first pass d3.path
to the draw
method to draw a triangle in an SVG path
element and then draw a triangle in a canvas
element by passing the canvas'
2D context to the draw
method.
<script> function draw(context){ context.fillStyle = "red"; context.moveTo(25,25); context.lineTo(75,25); context.lineTo(75,75); context.closePath(); return context; } d3.select("#demoFunctionSVG") .append("path") .attr("d", draw(d3.path())) .attr("stroke", "black") .attr("fill", "red"); var context = d3.select("#demoFunctionCanvas").node().getContext("2d"); draw(context).fill(); context.stroke(); </script> <svg id="demoFunctionSVG" width=100 height=100></svg> <canvas id="demoFunctionCanvas" width=100 height=100></canvas>