` ````
<div>
<h1>The SVG <code><path></code>: Bezier Curves</h1>
<p>The SVG <code><path></code> element can draw any shape you want — but only if you know how to tell it what to do. <a href="https://codepen.io/AmeliaBR/full/pIder">Basic straight-line paths</a> are easy to get the hang of. <a href="">Arcs take some getting used to</a>, but at least use the familiar shapes of circles and ellipses. Bezier curves — named after <a href="http://en.wikipedia.org/wiki/Pierre_B%C3%A9zier">the French mathematician</a> who developed the standard notation — are polynomial curves, but not in the standard <i>f(x)</i> notation from high school algebra. They are defined as weighted averages between the vertices (start and end points of the curve segment) and one or more control points.</p>
</div>
<svg viewBox="-30 -30 160 160" preserveAspectRatio="xMidYMin meet">
<defs>
<marker id="loop" viewBox="-3 -3 6 6" markerWidth="6" markerHeight="6">
<circle r="1.2"/>
</marker>
</defs>
<g id="viewBoxBorder">
<rect width="100" height="100"/>
<text>(0,0)</text>
<text y="100">(0,100)</text>
<text x="100">(100,0)</text>
<text x="100" y="100">(100,100)</text>
</g>
<g id="controls"></g>
<path class="cubic" d='M0,50
C0,0 125,-25 50,50
S 100,125,100,50 '/>
<!-- -->
<path class="quadratic" d='M0,50
Q 70,20 50,50
T 100,50 '/> <!-- -->
</g>
</svg>
<p> Both curves connect the three points (0,50) (50,50) and (100,50), as shown by the red marker elements, but have different control points, as indicated by the straight lines and smaller markers. <span class="navy">The navy curve uses quadratic Bezier curves, with one control point per curve segment.</span> <span class="purple">The purple curve uses cubic Bezier curves, with two control points per segment</span>. </p>
<p> It gets more complicated: for both paths, the second half of the path is defined using the shorthand notation for a smooth curve. This skips the definition of the first control point, which the computer calculates as a mirror reflection of the control point from the previous curve segment. So a shorthand quadratic curve doesn't include <em>any</em> control points in the command statement, only the end-point coordinates.</p>
<p> But <em>how</em> is the curve determined from the control points? As you can see from the diagrams, the control points, and the lines that connect them, extend well beyond the shape of the curves.</p>
<svg class="small" viewBox="-30 -30 160 160" preserveAspectRatio="xMidYMin meet">
<use xlink:href="#viewBoxBorder"/>
<g id="controls"></g>
<path class="quadratic" d="M0,75
Q 50,0 75,50"
title="M0,75 Q 50,0 75,50"/>
</svg>
<p><strong>Let's start with the simpler case: quadratic Bezier curves.</strong> These have one control point defining the shape of the curve between each pair of vertex points. The small diagram shows a single quadratic curve. The control point is connected to the start and end vertex points with dashed lines. The mid-points of those lines are connected by the dotted line. And the mid-point of the dotted line is also the mid-point of the curve.</p>
<p> The same relationship can be created for <em>any</em> point on the curve: the point 10% along the curve is 10% of the way from the 10% point of the first line to the 10% point of the second line.</p>
<p>Bezier curves are defined this way because it makes them easy to calculate with basic arithmetic — the curve may have the same shape as a quadratic polynomial, but there are no <i>x<small><sup>2</sup></small></i> terms to calculate. Instead, the curve's positions are calculated as weighted averages. The mid-point of a line between <code>(x1,y1)</code> and <code>(x2,y2)</code> is the point
<blockquote><code>
(average(x1,x2), average(y1,y2))
</code></blockquote>
More generally, the point that is <code>p%</code> of the way between them is at
<blockquote><code>
(( x1*(100%-p%) + x2*(p%) ),
( y1*(100%-p%) + y2*(p%) ) )
</code></blockquote>
That sort of calculation is used by the computer to plot the Bezier curve, except the computer uses powers of two instead of percentages for fast binary math. The curve is divided in half and in half again until enough points have been plotted to reach the resolution of the display. For example, for the first curve segment of the navy line in the diagram, this is how the curve is positioned when broken down into eighths:
<table>
<caption>
Calculation of positions on a quadratic Bezier curve between <code>(0,50)</code> and <code>(50,50)</code>, with a control point at<code>(70,20)</code>.
</caption>
<tHead><tr>
<th rowspan="2">Position</th>
<th>Start of line</th>
<th>End of line</th>
<th>Curve Position</th>
</tr>
<tr>
<!-- skip first column -->
<th><code>(1-p)*(0,50) + p*(70,20)</code></th>
<th><code>(1-p)*(70,20) + p*(50,50)</code></th>
<th><code>(1-p)*Start + p*End</code></th>
</tr></tHead>
<tr><td>0/8 = start</td><td>(0.00,50.00)</td><td>(70.00,20.00)</td><td>(0.00,50.00)</td></tr>
<tr><td>1/8</td><td>(8.75,46.25)</td><td>(67.50,23.75)</td><td>(16.09,43.44)</td></tr>
<tr><td>2/8 = 1/4</td><td>(17.50,42.50)</td><td>(65.00,27.50)</td><td>(29.38,38.75)</td></tr>
<tr><td>3/8</td><td>(26.25,38.75)</td><td>(62.50,31.25)</td><td>(39.84,35.94)</td></tr>
<tr><td>4/8 = 1/2</td><td>(35.00,35.00)</td><td>(60.00,35.00)</td><td>(47.50,35.00)</td></tr>
<tr><td>5/8</td><td>(43.75,31.25)</td><td>(57.50,38.75)</td><td>(52.34,35.94)</td></tr>
<tr><td>6/8 = 3/4</td><td>(52.50,27.50)</td><td>(55.00,42.50)</td><td>(54.38,38.75)</td></tr>
<tr><td>7/8</td><td>(61.25,23.75)</td><td>(52.50,46.25)</td><td>(53.59,43.44)</td></tr>
<tr><td>8/8 = end</td><td>(70.00,20.00)</td><td>(50.00,50.00)</td><td>(50.00,50.00)</td></tr>
</table>
</p>
<p> That's a lot of numbers for just the first rough approximation of a curve. Clearly, you'd never want to plot a Bezier curve by calculating every position yourself. How does knowing the math help you figure out how to draw with Bezier curves? There are a few key ideas to use:
<ul>
<li><p>
At the start of the curve, the curve position is very close to the straight line between the start vertex and the control point. The curve then pulls away from this line until at the end of the curve it approaches the line from the control point to the end vertex. These lines are the <em>tangents</em> to the curve at the vertex points, meaning the straight line that follows the same angle as the curve at that point.
</p></li>
<li><p>
A <em>smooth curve</em> has no sharp changes in its tangent angle, so to connect two curve segments smoothly the line from the vertex to the new control point must continue the straight line from the vertex to the previous control point. The shorthand curve notation — used to draw the second part of the curve in the diagram — assures this, while also making the control point an equal distance away from the vertex.
</p></li>
<li><p>
The shape of the curve is always contained within the shape (triangle for quadratic curves) made by the lines between the vertex and control points.
</p></li>
<li><p>
More specifically, the shape of the quadratic curve is contained tightly by the quadrilateral made after cutting off the tip of the triangle at the mid-point of each line. The mid-point of the curve will be tangent to that line.
</p></li>
</ul>
</p>
<svg class="small" viewBox="-30 -30 160 160" preserveAspectRatio="xMidYMin meet">
<use xlink:href="#viewBoxBorder"/>
<g id="controls"></g>
<path class="quadratic" d="M0,90
q 20,-30 12.5,-10
t12.5,-10 12.5,-10 12.5,-10
12.5,-10 12.5,-10 12.5,-10 12.5,-10"
title="M0,75 Q q 20,-30 15,-10
t12.5,-10 12.5,-10 12.5,-10 12.5,-10 12.5,-10 12.5,-10 12.5,-10"/>
</svg>
<p>
</p>
<p><b>Discussion of cubic curves & Animated diagrams still under construction...</b></p>
```

