我正在研究一个sankey图,只要我使用d3.js和sankey插件更改过滤器(单选按钮),该图就会更新。现在,我尝试添加一项功能,每当我将鼠标悬停在一条路径上时,都会在从源节点的颜色到目标节点的颜色的路径上添加线性渐变。如果我不使用滤镜,一切正常,但是如果我应用滤镜(颜色设置错误),则渐变颜色将不起作用,因为链接已过渡。我认为我必须以某种方式转换线性渐变,但是我不明白我该怎么做。
我写了一个小脚本来显示问题,在单击按钮颜色正确之前,以及弄乱之后,都是这样。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey@0.6"></script>
</head>
<body>
<svg id="diagram" height="150" width="600"></svg>
<button onclick="updateSankey()">Click Me!</button>
<style>
#diagram{
border: 1px solid black;
}
</style>
<script>
var target = 0;
var sankeyLinks;
var sankeyData = {nodes:[], links:[]};
calculateLinks();
initSankey();
updateSankey();
function initSankey() {
/*simple initialisation of the sankey, should explain itself*/
svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]])
.iterations(0);
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
// .attr("transform", "translate(" + marginleft + "," + margintop + ")");
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//.attr("stroke", "#000")
//.attr("stroke-opacity", 0.2);
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
function calculateLinks() {
if(target == 0)
{
target = 1;
sankeyLinks = [{source:0, target:1, value:5},{source:0, target:2, value:10},{source:0, target:3, value:15}];
}
else
{
target = 0;
sankeyLinks = [{source:0, target:2, value:15},{source:0, target:1, value:20},{source:0, target:3, value:10}];
}
}
function updateSankey() {
calculateLinks();
sankeyData.links = sankeyLinks;
sankeyData.nodes = [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
sankey(sankeyData);
var links = linkGroup.selectAll('path')
.data(sankeyData.links);
//Set attributes for each link separately
links.enter().append("g")
.attr("id",function (d,i) {return "path"+i;})
.append("path")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.15)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d,id) {
var pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
path.attr("stroke","url(#grad"+id+")")
.attr("stroke-opacity","0.95");
})
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
path.attr("stroke","#000")
.attr("stroke-opacity","0.15");
})
.append("title")
.text(function (d) {
//tooltip info for the links
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var pathGradient = svg.select(".links")
.selectAll("g")
.append("defs")
.append("linearGradient")
.attr("id",function (d, id) {
return "grad" + id;
})
//.attr("from", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("from");})
//.attr("to", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("to");})
.attr("gradientUnit","userSpaceOnUse")
.attr("style","mix-blend-mode: multiply;")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color + ";stop-opacity:1";
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color + ";stop-opacity:1";
});
links.transition(t)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
//same argumentation as above, we need the method again for the transition
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
links.exit().remove();
var nodes = nodeGroup.selectAll('.node')
.data(sankeyData.nodes);
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
//set attributes for each node separately
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Exit selection
nodes.exit().remove();
}
function setColor(d) {
switch (d.name) {
case "first":
return "#f00";
case "second":
return "#ff0";
case "third":
return "#f0f";
case "fourth":
return "#0ff";
default:
return "#0f0";
}
}
</script>
</body>
</html>
单击一次按钮后,从红色到紫色节点的路径具有从红色到黄色的线性渐变,即使我希望它从红色变为紫色。
我已经意识到,我可以在.iterations(15)
中写例如.iterations(0)
而不是initSankey()
来解决这个问题。在实际的项目中,我无法执行此操作,因为必须强制节点的顺序。
我希望我的解释足够清楚,如果没有,请随时提问。
如果有人可以告诉我如何解决此问题,我将非常高兴。
问候
PS。在此摘要中,顶部的链接在悬停时消失了,我已经在实际项目中修复了此问题,这没什么大不了的
答案 0 :(得分:2)
您的问题是,渐变网址基于i,对于每次更新的特定链接,它可能会有所不同(即,链接的顺序可能不同,因此i值会有所不同);并且数据更新不是基于链接的恒定唯一ID。
在代码段中,我为calculateLinks函数中的链接添加了唯一的名称值,然后将其用于数据连接并创建def渐变,这意味着它们在每次更新时保持不变。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey"></script>
</head>
<body>
<svg id="diagram" height="150" width="600"></svg>
<button onclick="updateSankey()">Click Me!</button>
<style>
#diagram{
border: 1px solid black;
}
</style>
<script>
var target = 0;
var sankeyLinks;
var sankeyData = {nodes:[], links:[]};
calculateLinks();
initSankey();
updateSankey();
function initSankey() {
svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width - 1, height - 6])
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
svg.append("defs")
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
function calculateLinks() {
if(target == 0)
{
target = 1;
sankeyLinks = [
{name: "firstsecond",source:0, target:1, value:5},
{name: "firstthird",source:0, target:2, value:10},
{name: "firstfourth",source:0, target:3, value:15}];
}
else
{
target = 0;
sankeyLinks = [
{name: "firstthird", source:0, target:2, value:15},
{name: "firstsecond", source:0, target:1, value:20},
{name: "firstfourth", source:0, target:3, value:10}
];
}
}
function updateSankey() {
calculateLinks();
sankeyData.links = sankeyLinks;
sankeyData.nodes = [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
sankey(sankeyData);
var pathGradient = svg.select("defs").selectAll("linearGradient")
.data(sankeyData.links, function(d){ return d.name })
.enter()
.append("linearGradient")
.attr("id",function (d) {
return "grad" + d.name;
})
.attr("gradientUnit","userSpaceOnUse")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color;
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color;
});
var links = linkGroup.selectAll('path')
.data(sankeyData.links, function(d){ return d.name });
//Set attributes for each link separately
var linksenter = links.enter()
.append("g")
.attr("id",function (d) {return "path" + d.name;})
.append("path")
.style("stroke", "#000")
.style("stroke-opacity", 0.15)
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d) {
var pathGroup = svg.select('#path' + d.name);
var path = pathGroup.select("path");
path.style("stroke","url(#grad" + d.name + ")")
.style("stroke-opacity","0.95");
})
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + d.source.name + d.target.name);
var path = pathGroup.select("path");
path.style("stroke","#000")
.style("stroke-opacity","0.15");
})
linksenter.merge(links).attr("d", d3.sankeyLinkHorizontal())
links.transition(t)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var nodes = nodeGroup.selectAll('.node')
.data(sankeyData.nodes, function(d){ return d.name });
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
}
function setColor(d) {
switch (d.name) {
case "first":
return "#f00";
case "second":
return "#ff0";
case "third":
return "#f0f";
case "fourth":
return "#0ff";
default:
return "#0f0";
}
}
</script>
</body>
</html>