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

前端 未结 2 1345
[愿得一人]
[愿得一人] 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/

提交回复
热议问题