` ````
svg {
width:100%;
height:calc(100% - 7em);
overflow:visible;
background-color:aliceblue;
border:3px ridge lightslategray;
}
svg.small {
width:45%;
height:45vm;
max-width:20em;
max-height:20em;
min-height:10em;
float:right;
clear:right;
margin:0.5em 0em 0.5em 1em;
}
rect, path, circle {
fill:none;
}
rect {
stroke:darkgray; stroke-width:1;
stroke-dasharray:2 1;
}
text {
text-anchor:middle;
font-size:5pt;
}
path{
stroke-width:1;
fill-opacity:0;
marker-start:url(#loop);
marker-end:url(#loop);
pointer-events:all;
}
.crisp {
shape-rendering:crispEdges;
}
rect {
stroke:darkgray; stroke-width:1;
stroke-dasharray:2 1;
}
text {
text-anchor:middle;
font-size:5pt;
}
path, line{
stroke:black; stroke-width:1;
marker:url(#loop);
}
path.cubic, g.pathControls.cubic line {
stroke:purple;
}
path.quadratic, g.pathControls.quadratic line {
stroke:navy;
}
marker#loop circle{
stroke-width:0.5;
fill:red;
fill-opacity:1;
stroke:darkred;
stroke-dasharray:0;
}
line.control, line.midpoint {
stroke-width:0.7;
stroke-dasharray: 5 1;
opacity:0.5;
}
line.midpoint {
stroke-dasharray: 1 1;
marker:none;
}
h1{
text-align:center;
font-variant:small-caps;
}
h1 code {
font-variant: normal;
font-size: larger;
}
body {
max-width: 40em;
margin-left:auto;
margin-right:auto;
}
table {
border: solid 2px darkgray;
border-collapse: collapse;
margin:0 auto;
}
td,th {
border: solid 1px gray;
padding:0.2em 0.5em;
text-align:center;
}
tHead {
border: solid 2px darkgray;
}
table caption {
margin:1em 3em 0;
font-style:italic;
text-align:justified;
}
span.dashed {
border-bottom: dashed 1px;
}
span.dotted {
border-bottom: dotted 1px;
}
span.purple {
color:purple;
}
span.navy {
color:navy;
}
@media (min-width: 50em) {
svg {
float:left;
max-width:30em;
margin: 3em;
}
body {
max-width:72em;
padding:0 1em;
}
h1 + p {
max-width: 40em;
margin:auto;
}
table {
float:left;
margin-right:2em;
}
}
```

