Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw a "squiggly line" in SVG

Tags:

javascript

svg

I'm trying to work out how to draw a squiggly line on an arbitrary SVG path element. The path is generated by a React Component. For example, I'm trying to replicate the line in this question:

Squiggly Line Example

Is there an easy way to do this in SVG and/or JavaScript generated paths?

I've considered joining up a series of curves using the s path command, but then I would need to calculate points along the curve. I've also considered some sort of displacement filter but I'm not really sure where to start.

like image 764
will-hart Avatar asked Feb 24 '17 14:02

will-hart


1 Answers

Seems to me the easiest way would be step along the path. Then, at each step, insert a quadratic bezier curve with a control point that is half way between them and perpendicular to the curve. Then for the next step switch the side that the control point is on.

function makeSquiggle(squigglePathId, followPathId, squiggleStep, squiggleAmplitude)
{
  var followPath = document.getElementById(followPathId);
  var pathLen = followPath.getTotalLength();

  // Adjust step so that there are a whole number of steps along the path
  var numSteps = Math.round(pathLen / squiggleStep);

  var pos = followPath.getPointAtLength(0);
  var newPath = "M" + [pos.x, pos.y].join(',');
  var side = -1;
  for (var i=1; i<=numSteps; i++)
  {
    var last = pos;
    var pos = followPath.getPointAtLength(i * pathLen / numSteps);

    // Find a point halfway between last and pos. Then find the point that is
    // perpendicular to that line segment, and is squiggleAmplitude away from
    // it on the side of the line designated by 'side' (-1 or +1).
    // This point will be the control point of the quadratic curve forming the
    // squiggle step.
    
    // The vector from the last point to this one
    var vector = {x: (pos.x - last.x),
                  y: (pos.y - last.y)};
    // The length of this vector
    var vectorLen = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
    // The point halfwasy between last point and tis one
    var half = {x: (last.x + vector.x/2),
                y: (last.y + vector.y/2)};
    // The vector that is perpendicular to 'vector'
    var perpVector = {x: -(squiggleAmplitude * vector.y / vectorLen),
                      y: (squiggleAmplitude * vector.x / vectorLen)};
    // No calculate the control point position
    var controlPoint = {x: (half.x + perpVector.x * side),
                        y: (half.y + perpVector.y * side)};
    newPath += ("Q" + [controlPoint.x, controlPoint.y, pos.x, pos.y].join(','));
    // Switch the side (for next step)
    side = -side;
  }
  var squigglePath = document.getElementById(squigglePathId);
  squigglePath.setAttribute("d", newPath);
}


makeSquiggle("squiggle", "follow", 25, 20);
#follow {
  fill: none;
  stroke: grey;
  stroke-width: 2;
}

#squiggle {
  fill: none;
  stroke: red;
  stroke-width: 2;
}
<svg width="500" height="400">
  <path id="follow" d="M 50,300 C 100,100 300,0, 350,250 L 450,200"/>
  <path id="squiggle" d="M0,0"/>
</svg>
like image 136
Paul LeBeau Avatar answered Oct 14 '22 07:10

Paul LeBeau