【问题标题】:Split an SVG path lengthwise into multiple colours将 SVG 路径纵向拆分为多种颜色
【发布时间】:2018-08-05 01:42:19
【问题描述】:

我有一个树形可视化,我试图在其中显示代表具有多个类的分布的节点之间的路径。我想将路径纵向分割成多种颜色来表示每个分布的频率。

例如:假设我们有 A 类(红色)和 B 类(黑色),它们的频率均为 50。那么我想要在节点之间有一条半红半黑的路径。这个想法是表示类的相对频率,因此频率将被归一化。

我当前(天真的)尝试是为每个类创建一个单独的路径,然后使用 x 偏移量。看起来像this

但是,如图所示,这些线在路径期间不会保持相等的距离。

相关代码段:

linkGroup.append("path").attr("class", "link")
              .attr("d", diagonal)
              .style("stroke", "red")
              .style("stroke-width", 5)
              .attr("transform", function(d) {
                  return "translate(" + -2.5 + "," + 0.0 + ")"; });

linkGroup.append("path").attr("class", "link")
              .attr("d", diagonal)
              .style("stroke", "black")
              .style("stroke-width", 5)
              .attr("transform", function(d) {
                  return "translate(" + 2.5 + "," + 0.0 + ")"; });

如果有人能提供一些建议,那就太好了。

谢谢!

【问题讨论】:

  • 如果他们的频率都 > 50 怎么办?说红色 80 和黑色 75。
  • 这个想法是表示每个类的相对大小,因此它们将被标准化。在您的示例中,这些值将标准化为 80 / (80+75) 和 75 / (80+75)。

标签: javascript html css d3.js svg


【解决方案1】:

一种可能的解决方案是计算各个路径并填充所需的颜色。

使用 geoexamples.com 中的库 svg-path-properties,您可以计算路径的属性 (x,y,tangent),而无需像在 this SO answer 中那样首先创建它(这不会计算切线)。

sn-p 代码可以实现 2 种颜色,但可以很容易地概括为更多。

您可以使用字典指定笔划的颜色、百分比和宽度

var duoProp = { color: ["red", "black"], percent: 0.30, width: 15 };

percentcolor[0] 从笔划宽度中获取的量。

var duoPath = pathPoints("M30,30C160,30 150,90 250,90S350,210 250,210", 10, duoProp);
duoPath.forEach( (d, i) => {
    svg.append("path")
       .attr("d", d)
       .attr("fill", duoProp.color[i])
       .attr("stroke", "none");
});

pathPoints 参数

  1. 需要描边的路径,可以由d3.linepath example from SO answer生成

    var lineGenerator = d3.line().x(d=>d[0]).y(d=>d[1]).curve(d3.curveNatural);
    var curvePoints = [[0,0],[0,10],[20,30]];
    var duoPath = pathPoints(lineGenerator(curvePoints), 10, duoProp);
    
  2. 采样的路径长度间隔(单位像素)。每 10 个像素给出一个很好的近似值

  3. 带有笔画百分比和宽度的字典

它返回一个数组,其中包含要填充的路径,每种颜色 1。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/svg-path-properties@0.4.4/build/path-properties.min.js"></script>
</head>
<body>
<svg id="chart" width="350" height="350"></svg>
<script>
var svg = d3.select("#chart");

function pathPoints(path, stepLength, duoProp) {
    var props = spp.svgPathProperties(path);
    var length = props.getTotalLength();
    var tList = d3.range(0, length, stepLength);
    tList.push(length);
    var tProps = tList.map(d => props.getPropertiesAtLength(d));
    var pFactor = percent => (percent - 0.5) * duoProp.width;
    tProps.forEach(p => {
        p.x0 = p.x - pFactor(0) * p.tangentY;
        p.y0 = p.y + pFactor(0) * p.tangentX;
        p.xP = p.x - pFactor(duoProp.percent) * p.tangentY;
        p.yP = p.y + pFactor(duoProp.percent) * p.tangentX;
        p.x1 = p.x - pFactor(1) * p.tangentY;
        p.y1 = p.y + pFactor(1) * p.tangentX;
    });
    var format1d = d3.format(".1f");
    var createPath = (forward, backward) => {
        var fp = tProps.map(p => forward(p));
        var bp = tProps.map(p => backward(p));
        bp.reverse();
        return 'M' + fp.concat(bp).map(p => `${format1d(p[0])},${format1d(p[1])}`).join(' ') + 'z';
    }
    return [createPath(p => [p.x0, p.y0], p => [p.xP, p.yP]), createPath(p => [p.xP, p.yP], p => [p.x1, p.y1])]
}

var duoProp = { color: ["red", "black"], percent: 0.30, width: 15 };

var duoPath = pathPoints("M30,30C160,30 150,90 250,90S350,210 250,210", 10, duoProp);

duoPath.forEach( (d, i) => {
    svg.append("path")
       .attr("d", d)
       .attr("fill", duoProp.color[i])
       .attr("stroke", "none");
});
</script>
</body>
</html>

【讨论】:

  • 非常感谢您的详细解答。我已经设法将它应用到我的应用程序中,但我正在努力将它扩展到两种以上的颜色。理想情况下,我想指定一个像 { 'red': 0.2, 'green': 0.3, 'blue': 0.5 } 的字典(其中值是百分比)。您对我如何做到这一点有任何提示吗?谢谢!
【解决方案2】:

作为对 rioV8 出色答案的快速跟进,我能够让他们的代码正常工作,但需要对其进行概括以使用两种以上的颜色。如果其他人有类似的要求,这里是代码:

function pathPoints(path, stepLength, duoProp) {
    // get the properties of the path
    var props = spp.svgPathProperties(path);
    var length = props.getTotalLength();

    // build a list of segments to use as approximation points
    var tList = d3.range(0, length, stepLength);
    tList.push(length);
    var tProps = tList.map(function (d) {
        return props.getPropertiesAtLength(d);
    });

    // incorporate the percentage
    var pFactor = function pFactor(percent) {
        return (percent - 0.5) * duoProp.width;
    };

    // for each path segment, calculate offset points
    tProps.forEach(function (p) {
        // create array to store modified points
        p.x_arr = [];
        p.y_arr = [];

        // calculate offset at 0%
        p.x_arr.push(p.x - pFactor(0) * p.tangentY);
        p.y_arr.push(p.y + pFactor(0) * p.tangentX);

        // calculate offset at each specified percent
        duoProp.percents.forEach(function(perc) {
            p.x_arr.push(p.x - pFactor(perc) * p.tangentY);
            p.y_arr.push(p.y + pFactor(perc) * p.tangentX);
        });

        // calculate offset at 100%
        p.x_arr.push(p.x - pFactor(1) * p.tangentY);
        p.y_arr.push(p.y + pFactor(1) * p.tangentX);
    });

    var format1d = d3.format(".1f");
    var createPath = function createPath(forward, backward) {
        var fp = tProps.map(function (p) {
            return forward(p);
        });
        var bp = tProps.map(function (p) {
            return backward(p);
        });
        bp.reverse();
        return 'M' + fp.concat(bp).map(function (p) {
            return format1d(p[0]) + "," + format1d(p[1]);
        }).join(' ') + 'z';
    };

    // create a path for each projected point
    var paths = [];
    for(var i=0; i <= duoProp.percents.length; i++) {
        paths.push(createPath(function (p) { return [p.x_arr[i], p.y_arr[i]]; }, function (p) { return [p.x_arr[i+1], p.y_arr[i+1]]; }));
    }

    return paths;
}

// generate the line 
var duoProp = { color: ["red", "blue", "green"], percents: [0.5, 0.7], width: 15 };
var duoPath = pathPoints("M30,30C160,30 150,90 250,90S350,210 250,210", 10, duoProp);
duoPath.forEach( (d, i) => {
    svg.append("path")
       .attr("d", d)
       .attr("fill", duoProp.color[i])
       .attr("stroke", "none");
});

请注意,percents 数组指定笔画的累积百分比,而不是宽度的单个百分比。例如。在上面的示例中,红色笔划的宽度为 0% 到 50%,蓝色笔划的宽度为 50% 到 70%,绿色笔划的宽度为 70% 到 100%。

【讨论】:

    猜你喜欢
    • 2015-06-02
    • 2018-11-16
    • 2016-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    • 2015-11-28
    相关资源
    最近更新 更多