Keep position of D3 graph after updating data - javascript

I have the following problem. I created a graph in D3. I added a dropdown menu where the nodes of the graph get resized according to the chosen category. To do that I wrote a function that re-runs the javascript code every time I choose an option in the dropdown menu.
However, the updated graph appears at the end of my website. Is there a way to keep the graph at its original position ?
I tried to put it in a div and fix the position, but that didn't work out. I hope my problem is clear.
Find in the following a toy example of my code.
Cheers!
<html lang="en">
<head>
<meta charset="utf-8">
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
<style type="text/css">
</style>
</head>
<body>
<select onchange="change(this)">
<option value="rad1">Option 1</option>
<option value="rad2">Option 2</option>
</select>
<p> This is some text above the plot </p>
<script type="text/javascript">
change('rad1')
function change(dd) {
update(dd.value)
}
function update(rad) {
var current = rad;
d3.selectAll('svg').remove();
var w = 500;
var h = 300;
var dataset = {
nodes: [
{ name:'Node 1',rad1: 1.31, rad2: 2.32 },
{ name:'Node 2',rad1: 2.12, rad2: 5.00 },
{ name:'Node 3',rad1: 40.30, rad2: 20.40 }
],
edges: [
{ source:0, target:0,rad1: 3.31 },
{ source:0, target:1,rad1: 3.31},
{ source:1, target:2,rad1: 20.31}
]
};
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([100])
.charge([-300])
.start();
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#808080")
.style("stroke-width", 1)
.style("stroke-opacity", 0.1);
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", function(d) {
if( current == "rad1") {
return d.rad1;
} else {
return d.rad2;
}
})
.style("fill", function(d, i) {
return '#000000';
})
.call(force.drag)
.on("click", function(d) {
console.log("Name: " + d.name);
});
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
}
</script>
<p> Here is more text below the plot </p>
</body>
</html>

The problem is that you are appending the SVG to the body, thus it will always show at the very end of the page. The correct way is creating a div (or anything else) with an ID (which are unique):
<div id="svghere"></div>
And then appending the SVG:
var svg = d3.select("#svghere")
.append("svg")
.//the rest of the code

Related

Network Graph D3. Different Lengths

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.nodeDetail {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
.text {
font: 12px sans-serif;
pointer-events: none; }
.node {
stroke:#fff;
stroke-width:3px;
fill:#008876;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var graph = {
"nodes":[
{"name":"RFID Scanner","group":1},
{"name":"Chemical 1 Tag ID: 10001 ","group":1},
{"name":"Chemical 2 Tag ID: 10002","group":1},
{"name":"Chemical 3 Tag ID: 10003","group":1},
{"name":"Chemical 4 Tag ID: 10004","group":1}
],
"links":[
{"source":0,"target":1,"value":1 },
{"source":0,"target":2,"value":1},
{"source":0,"target":3,"value":1},
{"source":0,"target":4,"value":1},
{"source":0,"target":0,"value":1}
]
};
var width = 1000,
height = 1000;
var force = d3.layout.force()
.charge(-300)
.linkDistance(100)
.linkStrength(15)
.size([width,height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var drawGraph = function(graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var gnodes = svg.selectAll('g.gnode')
.data(graph.nodes)
.enter()
.append('g')
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("class", "node")
.attr("r", function(d) { return d.name === 'RFID Scanner'? 40 : 20; })
.call(force.drag)
.filter(function(d) { return d.name !== 'RFID Scanner'})
.on("mouseover", function(d)
{
d3.select(labels[0][d.index]).style("visibility","visible")
})
.on("mouseout", function(d)
{
d3.select(labels[0][d.index]).style("visibility","hidden")
})
var labels = gnodes.append("text")
.text(function(d) { return d.name; })
.style("visibility", function(d) { return d.name === 'RFID Scanner'? 'visible' : 'hidden'; });
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; });
gnodes.attr("transform", function(d) {
return 'translate(' + [d.x, d.y] + ')';
});
});
};
drawGraph(graph);
</script>
</body>
</html>
Im trying to make different lengths in the network graph. I went through the API and I think it has something to do w .linkDistance(100). I am not too sure how to go about doing this. but I want to give each node a specific length. I tried going through the links data that I hard coded but it seems pretty limited. Thank you in advance, im curious to see how you do this.
It appears that you are using d3.js version 3.
The documentation about linkDistance states:
if distance is a function, then the function is evaluated for each link (in order), being passed the link and its index, with the this context as the force layout; the function's return value is then used to set each link's distance.
So if each link valueattribute contains the desired distance, instead of:
.linkDistance(100)
You may specify:
.linkDistance(function(d) {return d.value})

