我有一个树形可视化文件,其中我试图显示代表多个类分布的节点之间的路径。我想将路径纵向分成多种颜色,以表示每种分布的频率。
例如:假设我们有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 + ")"; });
如果有人提出建议,那就太好了。
谢谢!
答案 0 :(得分:1)
一种可能的解决方案是计算各个路径并填充所需的颜色。
使用svg-path-properties中的库geoexamples.com,无需像创建this SO answer一样先创建路径即可计算路径的属性(x,y,切线)(这不会计算切线)。
该代码段可用于2种颜色,但可以很容易地将其概括为更多颜色。
您可以用字典指定笔触的颜色,百分比和宽度
var duoProp = { color: ["red", "black"], percent: 0.30, width: 15 };
percent
是color[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
参数
d3.line
path 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);
要采样的路径长度间隔(单位像素)。每10个像素给出一个很好的近似值
字典,带有笔划的百分比和宽度
它返回一个包含要填充的路径的数组,每种颜色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>
答案 1 :(得分:1)
作为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%。