Reputation: 474
I am building a directed graph where on hover
event I want to highlight the concerned node, links and neighbors. Everything goes fine until I add arrows to the links.
I cannot workout how to highlight the arrows as well as the links.
Any help is welcome.
jsfiddle: https://jsfiddle.net/dkroaefw/3/
code:
graph = {
"nodes": [{
"name": "a"
}, {
"name": "b"
}, {
"name": "c"
}, {
"name": "d"
}, {
"name": "e"
}, {
"name": "f"
}, {
"name": "g"
}],
"links": [{
"source": 0,
"target": 1,
"value" :1
}, {
"source": 1,
"target": 2,
"value" :1
}, {
"source": 1,
"target": 3,
"value" :1
}, {
"source": 0,
"target": 4,
"value" :1
}, {
"source": 4,
"target": 5,
"value" :1
}, {
"source": 4,
"target": 6,
"value" :1
}]
}
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source + "," + d.target] = true;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
function hasConnections(a) {
for (var property in linkedByIndex) {
s = property.split(",");
if ((s[0] == a.index || s[1] == a.index) && linkedByIndex[property])
return true;
}
return false;
}
var width = 500
height = 400
var force = d3.layout.force()
.nodes(graph.nodes)
.links(graph.links)
.charge(-50)
.linkDistance(50)
.size([width, height])
.start();
// Zoom definition
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var svg = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
var container = svg.append("g");
// build the arrow.
var arrow = container.append("svg:defs")
.selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("class", "arrow")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = container.append("g")
.attr("class", "links")
.selectAll(".links")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.call(force.drag);
node.append("circle")
.attr("r", 5);
node.append("text")
.attr("class", "text")
.attr("x", 12)
.attr("dy", ".35em")
.text('')
force.on("tick", function(f) {
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 + ")";
});
});
function mouseover(d) {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
set_highlight(d);
}
function mouseout(d) {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 5);
exit_highlight(d)
}
function set_highlight(d) {
node.select("text").text(function(o) {
return isConnected(d, o) ? o.name : "";
})
node.attr("class", function(o) {
return isConnected(d, o) ? "node-active" : "node";
});
link.attr("class", function(o) {
return o.source.index == d.index || o.target.index == d.index ? "link-active" : "link";
});
}
function exit_highlight(d) {
node.attr("class", "node");
link.attr("class", "link");
node.select("text").text("")
}
Upvotes: 0
Views: 1157
Reputation: 415
So there are 2 things that you need to take into account imho:
So basically - check out this fiddle to see the full changes:
var arrows = defs
.selectAll("marker")
// let's add two markers: one for unhovered links and one for hovered links.
.data(["end", "end-active"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
//define the classes for each of the markers.
defs.select("#end").attr("class", "arrow");
defs.select("#end-active").attr("class", "arrow-active");
//...
// then further down in function set_highlight and exit highlight:
function set_highlight(d) {
node.select("text").text(function(o) {
return isConnected(d, o) ? o.name : "";
})
node.attr("class", function(o) {
return isConnected(d, o) ? "node-active" : "node";
});
link.attr("marker-end", function(o) {
return o.source.index == d.index || o.target.index == d.index ? "url(#end-active)" : "url(#end)";
});
link.attr("class", function(o) {
return o.source.index == d.index || o.target.index == d.index ? "link-active" : "link";
});
}
function exit_highlight(d) {
node.attr("class", "node");
link.attr("class", "link");
link.attr("marker-end", "url(#end)");
node.select("text").text("")
}
EDIT: if you want to reuse some of the code you could add this:
// inside the set_highlight function
link.attr("marker-end", function(o) {
return isLinkForNode(d, o) ? "url(#end-active)" : "url(#end)";
});
link.attr("class", function(o) {
return isLinkForNode(d, o) ? "link-active" : "link";
});
function isLinkForNode(node, link) {
return link.source.index == node.index || link.target.index == node.index;
}
Upvotes: 3