Fade Links And Nodes That Are Not Immediately Connected To The Node Hovered On In A D3 Graph
Solution 1:
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; });
Solution 2:
Addition to @TomShanley answer
Why are you using d3v3 if you are new to d3? Currently we are at d3v5 and the API has much been improved.
The program does not run out of the box because in determining linkedByIndex
it complains that links
does not exist. It should be json.links
.
There is no need to put break
after a return
in linkColor
.
You search for elements of class svg.selectAll(".nodes")
but you create elements with .attr("class", "node")
. This will not work if you want to use the enter-exit-update properly. The same with the links: search for class links
but add elements with class lol
.
Your markers are not unique and no need to use each
to add the 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
. And an id
in HTML should be unique.
// 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:
- unique markers for each color put in the
defs
tag of the svg. Create a new marker if not already done for this color using an object to keep track. - links are now paths to apply a marker trick described by Gerardo
- images are now centered on the node position, this only works for circular images.
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;
});
};
}
});
Post a Comment for "Fade Links And Nodes That Are Not Immediately Connected To The Node Hovered On In A D3 Graph"