Update position of nodes by force simulation, d3 version 4

I am trying to move my nodes (and links) by force in d3 graph. The nodes add dynamically to the graph. I've looked through pile of examples, but they are based on d3 outdated version with force.layout instead of forceSimulation.
I've tried many options and cases and nothing is working, the browser draws circle in the upper left part of svg.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./index.css" type="text/css">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script type="text/javascript" src="graph.js"></script>
</head>
<body>
<div id="graph">
<script>
var graph = new myGraph("#graph");
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");
</script>
</div>
</body>
</html>
graph.js
function myGraph(el) {
var graph = this.graph = {
"nodes":[{"name": "a"}],
"links":[{"source":0,"target":1}]
};
this.addNode = function (name) {
graph["nodes"].push({"name":name});
update();
}
var findNode = function (name) {
for (var i in graph["nodes"]) if (graph["nodes"][i]["name"] === name) return graph["nodes"][i];
}
this.addLink = function (source, target) {
graph["links"].push({"source":findNode(source),"target":findNode(target)});
update();
}
var vis = d3.select(el).append("svg:svg")
.attr("width", 578)
.attr("height", 300);
var nodes = vis.selectAll("circle.node")
.data(graph.nodes);
var links = vis.selectAll("line.link")
.data(graph.links);
var force = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-10))
.force("link", d3.forceLink(links))
.force("center", d3.forceCenter());
var update = function () {
var link = vis.selectAll("line.link")
.data(graph.links);
link.enter().insert("line")
.attr("class", "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; });
link.exit().remove();
var node = vis.selectAll("circle.node")
.data(graph.nodes);
node.enter().append("circle")
.attr("class", "node")
.attr("r", 10)
.attr("x", "-8px")
.attr("y", "-8px");
node.append("text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".25em")
.text(function(d) { return d.name });
node.exit().remove();
force.on("tick", move);
function move() {
links.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"});
};
force.restart();
}
update();
}
Link to jsFiddle with my code
There should be node and link variables (instead of all nodes and links) in move function, but browser produces error in this case:
d3.v4.min.js:4 Error: attribute transform: Expected number, "translate(undefined,undefiā€¦".
Help me, please, to make it running like it should be!

Adding link to d3js force layout cause bad data in nodes

I am working on a d3js side project where I am using the force layout to show nodes and lines to represent a graph. I have laid out the code such that the graph can be dynamically updated. I am doing this by:
Clearing force.nodes() then force.links()
Pushing all the nodes I want to add to force.nodes()
Updating the graph (join, enter, update, exit)
Pushing all the links I want with references to the nodes in force.nodes() to force.links
Updating the graph (join, enter, update, exit)
This works as long as I only have nodes to display, but as soon as I attempt to push a link to force.links then all hell breaks loose and the two nodes end up hiding in the top left corner.
Looking at the DOM I can see the following:
As you can see, the transform/translate parameters contain NaN values. So something blew up in my face but after two days of bug hunting I suspect that I am missing something here and need to take a cold shower.
I have stripped down my code to the smallest set that can reproduce the error. Please note from it that the nodes display fine until a link is pushed into force.links. No links are being drawn, only nodes, but the act of pushing a link where it belongs is disrupting the data in the nodes. This way of updating a graph should work as per the examples I have seen.
d3.json("data/fm.json", function(error, graph) {
if (error) throw error;
function chart(elementName) {
// look for the node in the d3 layout
var findNode = function(name) {
for (var i in nodes) {
if (nodes[i]["name"] === name) return nodes[i];
};
};
var width = 960, // default width
height = 450, // default height
color = d3.scale.category10(),
force = d3.layout.force(),
nodes = force.nodes(),
links = force.links(),
vis,
runOnceFlag = true;
vis = d3.select(elementName)
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("id", "svg")
.attr("pointer-events", "all")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("perserveAspectRatio", "xMinYMid")
.append('svg:g');
var update = function() {
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.name;
});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 12)
.attr("id", function (d) {
return "Node;" + d.name;
})
.attr("class", "nodeStrokeClass")
.attr("fill", function(d) { return color(d.group); });
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function (d) {
return d.name;
});
node.exit().remove();
force.on("tick", function () {
node.attr("transform", function (d) {
console.log(d);
return "translate(" + d.x + "," + d.y + ")";
});
});
// Restart the force layout.
force
.charge(-120)
.linkDistance( function(d) { return d.value * 10 } )
.size([width, height])
.start();
};
var a = graph.nodes[0];
var b = graph.nodes[1]
nodes.push(a);
update();
nodes.push(b);
update();
var c = {"source": findNode('a'), "target": findNode('b')}
// the line below causes the error
links.push(c);
update()
};
///
chart('body');
});
This is my data:
{
"nodes":[
{"name":"a", "group":1},
{"name":"b", "group":2},
{"name":"c", "group":3},
{"name":"d", "group":4},
{"name":"e", "group":5},
{"name":"f", "group":6},
{"name":"g", "group":7},
{"name":"h", "group":1},
{"name":"i", "group":2},
{"name":"j", "group":3},
{"name":"k", "group":4},
{"name":"l", "group":5},
{"name":"m", "group":6},
{"name":"n", "group":7}
],
"links":[
{"source":0,"target":1,"value":1},
{"source":2,"target":3,"value":1},
{"source":4,"target":5,"value":1},
{"source":7,"target":8,"value":1},
{"source":9,"target":10,"value":1},
{"source":11,"target":12,"value":1},
{"source":0,"target":5,"value":1},
{"source":1,"target":5,"value":1},
{"source":0,"target":6,"value":1},
{"source":1,"target":6,"value":1},
{"source":0,"target":7,"value":1},
{"source":1,"target":7,"value":1},
{"source":2,"target":8,"value":1},
{"source":3,"target":8,"value":1},
{"source":2,"target":9,"value":1},
{"source":3,"target":9,"value":1},
{"source":4,"target":11,"value":1},
{"source":5,"target":11,"value":1},
{"source":9,"target":12,"value":1},
{"source":10,"target":12,"value":1},
{"source":11,"target":13,"value":1},
{"source":12,"target":13,"value":1}
]
}
You have some basic problems with your code, see corrected concept below...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.link {
stroke: #2E2E2E;
stroke-width: 2px;
}
.node {
stroke: #fff;
stroke-width: 2px;
}
.textClass {
stroke: #323232;
font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif;
font-weight: normal;
stroke-width: .5;
font-size: 14px;
}
</style>
</head>
<body>
<!--<script src="d3 CB.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
d3.json("data.json", function (error, graph) {
if (error) throw error;
function chart(elementName) {
// look for the node in the d3 layout
var findNode = function (name) {
for (var i in nodes) {
if (nodes[i]["name"] === name) return nodes[i];
}
};
var width = 960, // default width
height = 450, // default height
color = d3.scale.category10(),
nodes = graph.nodes,
links = graph.links,
force = d3.layout.force()
.nodes(nodes)
.links([]),
vis,
runOnceFlag = true;
vis = d3.select(elementName)
.append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("id", "svg")
.attr("pointer-events", "all")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("perserveAspectRatio", "xMinYMid")
.append('svg:g');
var update = function () {
var link = vis.selectAll("line")
.data(force.links(), function (d) {
return d.source + "-" + d.target;
});
link.enter().insert("line", "g")
.attr("id", function (d) {
return d.source + "-" + d.target;
})
.attr("stroke-width", function (d) {
return d.value / 10;
})
.attr("class", "link")
.style("stroke", "red")
.transition().duration(5000).style("stroke", "black");
link.append("title")
.text(function (d) {
return d.value;
});
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.name;
});
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:circle")
.attr("r", 12)
.attr("id", function (d) {
return "Node;" + d.name;
})
.attr("class", "nodeStrokeClass")
.attr("fill", function (d) {
return color(d.group);
});
nodeEnter.append("svg:text")
.attr("class", "textClass")
.attr("x", 14)
.attr("y", ".31em")
.text(function (d) {
return d.name;
});
node.exit().remove();
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) {
console.log(d);
return "translate(" + d.x + "," + d.y + ")";
});
});
// Restart the force layout.
force
.charge(-120)
.linkDistance(function (d) {
return d.value * 100
})
.size([width, height])
.start();
};
update();
var c = {"source": findNode('a'), "target": findNode('b'), value: 1}
// the line below causes the error
window.setTimeout(function() {
force.links().push(c);
update()
},2000)
};
//
chart('body');
});
</script>
</body>
</html>

