Mouseover any of the nodes in this network to see the incoming links (dependants) in green and the outgoing links (dependencies) in red.
Compare to the static version.
forked from mbostock's block: Hierarchical Edge Bundling
xxxxxxxxxx
<meta charset="utf-8">
<style>
.someText {
font: 300 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #fff;
}
.node {
font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: #bbb;
}
.node:hover {
fill: #000;
}
.link {
stroke: steelblue;
stroke-opacity: 0.4;
fill: none;
pointer-events: none;
}
.node:hover,
.node--source,
.node--target {
font-weight: 700;
}
.node--source {
fill: #2ca02c;
}
.node--target {
fill: #d62728;
}
.link--source,
.link--target {
stroke-opacity: 1;
stroke-width: 2px;
}
.link--source {
stroke: #d62728;
}
.link--target {
stroke: #2ca02c;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var diameter = 1200,
radius = diameter / 2,
innerRadius = radius - 380;
var cluster = d3.cluster()
.size([360, innerRadius]);
var line = d3.radialLine()
.curve(d3.curveBundle.beta(0.85))
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")");
var link = svg.append("g").selectAll(".link"),
node = svg.append("g").selectAll(".node");
d3.json("https://pdmsdemo.co.uk/?venueCode=EAT", function(error, recordset) {
// console.log(data);
classes = JSON.parse(recordset[0]["JSON_F52E2B61-18A1-11d1-B105-00805F49916B"])
// grab the data and parse as JSON object
// d3.json("milestones", function(error, classes) {
if (error) throw error;
var root = packageHierarchy(classes)
.sum(function(d) {
return d.size;
});
var nodes = cluster(root);
link = link
.data(packageImports(root.leaves()))
.enter().append("path")
.each(function(d) {
d.source = d[0], d.target = d[d.length - 1];
})
.attr("class", "link")
.attr("d", line);
var groupData = svg.selectAll("g.group")
.data(root.descendants().filter(function(d) {
return d.depth == 1;
}))
.enter().append("group")
.attr("class", "group");
// swap the start and end angles here on the arc for anything between 90 and 180
var groupArc = d3.arc()
.innerRadius(innerRadius + 15)
.outerRadius(innerRadius + 30)
.startAngle(
function(d) {
return (findStartAngle(d.__data__.descendants()) - 2) * Math.PI / 180;
}
)
.endAngle(
function(d) {
return (findEndAngle(d.__data__.descendants()) + 2) * Math.PI / 180;
});
var arc_group = svg.selectAll("g.arc")
.data(groupData._groups[0])
.enter().append("svg:path")
.attr("d", groupArc)
.attr("class", "groupArc")
.style("fill", "#1f77b4")
.style("fill-opacity", 0.5)
.each(function(d, i) {
//Search pattern for everything between the start and the first capital L
var firstArcSection = /(^.+?)L/;
//Grab everything up to the first Line statement
var newArc = firstArcSection.exec(d3.select(this).attr("d"))[1];
//Replace all the commas so that IE can handle it
newArc = newArc.replace(/,/g, " ");
//If the start angle lies between 90 and 270
//flip the end and start position
var ea = groupArc.startAngle()(d) * 180 / Math.PI;
if (ea > 90 && ea < 270) {
// if(true){
var startLoc = /M(.*?)A/, //Everything between the first capital M and first capital A
middleLoc = /A(.*?)0 0 1/, //Everything between the first capital A and 0 0 1
endLoc = /0 0 1 (.*?)$/; //Everything between the first 0 0 1 and the end of the string (denoted by $);
//Flip the direction of the arc by switching the start en end point (and sweep flag)
//of those elements that are below the horizontal line
var newStart = endLoc.exec(newArc)[1];
var newEnd = startLoc.exec(newArc)[1];
var middleSec = middleLoc.exec(newArc)[1];
//Build up the new arc notation, set the sweep-flag to 0
newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
} //if
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenDonutArcs")
.attr("id", d.__data__.data.name)
.attr("d", newArc)
.style("fill", "none");
});
//Append the label names on the outside
svg.selectAll(".someText")
.data(groupData._groups[0])
.enter().append("text")
.attr("class", "someText")
.attr("dy", function(d, i) {
var ea = groupArc.startAngle()(d) * 180 / Math.PI;
if (ea > 90 && ea < 270) return -4
else return 12;
})
.append("textPath")
.attr("startOffset", "50%")
.style("text-anchor", "middle")
.attr("xlink:href", function(d, i) {
return "#" + d.__data__.data.name;
})
.text(function(d) {
return d.__data__.data.children[0]['heading'];
});
node = node.data(root.leaves())
.enter()
.append("g")
.attr("class", "node");
node
.append("text")
.attr("id", function(d) {
return "node-" + d.data.name;
})
.attr("dy", "0.31em")
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 35) + ",0)" + (d.x < 180 ? "" : "rotate(180)");
})
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.text(function(d) {
return d.data.description;
})
.on("mouseover", mouseovered)
.on("mouseout", mouseouted)
node.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 5)
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)");
})
.style("fill", function(d) {
return d.data.rag
});
});
function mouseovered(d) {
node
.each(function(n) {
n.target = n.source = false;
});
link
.classed("link--target", function(l) {
if (l.target === d) return l.source.source = true;
})
.classed("link--source", function(l) {
if (l.source === d) return l.target.target = true;
})
.filter(function(l) {
return l.target === d || l.source === d;
})
.raise();
node
.classed("node--target", function(n) {
return n.target;
})
.classed("node--source", function(n) {
return n.source;
});
}
function mouseouted(d) {
link
.classed("link--target", false)
.classed("link--source", false);
node
.classed("node--target", false)
.classed("node--source", false);
}
function packageHierarchy(classes) {
var map = {};
function find(name, data) {
var node = map[name],
i;
if (!node) {
node = map[name] = data || {
name: name,
heading: '',
children: []
};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
if (!node.parent.heading.length) {
node.parent.heading = node.heading
};
// propagate the heading up the hierarchy if there isn't one.
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return d3.hierarchy(map[""]);
}
// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.data.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.data.imports) d.data.imports.forEach(function(i) {
imports.push(map[d.data.name].path(map[i]));
});
});
return imports;
}
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d) {
if (d.x < min)
min = d.x;
});
return min;
}
function findEndAngle(children) {
var max = children[0].x;
children.forEach(function(d) {
if (d.x > max)
max = d.x;
});
return max;
}
</script>
https://d3js.org/d3.v4.min.js