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> |