` ````
d3.selectAll("svg").each(drawPaths);
function drawPaths() {
var svg = d3.select(this);
var paths = svg.selectAll("path");
var directions = [];
var dSplits = /\b(?=[a-zA-Z])|\B(?=[a-zA-Z])/ ;
//regular expression that matches the character boundary before a letter
//regardless of whether there is whitespace there, without matching the
//character itself (since we don't want it removed in the split)
paths.each(function(d){
directions.push(
d3.select(this).attr("d").trim().split(dSplits)
);
});
//console.log(directions);
//directions is now an array (representing paths)
//of arrays (representing individual directions)
var allPathGroups = svg.select("g#controls")
.selectAll("g.pathControls")
.data(directions); //assign one top-level array per group
allPathGroups.enter().append("g")
.attr("class", function(d,i){
return "pathControls " +
svg.selectAll(
"path:nth-of-type("+(i+1)+")").attr("class");
});
//create enough groups
var pSplits = /\s*,\s*|\s+/ ;
//regular expression that matches a comma (possibly surrounded by whitespace),
//or plain whitespace
allPathGroups.html("")//erase any existing content
.each(function(dArray, i){
console.log("Path " + i);
var vertex = [0,0];
//start point for curve commands
var control = [0,0];
//control point for curve commands
var mid ;//= [[],[]];
//middle control line
var pathGroup=d3.select(this); //group to add control lines/nodes to
var lineGroup; //subgroup for individual sets of control lines
dArray.forEach(function(dElement, j){
console.log(dElement)
var letter = dElement.charAt(0);
//The first char will always be a letter because of the
//dSplits regular expression
var points = dElement.substr(1).trim().split(pSplits);
//takes the remainder of the instruction and splits it
//into individual numbers
console.log(letter, points);
var midpoints = [];
switch (letter) {
//See http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands
//for defintions of the different direction types
case "c": case "s": case "q": case "t":
case "S": case "C": case "Q": case "T":
//Remainder of Bezier curve options
while(points.length) {
// repeat for multiple curves in a statement
if (/[csqt]/.test(letter)){
//Bezier curves using relative positions
//adjust the points values to be absolute positions
//by adding the x or y value from the start vertex
var z = {c:6, s:4, q:4, t:2}[letter];
//number of points per curve segment
while (z--)
points[z] = +points[z] + +vertex[z%2];
}
lineGroup = pathGroup.append("g")
.attr("class", "curveControls " + letter);
//Calculate the first control point
if (/[SsTt]/.test(letter)) {
//a symmetrical curve or symmetrical quadratic
//calculate the next control point from the last control point
//and the current vertex position:
control[0] = 2*vertex[0] - control[0];
//equal to vertex[0] + (vertex[0] - control[0])
//i.e., same distance away from vertex, but on the opposite side
control[1] = 2*vertex[1] - control[1];
}
else {
//regular curve notation,
//read the next control point from the points array
//(remembering that we've already adjusted for relative notation)
control[0] = +points.shift();
control[1] = +points.shift();
}
//Draw the control line for the start of the curve
lineGroup.append("line")
.attr("class", "control start " + letter)
.attr("x1", vertex[0])
.attr("y1", vertex[1])
.attr("x2", control[0])
.attr("y2", control[1]);
midpoints = [ [(vertex[0]+control[0])/2,
(vertex[1]+control[1])/2] ];
//For cubic curves:
if (/[SsCc]/.test(letter)){
//Append the middle line and set it's start position
mid = lineGroup.append("line")
.attr("class", "control middle " + letter)
.attr("x1", control[0])
.attr("y1", control[1]);
//store in midpoints array
midpoints.push([ control[0]/2,
control[1]/2 ] );
//Read the second control point:
control[0] = +points.shift();
control[1] = +points.shift();
//Set the end point for the middle control line
mid.attr("x2", control[0])
.attr("y2", control[1]);
//calculate midpoint of middle control
midpoints[1][0] += control[0]/2;
midpoints[1][1] += control[1]/2;
}
//Read the new vertex position
vertex[0] = +points.shift();
vertex[1] = +points.shift();
//Draw the control line for the end of the curve
lineGroup.append("line")
.attr("class", "control end " + letter)
.attr("x1", vertex[0])
.attr("y1", vertex[1])
.attr("x2", control[0])
.attr("y2", control[1]);
//calculate last midpoint
midpoints.push([(vertex[0]+control[0])/2,
(vertex[1]+control[1])/2] );
//draw midpoint lines
lineGroup.append("line")
.attr("class", "midpoint " + letter)
.attr("x1", midpoints[0][0])
.attr("y1", midpoints[0][1])
.attr("x2", midpoints[1][0])
.attr("y2", midpoints[1][1]);
if (midpoints.length > 2) {
lineGroup.append("line")
.attr("class", "midpoint " + letter)
.attr("x1", midpoints[1][0])
.attr("y1", midpoints[1][1])
.attr("x2", midpoints[2][0])
.attr("y2", midpoints[2][1]);
//second-level midpoint:
lineGroup.append("line")
.attr("class", "midpoint depth2 " + letter)
.attr("x1", (midpoints[0][0] + midpoints[1][0])/2 )
.attr("y1", (midpoints[0][1] + midpoints[1][1])/2 )
.attr("x2", (midpoints[1][0] + midpoints[2][0])/2 )
.attr("y2", (midpoints[1][1] + midpoints[2][1])/2 );
}
}
break;
//The remaining cases are not Bezier curves, so we don't draw
//anything, but the start point for the next curve gets
//adjusted.
case "a": //Eliptical arc, relative notation
while (points.length) {
points.shift(5);
vertex[0] += +points.shift();
vertex[1] += +points.shift();
}
break;
case "A": //Eliptical arc
case "M": case "L": //Move to or Line to
//set the start point to the last point in the list of commands
vertex[1] = +points.pop();
vertex[0] = +points.pop();
break;
case "m": case "l": //move by or line by
//adjust the start point by the cumulative effect of all the
//(relative) commands
while (points.length) {
vertex[0] += +points.shift();
vertex[1] += +points.shift();
}
break;
case "H": //Horizontal line to
//set the start point's x coordinate to the final coordinate
vertex[0] = +points.pop();
break;
case "h": //horizontal line by
//adjust the start point's x coordinate by the sum of values
while (points.length) {
vertex[0] += +points.shift();
}
break;
case "V": //Vertical line to
//set the y coordinate to the final coordinate
vertex[1] = +points.pop();
break;
case "v": //vertical line by
//adjust the y coordinate by the sum of values
while (points.length) {
vertex[1] += +points.shift();
}
break;
} //end of switch
}); //end of dArray.forEach
}); //end of allPathGroups.each
}//end of drawPaths function
```

999px

Loading
..................

Alt F
Opt F
Find & Replace

Also see: Tab Triggers