I have a bunch of static circles and I want to connect them with lines (it's a dependency graph). All the examples I see are done with d3's ready-made layouts and I'm not sure how to approach this efficiently. I also want to highlight lines related to a node when I mouse-over that node, as well as fade any other shapes/lines.
This is what I have for now: (it just draws evenly spaced and sized circles according to area size given)
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src=".js"></script>
</head>
<body style="overflow: hidden;">
<div id="drawarea" style="overflow: hidden;"></div>
<script type="text/javascript">
var dataset = [],
i = 0;
for(i=0; i<45; i++){
dataset.push(Math.round(Math.random()*100));
}
var width = 5000,
height = 3000;
var svg = d3.select("#drawarea").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
var div_area = width*height,
num_nodes = dataset.length,
node_area = div_area/num_nodes*0.7,
node_to_padding_ratio = 0.50,
node_dia_inc_pad = Math.sqrt(node_area),
node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio,
node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio),
nodes_in_width = parseInt(width/(node_dia_inc_pad)),
nodes_in_height = parseInt(height/(node_dia_inc_pad));
svg.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.attr("cx", function(d, i){ return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad;})
.attr("cy", function(d, i){ return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad})
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
EDIT: My revised code:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src=".js"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes":[
{"name":"Myriel","group":1},
{"name":"Napoleon","group":1}
],
"links":[
{"source":1,"target":0,"value":1}
]
}
var width = 2000,
height = 1000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
var div_area = width*height,
num_nodes = graph.nodes.length,
node_area = div_area/num_nodes,
node_to_padding_ratio = 0.50,
node_dia_inc_pad = Math.sqrt(node_area),
node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio,
node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio),
nodes_in_width = parseInt(width/(node_dia_inc_pad)),
nodes_in_height = parseInt(height/(node_dia_inc_pad));
var xScale = d3.scale.linear()
.domain([0,nodes_in_width])
.range([node_radius_wo_pad,width-node_radius_wo_pad]);
var yScale = d3.scale.linear()
.domain([0,nodes_in_height])
.range([node_radius_wo_pad,height-node_radius_wo_pad]);
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.links)
.enter().append("line")
.attr("x1", function(d) { return xScale(d.source%nodes_in_width); })
.attr("y1", function(d) { return yScale(parseInt(d.source/nodes_in_width)); })
.attr("x2", function(d) { return xScale(d.target%nodes_in_width); })
.attr("y2", function(d) { return yScale(parseInt(d.target/nodes_in_width)); })
.attr("src", function(d) { return d.source; })
.attr("trgt", function(d) { return d.target; })
.style("stroke", "grey");
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.attr("cx", function(d, i){ return xScale(i%nodes_in_width);})
.attr("cy", function(d, i){ return yScale(parseInt(i/nodes_in_width));})
.attr("index", function(d, i){return i;})
.on("mouseover", function(){
var that = this;
lines.filter(function() {
return d3.select(this).attr("src") == d3.select(that).attr("index");
}).style("stroke", "red");
lines.filter(function() {
return d3.select(this).attr("trgt") == d3.select(that).attr("index");
}).style("stroke", "green");
lines.filter(function() {
return (d3.select(this).attr("trgt") != d3.select(that).attr("index") && d3.select(this).attr("src") != d3.select(that).attr("index"));
}).style("display", "none");
d3.select(this).style("fill", "aliceblue");
})
.on("mouseout", function(){
lines.style("stroke", "grey")
.style("display", "block");
d3.select(this).style("fill", "white");
});
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
What I want to do now is have the circles the lines point to and from be colored similarly. I'm not sure how to make the reference to them from the "mouseover" event of a circle though. Will do some testing...
I have a bunch of static circles and I want to connect them with lines (it's a dependency graph). All the examples I see are done with d3's ready-made layouts and I'm not sure how to approach this efficiently. I also want to highlight lines related to a node when I mouse-over that node, as well as fade any other shapes/lines.
This is what I have for now: (it just draws evenly spaced and sized circles according to area size given)
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github./d3/d3.js"></script>
</head>
<body style="overflow: hidden;">
<div id="drawarea" style="overflow: hidden;"></div>
<script type="text/javascript">
var dataset = [],
i = 0;
for(i=0; i<45; i++){
dataset.push(Math.round(Math.random()*100));
}
var width = 5000,
height = 3000;
var svg = d3.select("#drawarea").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
var div_area = width*height,
num_nodes = dataset.length,
node_area = div_area/num_nodes*0.7,
node_to_padding_ratio = 0.50,
node_dia_inc_pad = Math.sqrt(node_area),
node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio,
node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio),
nodes_in_width = parseInt(width/(node_dia_inc_pad)),
nodes_in_height = parseInt(height/(node_dia_inc_pad));
svg.selectAll("circle")
.data(dataset)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.attr("cx", function(d, i){ return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad;})
.attr("cy", function(d, i){ return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad})
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
EDIT: My revised code:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github./d3/d3.js"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes":[
{"name":"Myriel","group":1},
{"name":"Napoleon","group":1}
],
"links":[
{"source":1,"target":0,"value":1}
]
}
var width = 2000,
height = 1000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom))
.append("g");
var div_area = width*height,
num_nodes = graph.nodes.length,
node_area = div_area/num_nodes,
node_to_padding_ratio = 0.50,
node_dia_inc_pad = Math.sqrt(node_area),
node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio,
node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio),
nodes_in_width = parseInt(width/(node_dia_inc_pad)),
nodes_in_height = parseInt(height/(node_dia_inc_pad));
var xScale = d3.scale.linear()
.domain([0,nodes_in_width])
.range([node_radius_wo_pad,width-node_radius_wo_pad]);
var yScale = d3.scale.linear()
.domain([0,nodes_in_height])
.range([node_radius_wo_pad,height-node_radius_wo_pad]);
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.links)
.enter().append("line")
.attr("x1", function(d) { return xScale(d.source%nodes_in_width); })
.attr("y1", function(d) { return yScale(parseInt(d.source/nodes_in_width)); })
.attr("x2", function(d) { return xScale(d.target%nodes_in_width); })
.attr("y2", function(d) { return yScale(parseInt(d.target/nodes_in_width)); })
.attr("src", function(d) { return d.source; })
.attr("trgt", function(d) { return d.target; })
.style("stroke", "grey");
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.attr("cx", function(d, i){ return xScale(i%nodes_in_width);})
.attr("cy", function(d, i){ return yScale(parseInt(i/nodes_in_width));})
.attr("index", function(d, i){return i;})
.on("mouseover", function(){
var that = this;
lines.filter(function() {
return d3.select(this).attr("src") == d3.select(that).attr("index");
}).style("stroke", "red");
lines.filter(function() {
return d3.select(this).attr("trgt") == d3.select(that).attr("index");
}).style("stroke", "green");
lines.filter(function() {
return (d3.select(this).attr("trgt") != d3.select(that).attr("index") && d3.select(this).attr("src") != d3.select(that).attr("index"));
}).style("display", "none");
d3.select(this).style("fill", "aliceblue");
})
.on("mouseout", function(){
lines.style("stroke", "grey")
.style("display", "block");
d3.select(this).style("fill", "white");
});
function zoom() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
</body>
</html>
What I want to do now is have the circles the lines point to and from be colored similarly. I'm not sure how to make the reference to them from the "mouseover" event of a circle though. Will do some testing...
Share Improve this question edited Feb 3, 2014 at 2:39 Mosho asked Feb 2, 2014 at 13:34 MoshoMosho 7,0783 gold badges35 silver badges52 bronze badges1 Answer
Reset to default 5You haven't specified how your nodes are connected, so I'm assuming that everything is connected to everything. The principle is the same as for any other layout -- you take the data you have that determines the links and pass it to .data()
. In your code, the coordinates aren't part of the data, which makes it a bit more verbose, but still quite straightforward.
To add the links, I'm using a nested selection -- I'm adding a g
element for each node and underneath the connections to all the other nodes.
var lines = svg.selectAll("g.line").data(dataset)
.enter().append("g").attr("class", "line")
.selectAll("line").data(dataset)
.enter().append("line")
.attr("x1", function(d, i) { return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad; })
.attr("y1", function(d, i) { return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad; })
.attr("x2", function(d, i, j) { return 2*node_radius_wo_pad+j%nodes_in_width*node_dia_inc_pad; })
.attr("y2", function(d, i, j) { return 2*node_radius_wo_pad+(parseInt(j/nodes_in_width))*node_dia_inc_pad; });
This adds a line for every pair of nodes. Note that it will add links between the same nodes (which you won't be able to see) and 2 links between each pair of nodes -- once starting at one node and once at the other. I haven't filtered out these cases here to keep the code simple. In your particular application, I'm guessing that the connections are determined in another way anyway.
To highlight the links that are connected a particular node on highlight, I'm using the links
variable that contains all of them and filtering out the ones whose start coordinates are different from the coordinates of the circle. The filtered selection is then painted red.
.on("mouseover", function(){
var that = this;
lines.filter(function() {
return d3.select(this).attr("x1") == d3.select(that).attr("cx") && d3.select(this).attr("y1") == d3.select(that).attr("cy");
}).style("stroke", "red");
d3.select(this).style("fill", "aliceblue");
})
If the coordinates are part of the data, everything will bee a bit easier and look more like the examples you may have seen for the force layout for example. I would remend to create a data structure much like what's used there for your links, with source
and target
attributes that determine the source and target nodes.
Complete example here.