Dilpreet Kaur
Dilpreet Kaur

Reputation: 21

Make rough svg paths smoother

I am creating a drawing application to create svg paths on mouse movement. But the issue is the paths created are not getting smoothed. For code please view Github link

I want to create smooth paths just like this. but I want to implement this in svg Paths not in canvas so that i can add, edit, delete, re-size and drag drop them individually.

The paths created are like

circle,
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="960" height="500">
  <g transform="translate(0,0)"></g>
  <path class="line" d="M467,238L466.83333333333326,237.83333333333331C466.66666666666663,237.66666666666666,466.33333333333326,237.33333333333331,465.66666666666663,237C464.99999999999994,236.66666666666666,463.99999999999994,236.33333333333331,462.33333333333326,235.83333333333331C460.66666666666663,235.33333333333331,458.3333333333333,234.66666666666666,455.49999999999994,233.99999999999997C452.66666666666663,233.33333333333331,449.3333333333333,232.66666666666666,447,232.33333333333331C444.66666666666663,232,443.33333333333326,232,441.49999999999994,231.99999999999997C439.66666666666663,232,437.3333333333333,232,435.33333333333326,231.99999999999997C433.3333333333333,232,431.66666666666663,232,429.66666666666663,231.99999999999997C427.66666666666663,232,425.3333333333333,232,422.99999999999994,231.99999999999997C420.66666666666663,232,418.3333333333333,232,416,232.33333333333331C413.66666666666663,232.66666666666666,411.33333333333326,233.33333333333331,408.99999999999994,234C406.66666666666663,234.66666666666666,404.3333333333333,235.33333333333331,401.99999999999994,236.16666666666663C399.66666666666663,236.99999999999997,397.3333333333333,237.99999999999997,395.16666666666663,239.16666666666663C393,240.33333333333331,391,241.66666666666666,389.16666666666663,242.83333333333331C387.3333333333333,244,385.66666666666663,245,384.3333333333333,246.16666666666666C383,247.33333333333331,382,248.66666666666666,380.6666666666667,250.33333333333331C379.3333333333333,252,377.66666666666663,254,376.3333333333333,255.49999999999997C375,257,374,258,373.16666666666663,259.16666666666663C372.3333333333333,260.3333333333333,371.66666666666663,261.66666666666663,371.1666666666667,262.66666666666663C370.66666666666663,263.66666666666663,370.3333333333333,264.3333333333333,370,265.1666666666667C369.66666666666663,266,369.3333333333333,267,369,268C368.66666666666663,269,368.3333333333333,270,368.16666666666663,270.8333333333333C368,271.66666666666663,368,272.3333333333333,367.99999999999994,273.16666666666663C368,274,368,275,367.99999999999994,276C368,277,368,278,367.8333333333333,279C367.66666666666663,280,367.3333333333333,281,367.1666666666667,282C367,283,367,284,367,285C367,286,367,287,367,288.5C367,290,367,292,367,293.5C367,295,367,296,367,297.1666666666667C367,298.3333333333333,367,299.66666666666663,367,301.16666666666663C367,302.66666666666663,367,304.3333333333333,367.3333333333333,305.8333333333333C367.66666666666663,307.3333333333333,368.3333333333333,308.66666666666663,368.83333333333337,309.8333333333333C369.3333333333333,311,369.66666666666663,312,370.3333333333333,313.16666666666663C371,314.3333333333333,372,315.66666666666663,372.8333333333333,316.8333333333333C373.66666666666663,318,374.3333333333333,319,375.1666666666667,319.99999999999994C376,321,377,322,378,322.8333333333333C379,323.66666666666663,380,324.3333333333333,383.16666666666663,325.6666666666667C386.3333333333333,327,391.66666666666663,329,395.33333333333326,330.16666666666663C398.99999999999994,331.3333333333333,400.99999999999994,331.66666666666663,403.1666666666666,332C405.33333333333326,332.3333333333333,407.66666666666663,332.66666666666663,410.16666666666663,332.8333333333333C412.66666666666663,333,415.33333333333326,333,417.83333333333326,333C420.33333333333326,333,422.66666666666663,333,425.16666666666663,333C427.66666666666663,333,430.33333333333326,333,434.16666666666663,332.8333333333333C437.99999999999994,332.66666666666663,442.99999999999994,332.3333333333333,446.33333333333326,332C449.66666666666663,331.66666666666663,451.3333333333333,331.3333333333333,452.99999999999994,330.8333333333333C454.66666666666663,330.3333333333333,456.3333333333333,329.66666666666663,457.66666666666663,329C459,328.3333333333333,460,327.66666666666663,460.83333333333326,327.16666666666663C461.66666666666663,326.66666666666663,462.33333333333326,326.3333333333333,463.49999999999994,325.5C464.66666666666663,324.66666666666663,466.3333333333333,323.3333333333333,467.49999999999994,322.6666666666667C468.66666666666663,322,469.3333333333333,322,469.8333333333333,321.8333333333333C470.3333333333333,321.66666666666663,470.66666666666663,321.3333333333333,471,321C471.3333333333333,320.66666666666663,471.66666666666663,320.3333333333333,471.99999999999994,320.16666666666663C472.33333333333326,320,472.66666666666663,320,473,319.8333333333333C473.3333333333333,319.66666666666663,473.66666666666663,319.3333333333333,473.8333333333333,319C474,318.66666666666663,474,318.3333333333333,474.16666666666663,318.1666666666667C474.3333333333333,318,474.66666666666663,318,474.83333333333326,318L475,318"></path>
