Skip to content

Instantly share code, notes, and snippets.

@sineline
Last active March 24, 2018 14:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sineline/2842a04a5abd081aae1d55234fdd4bb2 to your computer and use it in GitHub Desktop.
Save sineline/2842a04a5abd081aae1d55234fdd4bb2 to your computer and use it in GitHub Desktop.
# Created by .ignore support plugin (hsz.mobi)
{
"nodes":[
{
"id": "Sustainable Development",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan"]
},
{
"id": "Keep it simple",
"group": "Motivated by",
"paths": ["Less documentation, more action”,“Responding to change vs following the plan"]
},
{
"id": "Face to face conversations",
"group": "Motivated by",
"paths": ["Individual interactions over processes and tools"]
},
{
"id": "Work together",
"group": "Motivated by",
"paths": ["Individual interactions over processes and tools”,“Customer collab. Vs Contract negotiation"]
},
{
"id": "Reflect and adjust",
"group": "Motivated by",
"paths": ["Less documentation, more action"]
},
{
"id": "self/organizing teams",
"group": "Motivated by",
"paths": ["Individual interactions over processes and tools"]
},
{
"id": "consumer satisfaction",
"group": "Motivated by",
"paths": ["Customer collab. Vs Contract negotiation"]
},
{
"id": "Digital transformation",
"group": "Motivated by",
"paths": ["Less documentation, more action”,“Responding to change vs following the plan”,“We love"]
},
{
"id": "Challenge status quo",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan”,“We love"]
},
{
"id": "Delivering the best in class digital technological solutions",
"group": "Motivated by",
"paths": ["Less documentation, more action”,“Responding to change vs following the plan"]
},
{
"id": "Innovating ",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan”,“We love"]
},
{
"id": "Fail fast and learn from mistakes",
"group": "Motivated by",
"paths": ["Less documentation, more action"]
},
{
"id": "Better work in future for everyone",
"group": "Motivated by",
"paths": ["We love"]
},
{
"id": "Respond to change",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan"]
},
{
"id": "diverse",
"group": "We are",
"paths": ["Individual interactions over processes and tools"]
},
{
"id": "Learn from colleagues",
"group": "We are",
"paths": ["Individual interactions over processes and tools"]
},
{
"id": "Openminded",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Less documentation, more action”,“Responding to change vs following the plan"]
},
{
"id": "curious",
"group": "We are",
"paths": ["Individual interactions over processes and tools"]
},
{
"id": "Knowledge hungry",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Responding to change vs following the plan"]
},
{
"id": "Multicultural",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“We love"]
},
{
"id": "Passionate",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Responding to change vs following the plan"]
},
{
"id": "Trustworthy",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Customer collab. Vs Contract negotiation”,“Responding to change vs following the plan"]
},
{
"id": "Supportive",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Less documentation, more action”,“Customer collab. Vs Contract negotiation”,“Responding to change vs following the plan"]
},
{
"id": "Standing for our values",
"group": "We believe",
"paths": ["We love"]
},
{
"id": "Everyone has a voice",
"group": "We believe",
"paths": ["Individual interactions over processes and tools"]
},
{
"id": "Best version of ourselves",
"group": "We believe",
"paths": ["Individual interactions over processes and tools”,“Responding to change vs following the plan"]
},
{
"id": "Make a difference",
"group": "We believe",
"paths": ["Responding to change vs following the plan"]
},
{
"id": "Technology",
"group": "We love",
"paths": ["Less documentation, more action”,“Customer collab. Vs Contract negotiation”,“We love"]
},
{
"id": "PingPong",
"group": "We love",
"paths": ["We love"]
},
{
"id": "Loving chocolate and coffee",
"group": "We love",
"paths": ["We love"]
}
],
"links":[
{"source":1,"target":15,"value":1},
{"source":11,"target":1,"value":1},
{"source":12,"target":1,"value":1},
{"source":3,"target":1,"value":1}
]
}
/* global d3 */
function forceInABox(alpha) {
function index(d) {
return d.index;
}
var id = index,
nodes,
links, //needed for the force version
tree,
size = [100,100],
nodeSize = 1, // The expected node size used for computing the cluster node
forceCharge = -2,
foci = {},
// oldStart = force.start,
linkStrengthIntraCluster = 0.1,
linkStrengthInterCluster = 0.01,
// oldGravity = force.gravity(),
templateNodes = [],
offset = [0,0],
templateForce,
templateNodesSel,
groupBy = function (d) { return d.cluster; },
template = "treemap",
enableGrouping = true,
strength = 0.1;
// showingTemplate = false;
function force(alpha) {
if (!enableGrouping) {
return force;
}
if (template==="force") {
//Do the tick of the template force and get the new focis
templateForce.tick();
getFocisFromTemplate();
}
for (var i = 0, n = nodes.length, node, k = alpha * strength; i < n; ++i) {
node = nodes[i];
node.vx += (foci[groupBy(node)].x - node.x) * k;
node.vy += (foci[groupBy(node)].y - node.y) * k;
}
}
function initialize() {
if (!nodes) return;
// var i,
// n = nodes.length,
// m = links.length,
// nodeById = map(nodes, id),
// link;
if (template==="treemap") {
initializeWithTreemap();
} else {
initializeWithForce();
}
}
force.initialize = function(_) {
nodes = _;
initialize();
};
function getLinkKey(l) {
var sourceID = groupBy(l.source),
targetID = groupBy(l.target);
return sourceID <= targetID ?
sourceID + "~" + targetID :
targetID + "~" + sourceID;
}
function computeClustersNodeCounts(nodes) {
var clustersCounts = d3.map();
nodes.forEach(function (d) {
if (!clustersCounts.has(groupBy(d))) {
clustersCounts.set(groupBy(d), 0);
}
});
nodes.forEach(function (d) {
// if (!d.show) { return; }
clustersCounts.set(groupBy(d), clustersCounts.get(groupBy(d)) + 1);
});
return clustersCounts;
}
//Returns
function computeClustersLinkCounts(links) {
var dClusterLinks = d3.map(),
clusterLinks = [];
links.forEach(function (l) {
var key = getLinkKey(l), count;
if (dClusterLinks.has(key)) {
count = dClusterLinks.get(key);
} else {
count = 0;
}
count += 1;
dClusterLinks.set(key, count);
});
dClusterLinks.entries().forEach(function (d) {
var source, target;
source = d.key.split("~")[0];
target = d.key.split("~")[1];
clusterLinks.push({
"source":source,
"target":target,
"count":d.value
});
});
return clusterLinks;
}
//Returns the metagraph of the clusters
function getGroupsGraph() {
var gnodes = [],
glinks = [],
// edges = [],
dNodes = d3.map(),
// totalSize = 0,
clustersList,
c, i, size,
clustersCounts,
clustersLinks;
clustersCounts = computeClustersNodeCounts(nodes);
clustersLinks = computeClustersLinkCounts(links);
//map.keys() is really slow, it's crucial to have it outside the loop
clustersList = clustersCounts.keys();
for (i = 0; i< clustersList.length ; i+=1) {
c = clustersList[i];
size = clustersCounts.get(c);
gnodes.push({id : c, size :size });
dNodes.set(c, i);
// totalSize += size;
}
clustersLinks.forEach(function (l) {
glinks.push({
"source":dNodes.get(l.source),
"target":dNodes.get(l.target),
"count":l.count
});
});
return {nodes: gnodes, links: glinks};
}
function getGroupsTree() {
var children = [],
totalSize = 0,
clustersList,
c, i, size, clustersCounts;
clustersCounts = computeClustersNodeCounts(force.nodes());
//map.keys() is really slow, it's crucial to have it outside the loop
clustersList = clustersCounts.keys();
for (i = 0; i< clustersList.length ; i+=1) {
c = clustersList[i];
size = clustersCounts.get(c);
children.push({id : c, size :size });
totalSize += size;
}
// return {id: "clustersTree", size: totalSize, children : children};
return {id: "clustersTree", children : children};
}
function getFocisFromTemplate() {
//compute foci
foci.none = {x : 0, y : 0};
templateNodes.forEach(function (d) {
if (template==="treemap") {
foci[d.data.id] = {
x : (d.x0 + (d.x1-d.x0) / 2) - offset[0],
y : (d.y0 + (d.y1-d.y0) / 2) - offset[1]
};
} else {
foci[d.id] = {x : d.x - offset[0] , y : d.y - offset[1]};
}
});
}
function initializeWithTreemap() {
var treemap = d3.treemap()
.size(force.size());
tree = d3.hierarchy(getGroupsTree())
// .sort(function (p, q) { return d3.ascending(p.size, q.size); })
// .count()
.sum(function (d) { return d.size; })
.sort(function(a, b) {
return b.height - a.height || b.value - a.value; })
;
templateNodes = treemap(tree).leaves();
getFocisFromTemplate();
}
function checkLinksAsObjects() {
// Check if links come in the format of indexes instead of objects
var linkCount = 0;
if (nodes.length===0) return;
links.forEach(function (link) {
var source, target;
if (!nodes) return;
source = link.source;
target = link.target;
if (typeof link.source !== "object") source = nodes[link.source];
if (typeof link.target !== "object") target = nodes[link.target];
if (source === undefined || target === undefined) {
console.log(link);
throw Error("Error setting links, couldn't find nodes for a link (see it on the console)" );
}
link.source = source; link.target = target;
link.index = linkCount++;
});
}
function initializeWithForce() {
var net;
if (nodes && nodes.length>0) {
if (groupBy(nodes[0])===undefined) {
throw Error("Couldn't find the grouping attribute for the nodes. Make sure to set it up with forceInABox.groupBy('attr') before calling .links()");
}
}
checkLinksAsObjects();
net = getGroupsGraph();
templateForce = d3.forceSimulation(net.nodes)
.force("x", d3.forceX(size[0]/2).strength(0.5))
.force("y", d3.forceY(size[1]/2).strength(0.5))
.force("collide", d3.forceCollide(function (d) { return d.size*nodeSize; }))
.force("charge", d3.forceManyBody().strength(function (d) { return forceCharge * d.size; }))
.force("links", d3.forceLink(!net.nodes ? net.links :[]))
templateNodes = templateForce.nodes();
getFocisFromTemplate();
}
function drawTreemap(container) {
container.selectAll(".cell").remove();
container.selectAll("cell")
.data(templateNodes)
.enter().append("svg:rect")
.attr("class", "cell")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("width", function (d) { return d.x1-d.x0; })
.attr("height", function (d) { return d.y1-d.y0; });
}
function drawGraph(container) {
container.selectAll(".cell").remove();
templateNodesSel = container.selectAll("cell")
.data(templateNodes);
templateNodesSel
.enter().append("svg:circle")
.attr("class", "cell")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", function (d) { return d.size*nodeSize; });
}
force.drawTemplate = function (container) {
// showingTemplate = true;
if (template === "treemap") {
drawTreemap(container);
} else {
drawGraph(container);
}
return force;
};
//Backwards compatibility
force.drawTreemap = force.drawTemplate;
force.deleteTemplate = function (container) {
// showingTemplate = false;
container.selectAll(".cell").remove();
return force;
};
force.template = function (x) {
if (!arguments.length) return template;
template = x;
initialize();
return force;
};
force.groupBy = function (x) {
if (!arguments.length) return groupBy;
if (typeof x === "string") {
groupBy = function (d) {return d[x]; };
return force;
}
groupBy = x;
return force;
};
force.enableGrouping = function (x) {
if (!arguments.length) return enableGrouping;
enableGrouping = x;
// update();
return force;
};
force.strength = function (x) {
if (!arguments.length) return strength;
strength = x;
return force;
};
force.getLinkStrength = function (e) {
if(enableGrouping) {
if (groupBy(e.source) === groupBy(e.target)) {
if (typeof(linkStrengthIntraCluster)==="function") {
return linkStrengthIntraCluster(e);
} else {
return linkStrengthIntraCluster;
}
} else {
if (typeof(linkStrengthInterCluster)==="function") {
return linkStrengthInterCluster(e);
} else {
return linkStrengthInterCluster;
}
}
} else {
// Not grouping return the intracluster
if (typeof(linkStrengthIntraCluster)==="function") {
return linkStrengthIntraCluster(e);
} else {
return linkStrengthIntraCluster;
}
}
};
force.id = function(_) {
return arguments.length ? (id = _, force) : id;
};
force.size = function(_) {
return arguments.length ? (size = _, force) : size;
};
force.linkStrengthInterCluster = function(_) {
return arguments.length ? (linkStrengthInterCluster = _, force) : linkStrengthInterCluster;
};
force.linkStrengthIntraCluster = function(_) {
return arguments.length ? (linkStrengthIntraCluster = _, force) : linkStrengthIntraCluster;
};
force.nodes = function(_) {
return arguments.length ? (nodes = _, force) : nodes;
};
force.links = function(_) {
if (!arguments.length)
return links;
if (_ === null) links = [];
else links = _;
return force;
};
force.nodeSize = function(_) {
return arguments.length ? (nodeSize = _, force) : nodeSize;
};
force.forceCharge = function(_) {
return arguments.length ? (forceCharge = _, force) : forceCharge;
};
force.offset = function(_) {
return arguments.length ? (offset = _, force) : offset;
};
return force;
}
<html>
<head>
<style>
/* HTML styles */
html{ width: 100%; }
body{
width: 100%;
margin: 0; padding: 0;
display: flex;
font-family: sans-serif; font-size: 75%; }
.controls {
flex-basis: 200px;
padding: 0 5px;
overflow-y: scroll;
}
.controls .force {
background-color:#eee;
border-radius: 3px;
padding: 5px;
margin: 5px 0;
}
.controls .force p label { margin-right: .5em; font-size: 120%; font-weight: bold;}
.controls .force p { margin-top: 0;}
.controls .force label { display: inline-block; }
.controls input[type="checkbox"] { transform: scale(1.2, 1.2); }
.controls input[type="range"] { margin: 0 5% 0.5em 5%; width: 90%; }
/* alpha viewer */
.controls .alpha p { margin-bottom: .25em; }
.controls .alpha .alpha_bar { height: .5em; border: 1px #777 solid; border-radius: 2px; padding: 1px; display: flex; }
.controls .alpha .alpha_bar #alpha_value { background-color: #555; border-radius: 1px; flex-basis: 100% }
.controls .alpha .alpha_bar:hover { border-width: 2px; margin:-1px; }
.controls .alpha .alpha_bar:active #alpha_value { background-color: #222 }
/* SVG styles */
svg {
flex-basis: 100%;
min-width: 200px;
}
body {
margin:0;
position:fixed;
top:0;
right:0;
bottom:0;
left:0;
}
.links line {
stroke: #aaa;
}
.nodes circle {
pointer-events: all;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
path {
fill-opacity: .1;
stroke-opacity: 1;
}
.polygons :first-child {
/*fill: #f00;*/
}
.sites {
fill: #000;
stroke: #fff;
}
.sites :first-child {
fill: #fff;
}
</style>
</head>
<body>
<div class="controls">
<div class="force alpha">
<p><label>alpha</label> Simulation activity</p>
<div class="alpha_bar" onclick="updateAll();"><div id="alpha_value"></div></div>
</div>
<div class="force">
<p><label>Voronoi</label> </p>
<div id="scaleFactorSettings">
<input id="checkVoronoi" type="checkbox">Show Voronoi</input>
</div>
</div>
<div class="force">
<p><label>Delaunay links</label> </p>
<div id="scaleFactorSettings">
<input id="checkDelaunay" type="checkbox">Show Delaunay</input>
</div>
</div>
<div class="force">
<p><label><input type="checkbox" checked onchange="roadProperties.enabled = this.checked; updateAll();"> Roads</label> shows roads</p>
<label title="Show roads">
<output id="road_ResolutionSliderOutput">5</output>
<input type="range" min="0" max="125" value="25" step="0.01" oninput="d3.select('#road_ResolutionSliderOutput').text(Number(value)); roadProperties.Resolution=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label>Settings</label></p>
<input id="checkGroupInABox" type="checkbox">Group in a Box</input>
<input id="checkshowGrid" type="checkbox">Show Grid</input>
<input id="snapGrid" type="checkbox">Snap to Grid</input>
<input id="checkShowTreemap" type="checkbox">Show Template</input>
<select id="selectTemplate" type="select">
<option value="treemap">Treemap</option>
<option value="force">Force</option>
</select>
</div>
<div class="force">
<p><label>Grid Settings</label> </p>
<label>
R
<output id="Resolution_RSliderOutput">25</output>
<input type="range" min="0" max="125" value="25" step="0.01" oninput="d3.select('#Resolution_RSliderOutput').text(Number(value)); gridProperties.r=Number(value); updateAll();">
Resolution
<output id="Resolution_ReSliderOutput">25</output>
<input type="range" min="1" max="125" value="25" step="0.01" oninput="d3.select('#Resolution_ReSliderOutput').text(Number(value)); gridProperties.resolution=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label>center</label> Shifts the view, so the graph is centered at this location.</p>
<label>
x
<output id="center_XSliderOutput">.5</output>
<input type="range" min="0" max="1" value=".5" step="0.01" oninput="d3.select('#center_XSliderOutput').text(Number(value)); forceProperties.center.x=Number(value); updateAll();">
</label>
<label>
y
<output id="center_YSliderOutput">.5</output>
<input type="range" min="0" max="1" value=".5" step="0.01" oninput="d3.select('#center_YSliderOutput').text(Number(value)); forceProperties.center.y=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label><input type="checkbox" checked onchange="forceProperties.charge.enabled = this.checked; updateAll();"> charge</label> Attracts (+) or repels (-) nodes to/from each other.</p>
<label title="Negative strength repels nodes. Positive strength attracts nodes.">
strength
<output id="charge_StrengthSliderOutput">-30</output>
<input type="range" min="-6000" max="50" value="-30" step=".1" oninput="d3.select('#charge_StrengthSliderOutput').text(Number(value)); forceProperties.charge.strength=Number(value); updateAll();">
</label>
<label title="Minimum distance where force is applied">
distanceMin
<output id="charge_distanceMinSliderOutput">1</output>
<input type="range" min="-500" max="500" value="1" step=".1" oninput="d3.select('#charge_distanceMinSliderOutput').text(Number(value)); forceProperties.charge.distanceMin=Number(value); updateAll();">
</label>
<label title="Maximum distance where force is applied">
distanceMax
<output id="charge_distanceMaxSliderOutput">2000</output>
<input type="range" min="-2000" max="2000" value="2000" step=".1" oninput="d3.select('#charge_distanceMaxSliderOutput').text(Number(value)); forceProperties.charge.distanceMax=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label><input type="checkbox" checked onchange="forceProperties.collide.enabled = this.checked; updateAll();"> collide</label> Prevents nodes from overlapping</p>
<label>
strength
<output id="collide_StrengthSliderOutput">.7</output>
<input type="range" min="-50" max="50" value=".7" step=".1" oninput="d3.select('#collide_StrengthSliderOutput').text(Number(value)); forceProperties.collide.strength=Number(value); updateAll();">
</label>
<label title="Size of nodes">
radius
<output id="collide_radiusSliderOutput">5</output>
<input type="range" min="0" max="100" value="5" step="1" oninput="d3.select('#collide_radiusSliderOutput').text(Number(value)); forceProperties.collide.radius=Number(value); updateAll();">
</label>
<label title="Higher values increase rigidity of the nodes (WARNING: high values are computationally expensive)">
iterations
<output id="collide_iterationsSliderOutput">1</output>
<input type="range" min="1" max="10" value="1" step="1" oninput="d3.select('#collide_iterationsSliderOutput').text(Number(value)); forceProperties.collide.iterations=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label><input type="checkbox" onchange="forceProperties.forceX.enabled = this.checked; updateAll();"> forceX</label> Acts like gravity. Pulls all points towards an X location.</p>
<label>
strength
<output id="forceX_StrengthSliderOutput">.1</output>
<input type="range" min="0" max="1" value=".1" step="0.01" oninput="d3.select('#forceX_StrengthSliderOutput').text(Number(value)); forceProperties.forceX.strength=Number(value); updateAll();">
</label>
<label title="The X location that the force will push the nodes to (NOTE: This demo multiplies by the svg width)">
x
<output id="forceX_XSliderOutput">.5</output>
<input type="range" min="-10" max="10" value=".5" step="0.01" oninput="d3.select('#forceX_XSliderOutput').text(Number(value)); forceProperties.forceX.x=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label><input type="checkbox" onchange="forceProperties.forceY.enabled = this.checked; updateAll();"> forceY</label> Acts like gravity. Pulls all points towards a Y location.</p>
<label>
strength
<output id="forceY_StrengthSliderOutput">.1</output>
<input type="range" min="0" max="1" value=".1" step="0.01" oninput="d3.select('#forceY_StrengthSliderOutput').text(Number(value)); forceProperties.forceY.strength=Number(value); updateAll();">
</label>
<label title="The Y location that the force will push the nodes to (NOTE: This demo multiplies by the svg height)">
y
<output id="forceY_YSliderOutput">.5</output>
<input type="range" min="0" max="1" value=".5" step="0.01" oninput="d3.select('#forceY_YSliderOutput').text(Number(value)); forceProperties.forceY.y=Number(value); updateAll();">
</label>
</div>
<div class="force">
<p><label><input type="checkbox" checked onchange="forceProperties.link.enabled = this.checked; updateAll();"> link</label> Sets link length</p>
<label title="The force will push/pull nodes to make links this long">
distance
<output id="link_DistanceSliderOutput">30</output>
<input type="range" min="0" max="100" value="30" step="1" oninput="d3.select('#link_DistanceSliderOutput').text(Number(value)); forceProperties.link.distance=Number(value); updateAll();">
</label>
<label title="Higher values increase rigidity of the links (WARNING: high values are computationally expensive)">
iterations
<output id="link_IterationsSliderOutput">1</output>
<input type="range" min="1" max="10" value="1" step="1" oninput="d3.select('#link_IterationsSliderOutput').text(Number(value)); forceProperties.link.iterations=Number(value); updateAll();">
</label>
</div>
</div>
<svg></svg>
<script src="//d3js.org/d3.v4.js"></script>
<script type="text/javascript" src="./forceInABox.js"></script>
<script>
gridProperties = {
r:1,
resolution : 25
};
// Road Properties
roadProperties = {
enabled: true,
Resolution : 25
};
// values for all forces
forceProperties = {
center: {
x: 0.5,
y: 0.5
},
charge: {
enabled: true,
strength: -30,
distanceMin: 1,
distanceMax: 2000
},
collide: {
enabled: true,
strength: .7,
iterations: 1,
radius: 5
},
forceX: {
enabled: false,
strength: .1,
x: .5
},
forceY: {
enabled: false,
strength: .1,
y: .5
},
link: {
enabled: true,
distance: 30,
iterations: 1
}
};
var graph;
var svg = d3.select("svg"),
width = +svg.node().getBoundingClientRect().width,
height = +svg.node().getBoundingClientRect().height;
var radius = 50;
var svgbgGrid = svg.append("g");
var svgVoronoi = svg.append("g");
var svgRoads = svg.append("g");
var svgForce = svg.append("g");
var svgDelaunay = svg.append("g");
var svgm = svg.append("g");
var road,
roads,
node,
link,
links,
text,
polygons,
polygon,
site,
sites,
path_end;
var useGroupInABox = true,
drawTemplate = true,
checkGroupBubbles = true,
checkVoronoi = true,
checkDelaunay = true,
drawGrid = false,
snapGrid = false,
template = "force";
d3.select("#checkGroupInABox").property("checked", useGroupInABox);
d3.select("#checkshowGrid").property("checked", drawGrid);
d3.select("#checkDelaunay").property("checked", checkDelaunay);
d3.select("#checkVoronoi").property("checked", checkVoronoi);
d3.select("#checkGroupBubbles").property("checked", checkGroupBubbles);
d3.select("#snapGrid").property("checked", snapGrid);
d3.select("#checkShowTreemap").property("checked", drawTemplate);
d3.select("#selectTemplate").property("value", template);
//add drag capabilities
//add zoom capabilities
var zoom_handler = d3.zoom()
.on("zoom", zoom_actions);
//Zoom functions
function zoom_actions(){
svg.attr("transform", d3.event.transform)
}
var voronoi = d3.voronoi()
.x(function(d) {
return d[0];
})
.y(function(d) {
return d[1];
}).extent([[-1,-1], [width, height]]);
var nodes;
var force = d3.forceSimulation()
.force("charge", d3.forceManyBody())
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(16) )
.force("x", d3.forceX(width/2).strength(0.05))
.force("y", d3.forceY(height/2).strength(0.05)).force("center", d3.forceCenter(width / 2, height / 2));
var groupingForce = forceInABox();
var color = d3.scaleOrdinal(d3.schemeCategory10);
var nodeById ;
// set up the simulation and event to update locations after each tick
function initializeSimulation() {
nodeById = d3.map(graph.nodes, function(d) { return d.id; });
groupingForce
.strength(0.1) // Strength to foci
.template(template) // Either treemap or force
.groupBy("group") // Node attribute to group
.links(graph.links) // The graph links. Must be called after setting the grouping attribute
.enableGrouping(useGroupInABox)
.nodeSize(5) // How big are the nodes to compute the force template
.forceCharge(-20) // Separation between nodes on the force template
.size([width, height]); // Size of the chart
force
.nodes(graph.nodes)
.force("group", groupingForce)
.force("link", d3.forceLink(graph.links)
// .distance(5)
.strength(groupingForce.getLinkStrength)
) ;
// simulation;
initializeForces();
force.on("tick", ticked);
}
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
// add forces to the simulation
function initializeForces() {
// add forces and associate each with a name
force
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody())
.force("collide", d3.forceCollide())
.force("center", d3.forceCenter())
.force("forceX", d3.forceX())
.force("forceY", d3.forceY());
// apply properties to each of the forces
updateForces();
}
// apply new force properties
function updateForces() {
// get each force by name and update the properties
force.force("center")
.x(width * forceProperties.center.x)
.y(height * forceProperties.center.y);
force.force("charge")
.strength(forceProperties.charge.strength * forceProperties.charge.enabled)
.distanceMin(forceProperties.charge.distanceMin)
.distanceMax(forceProperties.charge.distanceMax);
force.force("collide")
.strength(forceProperties.collide.strength * forceProperties.collide.enabled)
.radius(forceProperties.collide.radius)
.iterations(forceProperties.collide.iterations);
force.force("forceX")
.strength(forceProperties.forceX.strength * forceProperties.forceX.enabled)
.x(width * forceProperties.forceX.x);
force.force("forceY")
.strength(forceProperties.forceY.strength * forceProperties.forceY.enabled)
.y(height * forceProperties.forceY.y);
force.force("link")
.id(function(d) {return d.id;})
.distance(forceProperties.link.distance)
.iterations(forceProperties.link.iterations)
.links(forceProperties.link.enabled ? graph.links : []);
// updates ignored until this is run
// restarts the simulation (important if simulation has already slowed down)
force.alpha(1).restart();
}
// generate the svg objects and force simulation
function initializeDisplay() {
if(drawGrid) {
svgbgGrid.selectAll('.vertical')
.data(d3.range(1, width / gridProperties.resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function (d) {
return d * gridProperties.resolution;
})
.attr('y1', 0)
.attr('x2', function (d) {
return d * gridProperties.resolution;
})
.attr('y2', height);
svgbgGrid.selectAll('.horizontal')
.data(d3.range(1, height / gridProperties.resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function (d) {
return d * gridProperties.resolution;
})
.attr('x2', width)
.attr('y2', function (d) {
return d * gridProperties.resolution;
});
}
polygons = svgVoronoi.append("g")
.attr("class", "polygons")
.selectAll("polygon")
.data(graph.nodes)
.enter().append("polygon")
.style("fill", function(d) { return color(d.group); })
.style("fill-opacity", .2)
.style("stroke", "black")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node = svgForce.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("fill", function(d) { return color(d.group); })
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
zoom_handler(node);
path_end = svgRoads.append("g")
.attr("class", "nodes")
.attr("class", "path-ends")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("fill", function(d) { return color(d.group); })
.attr("class", "path-end")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
zoom_handler(path_end);
site = svgDelaunay.append("g")
.attr("class", "sites")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "site")
.attr("r", 0.1) ;
zoom_handler(site);
links = voronoi(force.nodes().map(function(d) {return [d.x, d.y]})).links();
link = svgDelaunay.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line");
roads = links;
road = svgRoads.append("g")
.attr("class", "links")
.attr("class", "roads")
.selectAll("line")
.data(roads)
.enter()
.append("line");
updateDisplay();
}
// update the display based on the forces (but not positions)
function updateDisplay() {
// set the data and properties of link lines
link = link.enter().append("line").merge(link).call(redrawLink);
road = road.enter().append("line").merge(road).call(redrawRoad);
svgbgGrid.selectAll('.vertical').remove();
svgbgGrid.selectAll('.horizontal').remove();
if(drawGrid) {
svgbgGrid.selectAll('.vertical')
.data(d3.range(1, width / gridProperties.resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function (d) {
return d * gridProperties.resolution;
})
.attr('y1', 0)
.attr('x2', function (d) {
return d * gridProperties.resolution;
})
.attr('y2', height);
svgbgGrid.selectAll('.horizontal')
.data(d3.range(1, height / gridProperties.resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function (d) {
return d * gridProperties.resolution;
})
.attr('x2', width)
.attr('y2', function (d) {
return d * gridProperties.resolution;
});
}
node
.attr("r", forceProperties.collide.radius)
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : Math.abs(forceProperties.charge.strength)/15);
site
// .attr("r", forceProperties.collide.radius)
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : 0.01);
path_end
.attr("r", forceProperties.collide.radius)
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : 0.01);
link
.attr("stroke-width", forceProperties.link.enabled ? 1 : .5)
.attr("opacity", forceProperties.link.enabled ? 1 : 0);
road
.attr("opacity", roadProperties.enabled ? 1 : 0);
zoom_handler(node);
zoom_handler(site);
zoom_handler(path_end);
}
function redrawLink(link) {
link
.attr("x1", function(d) { return d.source[0]; })
.attr("y1", function(d) { return d.source[1]; })
.attr("x2", function(d) { return d.target[0]; })
.attr("y2", function(d) { return d.target[1]; });
}
function redrawRoad(link) {
link
.attr("x1", function(d) { return d.source[0]; })
.attr("y1", function(d) { return d.source[1]; })
.attr("x2", function(d) { return d.target[0]; })
.attr("y2", function(d) { return d.target[1]; });
}
/* function redrawLink(link) {
console.log("here?");
link
.classed("primary", function(d) { return d.source === sites[0] || d.target === sites[0]; })
.attr("x1", function(d) { return d.source[0]; })
.attr("y1", function(d) { return d.source[1]; })
.attr("x2", function(d) { return d.target[0]; })
.attr("y2", function(d) { return d.target[1]; });
}*/
// update the display positions after each simulation tick
function ticked() {
if(checkVoronoi){
if (graph.nodes[0].x) {
var polygonShapes = voronoi(graph.nodes.map(function(d) {return [d.x, d.y]})).polygons();
polygons.attr("points", function(d, i) {return polygonShapes[i]})
}
}
if (snapGrid) {
node.attr("cx", function (d) {
return d.x = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.x)), gridProperties.resolution);
})
.attr("cy", function (d) {
return d.y = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.y)), gridProperties.resolution);
});
site.attr("cx", function (d) {
return d.x = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.x)), gridProperties.resolution);
})
.attr("cy", function (d) {
return d.y = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.y)), gridProperties.resolution);
});
path_end.attr("cx", function (d) {
return d.x = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.x)), gridProperties.resolution);
})
.attr("cy", function (d) {
return d.y = round(Math.max(gridProperties.r, Math.min(width - gridProperties.r, d.y)), gridProperties.resolution);
});
d3.select("#alpha_value").style("flex-basis", (force.alpha()*100) + "%");
} else {
node
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
site
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
path_end
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
node.append("text")
.attr("dx", ".10em")
.attr("dy", ".10em")
.text(function(d) { return d.name; });
d3.select("#alpha_value").style("flex-basis", (force.alpha()*100) + "%");
}
links = voronoi(force.nodes().map(function(d) {return [d.x, d.y]})).links();
link = link.data(links), link.exit().remove();
link = link.enter().append("line").merge(link).call(redrawLink);
if(roadProperties.enabled){
roads = links;
road = road.data(roads), road.exit().remove();
road = road.enter().append("line").merge(road).call(redrawRoad);
}
}
// update size-related forces
d3.select(window).on("resize", function(){
width = +svg.node().getBoundingClientRect().width;
height = +svg.node().getBoundingClientRect().height;
updateForces();
});
// convenience function to update everything (run after UI input)
function updateAll() {
updateForces();
updateDisplay();
}
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
if (!d3.event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
/* d3.select(this).select("text")
.attr("x", d.x = d3.event.x)
.attr("y", d.y = d3.event.y);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x)
.attr("y", d.y = d3.event.y);*/
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
d3.select(this).classed("active", false);
if (!d3.event.active) force.alphaTarget(0);
d.fx = null;
d.fy = null;
}
(function() {
d3.json("data.json", function(error, _graph) {
if (error) throw error; graph = _graph;
initializeDisplay();
initializeSimulation();
});
d3.select("#checkGroupInABox").on("change", function () {
force.stop();
useGroupInABox = d3.select("#checkGroupInABox").property("checked");
force
.force("group").enableGrouping(useGroupInABox)
force.alphaTarget(0.5).restart();
});
d3.select("#snapGrid").on("change", function () {
force.stop();
snapGrid = d3.select("#snapGrid").property("checked");
force
.force("group").enableGrouping(useGroupInABox)
force.alphaTarget(0.5).restart();
});
d3.select("#checkVoronoi").on("change", function () {
checkVoronoi = d3.select("#checkVoronoi").property("checked");
checkVoronoi ? svgVoronoi.style("display","") : svgVoronoi.style("display","none");
//checkVoronoi ? null : polygons.remove();
//checkVoronoi ? null : links2.remove();
updateAll();
});
d3.select("#checkDelaunay").on("change", function () {
checkDelaunay = d3.select("#checkDelaunay").property("checked");
checkDelaunay ? svgDelaunay.style("display","") : svgDelaunay.style("display","none");
//checkVoronoi ? null : polygons.remove();
//checkVoronoi ? null : links2.remove();
updateAll();
});
d3.select("#checkshowGrid").on("change", function () {
drawGrid = d3.select("#checkshowGrid").property("checked");
if(drawGrid) {
svgbgGrid.selectAll('.vertical')
.data(d3.range(1, width / gridProperties.resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function (d) {
return d * gridProperties.resolution;
})
.attr('y1', 0)
.attr('x2', function (d) {
return d * gridProperties.resolution;
})
.attr('y2', height);
svgbgGrid.selectAll('.horizontal')
.data(d3.range(1, height / gridProperties.resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function (d) {
return d * gridProperties.resolution;
})
.attr('x2', width)
.attr('y2', function (d) {
return d * gridProperties.resolution;
});
}
else {
svgbgGrid.selectAll('.vertical').remove()
svgbgGrid.selectAll('.horizontal').remove()
}
});
d3.select("#selectTemplate").on("change", function () {
template = d3.select("#selectTemplate").property("value");
force.stop();
force.force("group").template(template);
force.alphaTarget(0.5).restart();
});
d3.select("#checkShowTreemap").on("change", function () {
drawTemplate = d3.select("#checkShowTreemap").property("checked");
if (drawTemplate) {
force.force("group").drawTemplate(svg);
} else {
force.force("group").deleteTemplate(svg);
}
});
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment