cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - Activehtmlicon-personicon-teamoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <div>
  <h1>The SVG <code>&lt;path&gt;</code>: Bezier Curves</h1>
  <p>The SVG <code>&lt;path&gt;</code> element can draw any shape you want &mdash; 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 &mdash; named after  <a href="http://en.wikipedia.org/wiki/Pierre_B%C3%A9zier">the French mathematician</a> who developed the standard notation &mdash; 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 &mdash; 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 &mdash; used to draw the second part of the curve in the diagram &mdash; 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
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

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

Console