Skip to content

Instantly share code, notes, and snippets.

@sineline
Last active March 15, 2018 08: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/c424542996d6b65a4a9d07606c2b844d to your computer and use it in GitHub Desktop.
Save sineline/c424542996d6b65a4a9d07606c2b844d to your computer and use it in GitHub Desktop.
{
"nodes":[
{
"name": "Sustainable Development",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan"]
},
{
"name": "Keep it simple",
"group": "Motivated by",
"paths": ["Less documentation, more action”,“Responding to change vs following the plan"]
},
{
"name": "Face to face conversations",
"group": "Motivated by",
"paths": ["Individual interactions over processes and tools"]
},
{
"name": "Work together",
"group": "Motivated by",
"paths": ["Individual interactions over processes and tools”,“Customer collab. Vs Contract negotiation"]
},
{
"name": "Reflect and adjust",
"group": "Motivated by",
"paths": ["Less documentation, more action"]
},
{
"name": "self/organizing teams",
"group": "Motivated by",
"paths": ["Individual interactions over processes and tools"]
},
{
"name": "consumer satisfaction",
"group": "Motivated by",
"paths": ["Customer collab. Vs Contract negotiation"]
},
{
"name": "Digital transformation",
"group": "Motivated by",
"paths": ["Less documentation, more action”,“Responding to change vs following the plan”,“We love"]
},
{
"name": "Challenge status quo",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan”,“We love"]
},
{
"name": "Delivering the best in class digital technological solutions",
"group": "Motivated by",
"paths": ["Less documentation, more action”,“Responding to change vs following the plan"]
},
{
"name": "Innovating ",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan”,“We love"]
},
{
"name": "Fail fast and learn from mistakes",
"group": "Motivated by",
"paths": ["Less documentation, more action"]
},
{
"name": "Better work in future for nestle",
"group": "Motivated by",
"paths": ["We love"]
},
{
"name": "Respond to change",
"group": "Motivated by",
"paths": ["Responding to change vs following the plan"]
},
{
"name": "diverse",
"group": "We are",
"paths": ["Individual interactions over processes and tools"]
},
{
"name": "Learn from colleagues",
"group": "We are",
"paths": ["Individual interactions over processes and tools"]
},
{
"name": "Openminded",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Less documentation, more action”,“Responding to change vs following the plan"]
},
{
"name": "curious",
"group": "We are",
"paths": ["Individual interactions over processes and tools"]
},
{
"name": "Knowledge hungry",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Responding to change vs following the plan"]
},
{
"name": "Multicultural",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“We love"]
},
{
"name": "Passionate",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Responding to change vs following the plan"]
},
{
"name": "Trustworthy",
"group": "We are",
"paths": ["Individual interactions over processes and tools”,“Customer collab. Vs Contract negotiation”,“Responding to change vs following the plan"]
},
{
"name": "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"]
},
{
"name": "Standing for our values",
"group": "We believe",
"paths": ["We love"]
},
{
"name": "Everyone has a voice",
"group": "We believe",
"paths": ["Individual interactions over processes and tools"]
},
{
"name": "Best version of ourselves",
"group": "We believe",
"paths": ["Individual interactions over processes and tools”,“Responding to change vs following the plan"]
},
{
"name": "Make a difference",
"group": "We believe",
"paths": ["Responding to change vs following the plan"]
},
{
"name": "Technology",
"group": "We love",
"paths": ["Less documentation, more action”,“Customer collab. Vs Contract negotiation”,“We love"]
},
{
"name": "PingPong",
"group": "We love",
"paths": ["We love"]
},
{
"name": "Loving chocolate and coffee",
"group": "We love",
"paths": ["We love"]
}
],
"links":[
{"source":1,"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;
}
.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;
}
</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>Groups bubbles</label> </p>
<div id="scaleFactorSettings">
<input id="checkGroupBubbles" type="checkbox">Show grouping bubbles</input>
<p>Scale of the groups: <span id='scaleFactorLabel'>1.2</span></p>
<input type="range" min="1" max="3" value="1.2" step=".1"
oninput="scaleFactor = value; d3.select('#scaleFactorLabel').text(scaleFactor); updateGroups()">
</div>
<div id="curveSettings">
<p>Type of curve: <span id='curveLabel'>curveCatmullRomClosed</span></p>
</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>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_XSliderOutput">25</output>
<input type="range" min="0" max="125" value="25" step="0.01" oninput="d3.select('#Resolution_XSliderOutput').text(Number(value)); gridProperties.r=Number(value); updateAll();">
Resolution
<output id="Resolution_XSliderOutput">25</output>
<input type="range" min="0" max="125" value="25" step="0.01" oninput="d3.select('#Resolution_XSliderOutput').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="-200" 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="0" max="50" 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="0" 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="0" max="2" 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="0" max="1" 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>
var link, node;
// the data - an object with nodes and links
var graph;
var svgm = d3.select("svg"),
width = +svgm.node().getBoundingClientRect().width,
height = +svgm.node().getBoundingClientRect().height;
var svgbg = svgm.append("g");
var svgbg2 = svgm.append("g");
var svgbg3 = svgm.append("g");
var svg = svgm.append("g");
var valueline = d3.line()
.x(function(d) { return d[0]; })
.y(function(d) { return d[1]; })
.curve(d3.curveCatmullRomClosed),
paths,
text,
groups,
groupIds,
scaleFactor = 1.2,
polygon,
centroid,
node,
link,
curveTypes = ['curveBasisClosed', 'curveCardinalClosed', 'curveCatmullRomClosed', 'curveLinearClosed'];
var useGroupInABox = true,
drawTemplate = true,
checkGroupBubbles = true,
voronoi = true,
drawGrid = false,
snapGrid = false,
template = "force";
d3.select("#checkGroupInABox").property("checked", useGroupInABox);
d3.select("#checkshowGrid").property("checked", drawGrid);
d3.select("#checkVoronoi").property("checked", voronoi);
d3.select("#checkGroupBubbles").property("checked", checkGroupBubbles);
d3.select("#snapGrid").property("checked", snapGrid);
d3.select("#checkShowTreemap").property("checked", drawTemplate);
d3.select("#selectTemplate").property("value", template);
var voronoi ;
var polygons;
var force = d3.forceSimulation()
.force("charge", d3.forceManyBody())
.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));
var groupingForce = forceInABox();
var options;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
// set up the simulation and event to update locations after each tick
function initializeSimulation() {
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
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link", d3.forceLink(graph.links));
paths
.transition()
.duration(2000)
.attr('opacity', 1);
// add interaction to the groups
groups.selectAll('.path_placeholder')
.call(d3.drag()
.on('start', group_dragstarted)
.on('drag', group_dragged)
.on('end', group_dragended)
);
initializeForces();
force.on("tick", ticked);
}
gridProperties = {
r:1,
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
}
}
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();
}
//////////// DISPLAY ////////////
// generate the svg objects and force simulation
function initializeDisplay() {
voronoi = d3.voronoi().extent([[0,0], [width, height]]);
// create selector for curve types
var select = d3.select('#curveSettings')
.append('select')
.attr('class','select')
.on('change', function() {
var val = d3.select('select').property('value');
d3.select('#curveLabel').text(val);
valueline.curve(d3[val]);
updateGroups();
});
options = select
.selectAll('option')
.data(curveTypes).enter()
.append('option')
.text(function (d) { return d; });
if(drawGrid) {
svgbg.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);
svgbg.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;
});
}
groups = svgbg2.append('g').attr('class', 'groups');
groupIds = d3.set(graph.nodes.map(function(n) {
return n.group;
}))
.values()
.map( function(groupId) {
return {
groupId : groupId,
count : graph.nodes.filter(function(n) { return n.group == groupId; }).length
};
})
.filter( function(group) { return group.count > 2;})
.map( function(group) { return group.groupId; });
paths = groups.selectAll('.path_placeholder')
.data(groupIds, function(d) { return +d; })
.enter()
.append('g')
.attr('class', 'path_placeholder')
.append('path')
.attr('stroke', function(d) { return color(d); })
.attr('fill', function(d) { return color(d); })
.attr('opacity', 0);
// set the data and properties of link lines
link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line");
// set the data and properties of node circles
node = svg.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));
polygons = svgbg3.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));
/*simulation.force("link")
.links(graph.links);*/
// visualize the graph
updateDisplay();
}
// update the display based on the forces (but not positions)
function updateDisplay() {
svgbg.selectAll('.vertical').remove();
svgbg.selectAll('.horizontal').remove();
if(drawGrid) {
svgbg.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);
svgbg.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", forceProperties.charge.strength > 0 ? "blue" : "red")
.attr("stroke-width", forceProperties.charge.enabled==false ? 0 : Math.abs(forceProperties.charge.strength)/15);
link
.attr("stroke-width", forceProperties.link.enabled ? 1 : .5)
.attr("opacity", forceProperties.link.enabled ? 1 : 0);
}
// update the display positions after each simulation tick
function ticked() {
/*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; });*/
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);
});
text.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
})
.attr("cy", function (d) {
return d.y
});
text.attr("cx", function (d) {
return d.x
})
.attr("cy", function (d) {
return d.y
});
d3.select("#alpha_value").style("flex-basis", (force.alpha()*100) + "%");
}
updateGroups();
}
// 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();
updateGroups();
updateDisplay();
}
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
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);
}
// select nodes of the group, retrieve its positions
// and return the convex hull of the specified points
// (3 points as minimum, otherwise returns null)
var polygonGenerator = function(groupId) {
var node_coords = node
.filter(function(d) { return d.group == groupId; })
.data()
.map(function(d) { return [d.x, d.y]; });
return d3.polygonHull(node_coords);
};
function updateGroups() {
groupIds.forEach(function(groupId) {
var path = paths.filter(function(d) { return d == groupId;})
.attr('transform', 'scale(1) translate(0,0)')
.attr('d', function(d) {
polygon = polygonGenerator(d);
centroid = d3.polygonCentroid(polygon);
// to scale the shape properly around its points:
// move the 'g' element to the centroid point, translate
// all the path around the center of the 'g' and then
// we can scale the 'g' element properly
return valueline(
polygon.map(function(point) {
return [ point[0] - centroid[0], point[1] - centroid[1] ];
})
);
});
d3.select(path.node().parentNode).attr('transform', 'translate(' + centroid[0] + ',' + (centroid[1]) + ') scale(' + scaleFactor + ')');
});
}
// drag groups
function group_dragstarted(groupId) {
if (!d3.event.active) force.alphaTarget(0.3).restart();
d3.select(this).select('path').style('stroke-width', 3);
}
function group_dragged(groupId) {
node
.filter(function(d) { return d.group == groupId; })
.each(function(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
})
}
function group_dragended(groupId) {
if (!d3.event.active) force.alphaTarget(0.3).restart();
d3.select(this).select('path').style('stroke-width', 1);
}
(function() {
// your page initialization code here
// the DOM will be available here
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("#checkGroupBubbles").on("change", function () {
force.stop();
checkGroupBubbles = d3.select("#snapGrid").property("checked");
d3.select("#checkGroupBubbles").property("checked") ? svgbg2.style("display","") : svgbg2.style("display","none");
updateAll();
});
d3.select("#checkshowGrid").on("change", function () {
drawGrid = d3.select("#checkshowGrid").property("checked");
if(drawGrid) {
svgbg.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);
svgbg.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 {
svgbg.selectAll('.vertical').remove()
svgbg.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