d3.js How to simplify a complex path - using a custom algorithm

前端 未结 2 1324
[愿得一人]
[愿得一人] 2021-01-22 01:08

\"enter

I\'ve got a very basic example here. http://jsfiddle.net/jEfsh/57/ that creates a

相关标签:
2条回答
  • 2021-01-22 01:14

    It's not clear what your problem is exactly. Do you have problems to turn the SVG data string into a list of points? You can use this:

    function path_from_svg(svg) {
        var pts = svg.split(/[ML]/);
        var path = [];
    
        console.log(pts.length);
        for (var i = 1; i < pts.length; i++) {
            path.push(pts[i].split(","));
        }
    
        return path;
    }
    

    It is a very simple approach: It splits the string on all move (M) and line (L) commands and treats them as lines. It then splits all substrings on the comma. The first "substring" is ignored, because it is the empty string before the first M. If there is a way to do this better in d3 I haven't found it.

    The reverse operation is easier:

    function svg_to_path(path) {
        return "M" + path.join("L");
    }
    

    This is equivalent to svg.line.interpolate("linear").

    You can then implement the Douglas-Peucker algorithm on this path data recursively:

    function path_simplify_r(path, first, last, eps) {
        if (first >= last - 1) return [path[first]];
    
        var px = path[first][0];
        var py = path[first][1];
    
        var dx = path[last][0] - px;
        var dy = path[last][1] - py;
    
        var nn = Math.sqrt(dx*dx + dy*dy);
        var nx = -dy / nn;
        var ny = dx / nn;
    
        var ii = first;
        var max = -1;
    
        for (var i = first + 1; i < last; i++) {
            var p = path[i];
    
            var qx = p[0] - px;
            var qy = p[1] - py;
    
            var d = Math.abs(qx * nx + qy * ny);
            if (d > max) {
                max = d;
                ii = i;
            }
        }
    
        if (max < eps) return [path[first]];
    
        var p1 = path_simplify_r(path, first, ii, eps);
        var p2 = path_simplify_r(path, ii, last, eps);
    
        return p1.concat(p2);        
    }
    
    function path_simplify(path, eps) {
        var p = path_simplify_r(path, 0, path.length - 1, eps);
        return p.concat([path[path.length - 1]]);
    }
    

    The distance to the line is not calculated in a separate function but directly with the formula for the distance of a point to a 2d line from the normal {nx, ny} on the line vector {dx, dy} between the first and last point. The normal is normalised, nx*nx + ny*ny == 1.

    When creating the paths, only the first point is added, the last point path[last] is implied and must be added in path_simplify, which is a front end to the recursive function path_simplify_r. This approach was chosen so that concatenating the left and right subpaths does not create a duplicate point in the middle. (This could equally well and maybe cleaner be done by joining p1 and p2.slice(1).)

    Here's everything put together in a fiddle: http://jsfiddle.net/Cbk9J/3/

    0 讨论(0)
  • 2021-01-22 01:35

    Lots of good references in the comments to this question -- alas they are comments and not suggested answers which can be truly voted on.

    http://bost.ocks.org/mike/simplify/

    shows interactive use of this kind of thing which references Douglas-Peucker but also Visvalingam.

    0 讨论(0)
提交回复
热议问题