D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
slattery
Full window
Github gist
Hierarchical edge bundling with dynamically generated arcs for each group
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <link type="text/css" rel="stylesheet" href="style.css"/> <style type="text/css"> path.arc { cursor: move; fill: #fff; } .node { font-size: 10px; } .node:hover { fill: #1f77b4; } .link { fill: none; stroke: #1f77b4; stroke-opacity: .4; pointer-events: none; } .link.source, .link.target { stroke-opacity: 1; stroke-width: 2px; } .node.target { fill: #d62728 !important; } .link.source { stroke: #d62728; } .node.source { fill: #2ca02c; } .link.target { stroke: #2ca02c; } </style> </head> <body> <h2> Flare imports<br> hierarchical edge bundling </h2> <div id="bundle"></div> <div style="position:absolute;bottom:0;font-size:18px;">tension: <input style="position:relative;top:3px;" type="range" min="0" max="100" value="85"></div> <script type="text/javascript" src="//code.jquery.com/jquery-1.12.3.min.js"></script> <script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script> <script type="text/javascript" src="packages.js"></script> <script type="text/javascript"> var w = 740, h = 700, rx = w / 2, ry = h / 2, m0, rotate = 0 pi = Math.PI; var splines = []; var cluster = d3.layout.cluster() .size([360, ry - 180]) .sort(function(a, b) { return d3.ascending(a.key, b.key); }); var bundle = d3.layout.bundle(); var line = d3.svg.line.radial() .interpolate("bundle") .tension(.85) .radius(function(d) { return d.y; }) .angle(function(d) { return d.x / 180 * Math.PI; }); // Chrome 15 bug: <https://code.google.com/p/chromium/issues/detail?id=98951> var div = d3.select("#bundle") .style("width", w + "px") .style("height", w + "px") .style("position", "absolute"); var svg = div.append("svg:svg") .attr("width", w) .attr("height", w) .append("svg:g") .attr("transform", "translate(" + rx + "," + ry + ")"); svg.append("svg:path") .attr("class", "arc") .attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI)) .on("mousedown", mousedown); d3.json("flare-imports.json", function(classes) { var nodes = cluster.nodes(packages.root(classes)), links = packages.imports(nodes), splines = bundle(links); var path = svg.selectAll("path.link") .data(links) .enter().append("svg:path") .attr("class", function(d) { return "link source-" + d.source.key + " target-" + d.target.key; }) .attr("d", function(d, i) { return line(splines[i]); }); var groupData = svg.selectAll("g.group") .data(nodes.filter(function(d) { return (d.key=='Jobs' || d.key == 'Freelance' || d.key == 'Bayard') && d.children; })) .enter().append("group") .attr("class", "group"); var groupArc = d3.svg.arc() .innerRadius(ry - 177) .outerRadius(ry - 157) .startAngle(function(d) { return (findStartAngle(d.__data__.children)-2) * pi / 180;}) .endAngle(function(d) { return (findEndAngle(d.__data__.children)+2) * pi / 180}); svg.selectAll("g.arc") .data(groupData[0]) .enter().append("svg:path") .attr("d", groupArc) .attr("class", "groupArc") .style("fill", "#1f77b4") .style("fill-opacity", 0.5); svg.selectAll("g.node") .data(nodes.filter(function(n) { return !n.children; })) .enter().append("svg:g") .attr("class", "node") .attr("id", function(d) { return "node-" + d.key; }) .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) .append("svg:text") .attr("dx", function(d) { return d.x < 180 ? 25 : -25; }) .attr("dy", ".31em") .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; }) .text(function(d) { return d.key.replace(/_/g, ' '); }) .on("mouseover", mouseover) .on("mouseout", mouseout); d3.select("input[type=range]").on("change", function() { line.tension(this.value / 100); path.attr("d", function(d, i) { return line(splines[i]); }); }); }); d3.select(window) .on("mousemove", mousemove) .on("mouseup", mouseup); function mouse(e) { return [e.pageX - rx, e.pageY - ry]; } function mousedown() { m0 = mouse(d3.event); d3.event.preventDefault(); } function mousemove() { if (m0) { var m1 = mouse(d3.event), dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI; div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)"); } } function mouseup() { if (m0) { var m1 = mouse(d3.event), dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI; rotate += dm; if (rotate > 360) rotate -= 360; else if (rotate < 0) rotate += 360; m0 = null; div.style("-webkit-transform", "rotate3d(0,0,0,0deg)"); svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")") .selectAll("g.node text") .attr("dx", function(d) { return (d.x + rotate) % 360 < 180 ? 25 : -25; }) .attr("text-anchor", function(d) { return (d.x + rotate) % 360 < 180 ? "start" : "end"; }) .attr("transform", function(d) { return (d.x + rotate) % 360 < 180 ? null : "rotate(180)"; }); } } function mouseover(d) { svg.selectAll("path.link.target-" + d.key) .classed("target", true) .each(updateNodes("source", true)); svg.selectAll("path.link.source-" + d.key) .classed("source", true) .each(updateNodes("target", true)); } function mouseout(d) { svg.selectAll("path.link.source-" + d.key) .classed("source", false) .each(updateNodes("target", false)); svg.selectAll("path.link.target-" + d.key) .classed("target", false) .each(updateNodes("source", false)); } function updateNodes(name, value) { return function(d) { if (value) this.parentNode.appendChild(this); svg.select("#node-" + d[name].key).classed(name, value); }; } function cross(a, b) { return a[0] * b[1] - a[1] * b[0]; } function dot(a, b) { return a[0] * b[0] + a[1] * b[1]; } 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> </body> </html>
https://code.jquery.com/jquery-1.12.3.min.js
https://d3js.org/d3.v3.min.js