How to display a text when mouseover a node in D3 force layout

I am trying to display the text when I move the mouse over a node in Force-Directed Graph in D3.js. My problem is that when I move the mouse over any node, all the texts of these nodes are displayed. Here is my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.nodeDetail {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
node .text {
font: 12px sans-serif;
pointer-events: none;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="d3/d3.v3.min.js charset=UTF-8"></script>
<script>
var graph = {
"nodes":[
{"name":"Myriel","group":1},
{"name":"Napoleon","group":1},
{"name":"Mlle.Baptistine","group":1},
{"name":"Mme.Magloire","group":1},
{"name":"CountessdeLo","group":1}
],
"links":[
{"source":1,"target":0,"value":1},
{"source":2,"target":0,"value":8},
{"source":3,"target":0,"value":10},
{"source":3,"target":2,"value":6},
{"source":4,"target":0,"value":1}
]
};
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var drawGraph = function(graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var gnodes = svg.selectAll('g.gnode')//('g.gnode')
.data(graph.nodes)
.enter()
.append('g')
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.on("mouseover", function(d)
{
d3.select(this).transition()
.duration(750)
.attr("r", 15);
return labels.style("visibility", "visible");
})
.on("mouseout", function()
{
d3.select(this).transition()
.duration(750)
.attr("r", 5);
return labels.style("visibility", "hidden");//})
})
.call(force.drag);
var labels = gnodes.append("text")
.text(function(d) { return d.name; })
.style("visibility", "hidden");
/*gnodes.append("text")
.text(function(d) { return d.name; })
.style("visibility", "hidden");*/
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; });
gnodes.attr("transform", function(d) {
return 'translate(' + [d.x, d.y] + ')';
});
});
};
drawGraph(graph);
</script>
</body>
</html>
I tried to do the following codes but it did not work neither:
.on("mouseover", function(d)
{
d3.select(this).transition()
.duration(750)
.attr("r", 15);
d3.select(this).append(text)
.style("visibility", "visible");
})
How can I display the text that is related to a particular node when I move the mouse over that node? Could anyone please help me solve this problem. Thank you in advance.
This should help:
.on("mouseover", function(d)
{
d3.select(labels[0][d.index]).style("visibility","visible")
})
.on("mouseout", function(d)
{
d3.select(labels[0][d.index]).style("visibility","hidden")
})
Learning d3 myself here - also thanks very much to juvian
I find a more transparent way of coding this would be to say
.on("mouseover", function(d) {
d3.select(this).select("text").style("visibility", "visible")
})
which selects the object you hover over (a group), selects the text inside it and changes the style to visible. This also avoids the need to make a labels variable which gets a bit messy with the array indexing.

d3.js - connecting shapes with lines (without using force or other layouts)

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.com/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.com/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...
You 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 become a bit easier and look more like the examples you may have seen for the force layout for example. I would recommend 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.

Categories

Resources