</svg>

How can I make them smooth instead of rough?

Upvotes: 1

Views: 813

Answers (1)

Dilpreet Kaur
Dilpreet Kaur

Reputation: 21

The issue in code was that after simplification of coordinates, while redrawing the path, the old array was used instead of new simplified array.

svg {
  background: #ddd;
  font: 10px sans-serif;
  cursor: crosshair;
}

.line {
  cursor: crosshair;
  fill: none;
  stroke: #000;
  stroke-width: 2px;
  stroke-linejoin: round;
}

#output {
  position: relative;
  top: -2em;
  left: 0.67em;
  font: 12px/1.4 monospace;
}
<html>
<body>
<div id="sketch"></div>
<div id="output"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
(function () { "use strict";

// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)

// square distance between 2 points
function getSqDist(p1, p2) {

    var dx = p1.x - p2.x,
        dy = p1.y - p2.y;

    return dx * dx + dy * dy;
}

// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {

    var x = p1.x,
        y = p1.y,
        dx = p2.x - x,
        dy = p2.y - y;

    if (dx !== 0 || dy !== 0) {

        var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);

        if (t > 1) {
            x = p2.x;
            y = p2.y;

        } else if (t > 0) {
            x += dx * t;
            y += dy * t;
        }
    }

    dx = p.x - x;
    dy = p.y - y;

    return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format

// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {

    var prevPoint = points[0],
        newPoints = [prevPoint],
        point;

    for (var i = 1, len = points.length; i < len; i++) {
        point = points[i];

        if (getSqDist(point, prevPoint) > sqTolerance) {
            newPoints.push(point);
            prevPoint = point;
        }
    }

    if (prevPoint !== point) {
        newPoints.push(point);
    }

    return newPoints;
}

// simplification using optimized Douglas-Peucker algorithm with recursion elimination
function simplifyDouglasPeucker(points, sqTolerance) {

    var len = points.length,
        MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
        markers = new MarkerArray(len),
        first = 0,
        last = len - 1,
        stack = [],
        newPoints = [],
        i, maxSqDist, sqDist, index;

    markers[first] = markers[last] = 1;

    while (last) {

        maxSqDist = 0;

        for (i = first + 1; i < last; i++) {
            sqDist = getSqSegDist(points[i], points[first], points[last]);

            if (sqDist > maxSqDist) {
                index = i;
                maxSqDist = sqDist;
            }
        }

        if (maxSqDist > sqTolerance) {
            markers[index] = 1;
            stack.push(first, index, index, last);
        }

        last = stack.pop();
        first = stack.pop();
    }

    for (i = 0; i < len; i++) {
        if (markers[i]) {
            newPoints.push(points[i]);
        }
    }

    return newPoints;
}

// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {

    var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
    
    //alert(sqTolerance);

    points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
    points = simplifyDouglasPeucker(points, sqTolerance);

    return points;
}

// export as AMD module / Node module / browser variable
if (typeof define === 'function' && define.amd) {
    define(function() {
        return simplify;
    });
} else if (typeof module !== 'undefined') {
    module.exports = simplify;
} else {
    window.simplify = simplify;
}

})();

</script>

<script>
var margin = {top: 0, right: 0, bottom: 0, left: 0},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var ptdata = [];
var session = [];
var path;
var drawing = false;

var output = d3.select('#output');

var line = d3.svg.line()
    .interpolate("basis")
    .tension(1)
    .x(function(d, i) { return d.x; })
    .y(function(d, i) { return d.y; });

var svg = d3.select("#sketch").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)

svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

svg
  .on("mousedown", listen)
  .on("touchstart", listen)
  .on("touchend", ignore)
  .on("touchleave", ignore)
  .on("mouseup", ignore)
  .on("mouseleave", ignore);


// ignore default touch behavior
var touchEvents = ['touchstart', 'touchmove', 'touchend'];
touchEvents.forEach(function (eventName) {
  document.body.addEventListener(eventName, function(e){
    e.preventDefault();
  });  
});


function listen () {
  drawing = true;
  output.text('event: ' + d3.event.type);
  ptdata = []; // reset point data
  path = svg.append("path") // start a new line
    .data([ptdata])
    .attr("class", "line")
    .attr("d", line);

  if (d3.event.type === 'mousedown') {
    svg.on("mousemove", onmove);
  } else {
    svg.on("touchmove", onmove);
  }
}

function ignore () {
  var before, after;
  output.text('event: ' + d3.event.type);
  svg.on("mousemove", null);
  svg.on("touchmove", null);

  // skip out if we're not drawing
  if (!drawing) return;
  drawing = false;

  before = ptdata.length;
  console.group('Line Simplification');
  console.log("Before simplification:", before)
  
  
  console.log(ptdata);
  ptdata = simplify(ptdata, 100, true);
  console.log(ptdata);
  
  //ptdata.smooth();
  //ptdata.simplify1(10);
  after = ptdata.length;
  
  //console.log(ptdata);

  console.log("After simplification:", ptdata.length)
  console.groupEnd();

  var percentage = parseInt(100 - (after/before)*100, 10);
  output.html('Points: ' + before + ' => ' + after + '. <b>' + percentage + '% simplification.</b>');

  // add newly created line to the drawing session
  session.push(ptdata);
  
  // redraw the line after simplification
  tick();
}


function onmove (e) {
  var type = d3.event.type;
  var point;

  if (type === 'mousemove') {
    point = d3.mouse(this);
    output.text('event: ' + type + ': ' + d3.mouse(this));
  } else {
    // only deal with a single touch input
    point = d3.touches(this)[0];
    output.text('event: ' + type + ': ' + d3.touches(this)[0]);
  }

  // push a new data point onto the back
  ptdata.push({ x: point[0], y: point[1] });
  //alert("x: "+point[0]+" y: "+point[1]);
  tick();
}

function tick() {
  path.attr("d", function(d) { 
    console.log("before d:", d.length)
    d = simplify(d, 2, true);
    console.log("after d:", d.length)
  return line(d); }) // Redraw the path:
}
</script>
</body>
</html>

Upvotes: 1

Related Questions