[英]Fade links and nodes that are not immediately connected to the node hovered on in a d3 graph
I am now to d3 and web development in general. 我现在正在进行d3和web开发。
I am creating a graph using the d3 library. 我正在使用d3库创建一个图形。 I am trying to ensure that whenever the user hovers upon a node, the opacity of its immediate parents and children should remain the same but the opacity of the rest of the nodes should decrease.
我试图确保每当用户在节点上盘旋时,其直接父节点和子节点的不透明度应该保持不变,但其余节点的不透明度应该降低。
I am partly achieving my goal by letting the text written below fade for all the ones other than the one I hover on. 我部分实现了我的目标,让下面写的文字在我悬停的所有文本之外消失。
Here is my javascript code: 这是我的javascript代码:
// setting up the canvas size :)
var width = 960,
height = 500;
// initialization
var svg = d3.select("div").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "blueLine"); // the graph invisible thing :)
var force = d3.layout.force()
.gravity(0) // atom's cohesiveness / elasticity of imgs :)
.distance(150) // how far the lines ---> arrows :)
.charge(-50) // meta state transition excitement
.linkDistance(140)
//.friction(0.55) // similar to charge for quick reset :)
.size([width, height]); // degree of freedom to the canvas
// exception handling
d3.json("graph.json", function(error, json) {
if (error) throw error;
// Restart the force layout
force
.nodes(json.nodes)
.links(json.links)
.start();
// Build the link
var link = svg.selectAll(".links")
.data(json.links)
.enter().append("line")
.attr("class", "lol")
.style("stroke-width", "2")
.attr("stroke", function(d){
return linkColor(d.colorCode);})
.each(function(d) {
var color = linkColor(d.colorCode);
d3.select(this).attr("marker-end", marker(color));
});
function marker(color) {
svg.append("svg:marker")
.attr("id", color.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
return "url(" + color + ")";
};
// this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3
// create a node
var node = svg.selectAll(".nodes")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag)
.on("mouseover", fade(.2))
.on("mouseout", fade(1));;
// Define the div for the tooltip
var div = d3.select("body").append("pre")
.attr("class", "tooltip")
.style("opacity", 0);
// Append custom images
node.append("svg:image")
.attr("xlink:href", function(d) { return d.img;}) // update the node with the image
.attr("x", function(d) { return -5;}) // how far is the image from the link??
.attr("y", function(d) { return -25;}) // --- same ---
.attr("height", 55) // size
.attr("width", 55);
node.append("text")
.attr("class", "labelText")
.attr("x", function(d) { return -5;})
.attr("y", function(d) { return 48;})
.text(function(d) { return d.name });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
force.stop();
});
function linkColor(linkCode) {
switch (linkCode)
{
case 'ctoc':
return '#0000FF';//blue
break;
case 'ctof':
return '#00afaa';//green
break;
case 'ftoc':
return '#fab800';//yellow
break;
case 'ftof':
return '#7F007F';//purple
break;
default:
return '#0950D0';//generic blue
break;
}
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.id + "," + d.target.id] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
// fade nodes on hover
function fade(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
// also style link accordingly
link.style("stroke-opacity", function(o) {
return o[0] === d || o[2] === d ? 1 : opacity;
});
};
}
});
The css: css:
.node text {
font-size: 1rem;
text-decoration: underline;
fill: #aeb4bf;
font-weight: 700;
text-anchor: end;
alignment-baseline: central;
pointer-events: none;
}
.node:not(:hover) .nodetext {
display: none;
}
pre.tooltip {
position: absolute;
text-align: left;
width: auto;
height: auto;
padding: 5px;
font: 14px "Helvetica","Arial",sans-serif bold;
background: #273142;
border: 0;
border-radius: 8px;
cursor: pointer!important;
pointer-events: none;
color: #aeb4bf;
}
and my json file: 和我的json文件:
{
"nodes": [
{"x": 100, "y": 100, "name": "A", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 0},
{"x": 250, "y": 100, "name": "B", "img":"https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 1},
{"x": 400, "y": 100, "name": "C", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id": 2},
{"x": 550, "y": 200, "name": "D", "img":"https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 3},
{"x": 700, "y": 200, "name": "E", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 4},
{"x": 100, "y": 300, "name": "F", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 5},
{"x": 250, "y": 300, "name": "G", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id" : 6},
{"x": 400, "y": 300, "name": "H", "img": "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png", "id": 7}
],
"links": [
{"source": 0, "target": 1, "colorCode" : "ctof"},
{"source": 1, "target": 2, "colorCode" : "ftoc"},
{"source": 2, "target": 3, "colorCode" : "ctof"},
{"source": 3, "target": 4, "colorCode" : "ftoc"},
{"source": 5, "target": 6, "colorCode" : "ctof"},
{"source": 6, "target": 7, "colorCode" : "ftoc"},
{"source": 7, "target": 3, "colorCode" : "ctof"}
]
}
I dont know where I am going wrong. 我不知道我哪里错了。 I need to achieve two things: 1. The immediate parents and children of X should stay unfaded if I hover over X and 2. The other nodes which aren't directly related to X should fade just the way the other links do.
我需要实现两件事:1。当我将鼠标悬停在X和2上时,X的直接父母和孩子应该保持不变。与X没有直接关系的其他节点应该像其他链接一样消失。 Currently none of the node fades.
目前,所有节点都没有消失。
I researched over my code and realized that it says that all the nodes are connected to each other so my isConnected()
is the culprit. 我研究了我的代码并意识到它说所有节点都相互连接,所以我的
isConnected()
是罪魁祸首。 I still have no clue about the links though. 我仍然不知道这些链接。
Please help me. 请帮我。
two issues to resolve 要解决的两个问题
For your nodes, as they are image files, you need set their 'opacity', and not the stroke/fill opacity. 对于您的节点,因为它们是图像文件,您需要设置它们的“不透明度”,而不是笔画/填充不透明度。
node.style("opacity", function(o) { thisOpacity = isConnected(d, o) ? 1 : opacity; return thisOpacity; });
For your links, assuming the name attributes are unique, you should match the link's source and target to the chosen node's name. 对于您的链接,假设名称属性是唯一的,您应该将链接的源和目标与所选节点的名称相匹配。
link.style("stroke-opacity", function(o) { return o.source.name === d.name || o.target.name === d.name ? 1 : opacity; });
Addition to @TomShanley answer 除了@TomShanley的回答
Why are you using d3v3 if you are new to d3? 如果你是d3的新手,为什么还要使用d3v3? Currently we are at d3v5 and the API has much been improved.
目前我们处于d3v5并且API得到了很大改进。
The program does not run out of the box because in determining linkedByIndex
it complains that links
does not exist. 该程序没有开箱即用,因为在确定
linkedByIndex
它抱怨links
不存在。 It should be json.links
. 它应该是
json.links
。
There is no need to put break
after a return
in linkColor
. 在
linkColor
return
后无需break
。
You search for elements of class svg.selectAll(".nodes")
but you create elements with .attr("class", "node")
. 您搜索类
svg.selectAll(".nodes")
元素,但您使用.attr("class", "node")
创建元素。 This will not work if you want to use the enter-exit-update properly. 如果要正确使用enter-exit-update,这将不起作用。 The same with the links: search for class
links
but add elements with class lol
. 与链接相同:搜索类
links
但添加类lol
元素。
Your markers are not unique and no need to use each
to add the marker-end
. 您的标记不是唯一的,无需使用
each
marker-end
添加marker-end
。 Maybe best to create a set of markers based on color and just reference them. 也许最好根据颜色创建一组标记,然后引用它们。 In the original code you have multiple tags with the same
id
. 在原始代码中,您有多个具有相同
id
标记。 And an id
in HTML should be unique. HTML中的
id
应该是唯一的。
// Build the link
var link = svg.selectAll(".lol")
.data(json.links)
.enter().append("line")
.attr("class", "lol")
.style("stroke-width", "2")
.attr("stroke", function(d){
return linkColor(d.colorCode);})
.attr("marker-end", function(d, i){
return marker(i, linkColor(d.colorCode));} );
function marker(i, color) {
var markId = "#marker"+i;
svg.append("svg:marker")
.attr("id", markId.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
return "url(" + markId + ")";
};
Edit Unique markers, link is path from edge to edge 编辑唯一标记,链接是从边到边的路径
I have modified the code to have: 我已经修改了代码:
defs
tag of the svg. defs
标签中。 Create a new marker if not already done for this color using an object to keep track. Here is the full code 这是完整的代码
var width = 960,
height = 500;
// initialization
var svg = d3.select("div").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "blueLine"); // the graph invisible thing :)
var svgDefs = svg.append("defs");
var force = d3.layout.force()
.gravity(0) // atom's cohesiveness / elasticity of imgs :)
.distance(150) // how far the lines ---> arrows :)
.charge(-50) // meta state transition excitement
.linkDistance(140)
//.friction(0.55) // similar to charge for quick reset :)
.size([width, height]); // degree of freedom to the canvas
// exception handling
d3.json("/fade-links.json", function(error, json) {
if (error) throw error;
var imageSize = { width:55, height:55 };
// Restart the force layout
force
.nodes(json.nodes)
.links(json.links)
.start();
var markersDone = {};
// Build the link
var link = svg.selectAll(".lol")
.data(json.links)
.enter().append("path")
.attr("class", "lol")
.style("stroke-width", "2")
.attr("stroke", function(d){
return linkColor(d.colorCode);})
.attr("marker-end", function(d){
return marker(linkColor(d.colorCode));} );
function marker(color) {
var markerId = markersDone[color];
if (!markerId) {
markerId = color;
markersDone[color] = markerId;
svgDefs.append("svg:marker")
.attr("id", color.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 15)
.attr("markerHeight", 15)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
}
return "url(" + markerId + ")";
};
// this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3
// create a node
var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag)
.on("mouseover", fade(.2))
.on("mouseout", fade(1));
// Define the div for the tooltip
var div = d3.select("body").append("pre")
.attr("class", "tooltip")
.style("opacity", 0);
// Append custom images
node.append("svg:image")
.attr("xlink:href", function(d) { return d.img;}) // update the node with the image
.attr("x", function(d) { return -imageSize.width*0.5;}) // how far is the image from the link??
.attr("y", function(d) { return -imageSize.height*0.5;}) // --- same ---
.attr("height", imageSize.width)
.attr("width", imageSize.height);
node.append("text")
.attr("class", "labelText")
.attr("x", function(d) { return 0;})
.attr("y", function(d) { return imageSize.height*0.75;})
.text(function(d) { return d.name });
force.on("tick", function() {
// use trick described by Gerardo to only draw link from image border to border: https://stackoverflow.com/q/51399062/9938317
link.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
var angle = Math.atan2(dy, dx);
var radius = imageSize.width*0.5;
var offsetX = radius * Math.cos(angle);
var offsetY = radius * Math.sin(angle);
return ( `M${d.source.x + offsetX},${d.source.y + offsetY}L${d.target.x - offsetX},${d.target.y - offsetY}`);
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
force.stop();
});
function linkColor(linkCode) {
switch (linkCode)
{
case 'ctoc': return '#0000FF';//blue
case 'ctof': return '#00afaa';//green
case 'ftoc': return '#fab800';//yellow
case 'ftof': return '#7F007F';//purple
}
return '#0950D0';//generic blue
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
json.links.forEach(function(d) {
linkedByIndex[d.source.id + "," + d.target.id] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
// fade nodes on hover
function fade(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("opacity", function(o) {
return isConnected(d, o) ? 1 : opacity;
});
// also style link accordingly
link.style("opacity", function(o) {
return o.source.name === d.name || o.target.name === d.name ? 1 : opacity;
});
};
}
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.