Built with blockbuilder.org
forked from SpaceActuary's block: Group Clustering
forked from SpaceActuary's block: Org Bubbles
license: mit |
Built with blockbuilder.org
forked from SpaceActuary's block: Group Clustering
forked from SpaceActuary's block: Org Bubbles
ID | PID | Level | Position | Location | Type | |
---|---|---|---|---|---|---|
0 | 0 | 1 | 5.5 | 2 | M | |
1 | 0 | 2 | 5.5 | 1 | M | |
2 | 1 | 3 | 3 | 1 | M | |
3 | 1 | 3 | 9 | 1 | N | |
4 | 1 | 3 | 7 | 1 | N | |
5 | 1 | 3 | 5.5 | 1 | M | |
6 | 5 | 4 | 3 | 1 | N | |
7 | 5 | 4 | 8 | 2 | N | |
8 | 6 | 5 | 1 | 3 | M | |
9 | 6 | 5 | 3 | 1 | M | |
10 | 6 | 5 | 5 | 1 | N | |
11 | 7 | 5 | 7 | 1 | N | |
12 | 7 | 5 | 9 | 2 | M |
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { | |
margin:0; | |
position:fixed; | |
top:0; | |
right:0; | |
bottom:0; | |
left:0; | |
font-family: sans-serif; | |
} | |
.selected { | |
fill: none; | |
} | |
.links line { | |
stroke: #000; | |
stroke-width: 3px; | |
} | |
.button { | |
min-width: 130px; | |
padding: 4px 5px; | |
cursor: pointer; | |
text-align: center; | |
font-size: 13px; | |
border: 1px solid #e0e0e0; | |
text-decoration: none; | |
} | |
.button.active { | |
background: #000; | |
color: #fff; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="toolbar"> | |
<button id="all" class="button">All</button> | |
<button id="Location" class="button">By Location</button> | |
<button id="Org" class="button active">By Org</button> | |
<button id="Type" class="button">By Type</button> | |
</div> | |
<script> | |
console.clear() | |
var w = 960, h = 500; | |
var radius = 25; | |
var color = d3.scaleOrdinal(d3.schemeCategory20); | |
var centerScale = d3.scalePoint().padding(1).range([0, w]); | |
var xScale = d3.scaleLinear().range([w * .2, w * .8]); | |
var yScale = d3.scaleLinear().range([h * .2, h * .8]); | |
var forceStrength = 0.05; | |
var svg = d3.select("body").append("svg") | |
.attr("width", w) | |
.attr("height", h) | |
var simulation = d3.forceSimulation() | |
.force("link", d3.forceLink().id(function(d) { return d.ID; }) | |
.strength(0)) | |
.force("collide",d3.forceCollide( function(d){ | |
return d.r + 8 }).iterations(16) | |
) | |
.force("charge", d3.forceManyBody()) | |
.force("y", d3.forceY().y(h / 2)) | |
.force("x", d3.forceX().x(w / 2)) | |
d3.csv("data.csv", function(data){ | |
graph = { | |
"nodes": data, | |
"links": data.map(function(d){ | |
return {source: d.ID, target: d.PID}; | |
}) | |
}; | |
data.forEach(function(d){ | |
d.r = radius; | |
d.x = w / 2; | |
d.y = h / 2; | |
}) | |
console.table(data); | |
var link = svg.append("g") | |
.attr("class", "links") | |
.selectAll("line") | |
.data(graph.links) | |
.enter().append("line") | |
.attr("stroke-width", function(d) { return 3; }); | |
var node = svg.append("g") | |
.attr("class", "nodes") | |
.selectAll("circle") | |
.data(graph.nodes) | |
.enter().append("circle") | |
.attr("r", radius) | |
.style("fill", function(d, i){ return color(d.ID); }) | |
.style("stroke", function(d, i){ return color(d.ID); }) | |
.style("stroke-width", 5) | |
.style("pointer-events", "all") | |
.call(d3.drag() | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended)); | |
function ticked() { | |
//console.log("tick") | |
//console.log(data.map(function(d){ return d.x; })); | |
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("cx", function(d){ return d.x; }) | |
.attr("cy", function(d){ return d.y; }); | |
} | |
simulation | |
.nodes(graph.nodes) | |
.on("tick", ticked); | |
simulation.force("link") | |
.links(graph.links); | |
function dragstarted(d,i) { | |
//console.log("dragstarted " + i) | |
if (!d3.event.active) simulation.alpha(1).restart(); | |
d.fx = d.x; | |
d.fy = d.y; | |
} | |
function dragged(d,i) { | |
//console.log("dragged " + i) | |
d.fx = d3.event.x; | |
d.fy = d3.event.y; | |
} | |
function dragended(d,i) { | |
//console.log("dragended " + i) | |
if (!d3.event.active) simulation.alphaTarget(0); | |
d.fx = null; | |
d.fy = null; | |
var me = d3.select(this) | |
console.log(me.classed("selected")) | |
me.classed("selected", !me.classed("selected")) | |
d3.selectAll("circle") | |
.style("fill", function(d, i){ return color(d.ID); }) | |
d3.selectAll("circle.selected") | |
.style("fill", "none") | |
} | |
function groupBubbles() { | |
hideTitles(); | |
link.transition().duration(500) | |
.style("stroke-opacity", 0) | |
// @v4 Reset the 'x' force to draw the bubbles to the center. | |
simulation.force('x', d3.forceX().strength(forceStrength).x(w / 2)); | |
// @v4 We can reset the alpha value and restart the simulation | |
simulation.alpha(1).restart(); | |
} | |
function splitBubbles(byVar) { | |
centerScale.domain(data.map(function(d){ return d[byVar]; })); | |
if(byVar == "all"){ | |
hideTitles() | |
} else { | |
showTitles(byVar, centerScale); | |
} | |
link.transition().duration(500) | |
.style("stroke-opacity", 0) | |
// @v4 Reset the 'x' force to draw the bubbles to their year centers | |
simulation | |
.force('x', d3.forceX().strength(forceStrength).x(function(d){ | |
return centerScale(d[byVar]); | |
})). | |
force('y', d3.forceY().strength(forceStrength).y(h / 2)); | |
// @v4 We can reset the alpha value and restart the simulation | |
simulation.alpha(2).restart(); | |
} | |
function orgBubbles() { | |
console.log("orgBubbles") | |
xScale.domain(d3.extent(graph.nodes, function(d){ return +d.Position; })); | |
yScale.domain(d3.extent(graph.nodes, function(d){ return +d.Level; })); | |
link.transition().duration(2000) | |
.style("stroke-opacity", 1) | |
// @v4 Reset the 'x' force to draw the bubbles to their year centers | |
simulation | |
.force('x', d3.forceX().strength(forceStrength).x(function(d){ | |
return xScale(d.Position); | |
})) | |
.force('y', d3.forceY().strength(forceStrength).y(function(d){ | |
return yScale(d.Level); | |
})); | |
// @v4 We can reset the alpha value and restart the simulation | |
simulation.alpha(2).restart(); | |
} | |
function hideTitles() { | |
svg.selectAll('.title').remove(); | |
} | |
function showTitles(byVar, scale) { | |
// Another way to do this would be to create | |
// the year texts once and then just hide them. | |
var titles = svg.selectAll('.title') | |
.data(scale.domain()); | |
titles.enter().append('text') | |
.attr('class', 'title') | |
.merge(titles) | |
.attr('x', function (d) { return scale(d); }) | |
.attr('y', 40) | |
.attr('text-anchor', 'middle') | |
.text(function (d) { return byVar + ' ' + d; }); | |
titles.exit().remove() | |
} | |
function setupButtons() { | |
d3.selectAll('.button') | |
.on('click', function () { | |
// Remove active class from all buttons | |
d3.selectAll('.button').classed('active', false); | |
// Find the button just clicked | |
var button = d3.select(this); | |
// Set it as the active button | |
button.classed('active', true); | |
// Get the id of the button | |
var buttonId = button.attr('id'); | |
console.log(buttonId) | |
// Toggle the bubble chart based on | |
// the currently clicked button. | |
if(buttonId == "Org"){ | |
orgBubbles() | |
} else { | |
splitBubbles(buttonId); | |
} | |
}); | |
} | |
setupButtons() | |
orgBubbles() | |
}) | |
</script> | |
</body> |