Adding and removing nodes and links from force diagram in d3 based on
filter dropdown
I'm trying to make a force diagram, with a couple of drop down boxes which
filter the data on display. The first one (which is where I'm almost at
now) checks for the type, and only shows nodes & links which have a source
or target matching the type.
What I have now, is the ability to select the filter, and the graph
updates, it removes unnecessary nodes, and reformats the remaining ones to
be correct. But it only works the first time. If I 're-filter' it starts
to go haywire.
Here's my full code, I'm very new to javascript (&d3), and I've been
unashamedly stealing from bl.ocks.org, so please feel free to answer in
'noob'. Thanks in advance.
Also, I've put this on a jsfiddle: http://jsfiddle.net/J85Vu/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Enterprise Collaboration Map</title>
<script type="text/javascript" src="d3.v3.js"></script>
<script type="text/javascript" src="jquery-1.10.2.min.js"></script>
<style type="text/css">
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
marker#a {
fill: green;
}
path.link.a {
stroke: green;
}
circle.a {
fill: green;
stroke: #333;
stroke-width: 1.5px;
}
marker#b {
fill: blue;
}
path.link.b {
stroke: blue;
}
circle.b {
fill: blue;
stroke: #333;
stroke-width: 1.5px;
}
marker#c {
fill: orange;
}
path.link.c {
stroke: orange;
}
circle.c {
fill: orange;
stroke: #333;
stroke-width: 1.5px;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
</style>
</head>
<body>
<select class="BU">
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<script type="text/javascript">
var links = [
{source:"one",target:"two", type:"a", typeKBP:"a"},
{source:"two",target:"three", type:"a", typeKBP:"a"},
{source:"three",target:"four", type:"a", typeKBP:"a"},
{source:"four",target:"five", type:"a", typeKBP:"b"},
{source:"five",target:"six", type:"b", typeKBP:"b"},
{source:"six",target:"seven", type:"b", typeKBP:"b"},
{source:"seven",target:"eight", type:"b", typeKBP:"b"},
{source:"eight",target:"nine", type:"b", typeKBP:"c"},
{source:"nine",target:"ten", type:"c", typeKBP:"c"},
{source:"ten",target:"one", type:"c", typeKBP:"a"},
{source:"one",target:"three", type:"a", typeKBP:"a"},
{source:"two",target:"four", type:"a", typeKBP:"a"},
{source:"three",target:"five", type:"a", typeKBP:"b"},
{source:"four",target:"six", type:"a", typeKBP:"b"},
{source:"five",target:"seven", type:"b", typeKBP:"b"},
{source:"six",target:"eight", type:"b", typeKBP:"b"},
{source:"seven",target:"nine", type:"b", typeKBP:"c"},
{source:"eight",target:"ten", type:"b", typeKBP:"c"},
{source:"nine",target:"one", type:"c", typeKBP:"a"},
{source:"ten",target:"two", type:"c", typeKBP:"a"},
{source:"one",target:"four", type:"a", typeKBP:"a"},
{source:"two",target:"five", type:"a", typeKBP:"b"},
{source:"three",target:"six", type:"a", typeKBP:"b"},
{source:"four",target:"seven", type:"a", typeKBP:"b"},
{source:"five",target:"eight", type:"b", typeKBP:"b"},
{source:"six",target:"nine", type:"b", typeKBP:"c"},
{source:"seven",target:"ten", type:"b", typeKBP:"c"},
{source:"eight",target:"one", type:"b", typeKBP:"a"},
{source:"nine",target:"two", type:"c", typeKBP:"a"},
{source:"ten",target:"three", type:"c", typeKBP:"a"}
];
var inputlinks=[];
var nodes = {};
inputlinks.push(links);
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name:
link.source, type:link.type});
link.target = nodes[link.target] || (nodes[link.target] = {name:
link.target, type:link.typeKBP});
});
var w = 1024,
h = 800;
//setup initial force layout
var force = d3.layout.force()
.gravity(0.4)
.size([w, h])
.nodes(d3.values(nodes))
.links(links)
.linkDistance(100)
.charge(-1000)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
// Per-type markers, as they don't inherit styles.
svg.append("svg:defs").selectAll("marker")
.data(["a","b","c"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type +
")"; });
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.attr("class", function(d) { return d.type; })
.call(force.drag);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.text(function(d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class","write")
.text(function(d) { return d.name; });
//jQuery update parts for drop downs.
$(document).ready(function(){
$('.BU').on('change',function(){
curBU=$('.BU').val();
//alert('The selected BU is ' + curBU);
//Filter links and rebuild nodes based on this.
minLinks={};
minLinks=links.filter(function(d){
if ((d.type==curBU) || (d.typeKBP==curBU)) {
return d
}
})
//new nodes
nodes2={};
nodes2=force.nodes().filter(function(d){return
d3.keys(minLinks.filter(function(e){return
e.source.name==d.name ||
e.target.name==d.name;})).length>0});
// minLinks.forEach(function(d) {
// d.source = nodes2[d.source] || (nodes2[d.source] =
{name: d.source, type:d.type});
// d.target = nodes2[d.target] || (nodes2[d.target] =
{name: d.target, type:d.typeKBP});
// });
force
.nodes(nodes2)
.links(minLinks)
.start();
//circle.remove();
newCirc=circle.data(force.nodes());
newCirc.enter().append("svg:circle")
.attr("r", 6)
.attr("class", function(d) { return d.type; })
.call(force.drag);
newCirc.exit().remove();
newPath=path.data(force.links());
newPath
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" +
d.type + ")"; });
newPath.exit().remove();
newText=text.data(force.nodes());
newText.exit().remove();
newText.select(".shadow").text(function(d){return d.name;});
newText.select(".write").text(function(d){return d.name;});
});
});
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt((dx * dx)/2 + (dy * dy));
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," +
dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
</script>
</body>
</html>
No comments:
Post a Comment