Skip to content

Instantly share code, notes, and snippets.

@sineline
Last active March 14, 2018 20:37
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/3a205e292af68bb6c7a695d3852057a6 to your computer and use it in GitHub Desktop.
Save sineline/3a205e292af68bb6c7a695d3852057a6 to your computer and use it in GitHub Desktop.
Testing of v4 force graph with grid - Based in // Credit https://github.com/john-guerra/forceInABox among others
{
"nodes": [
{"name": "Myriel", "group": 1},
{"name": "Napoleon", "group": 1},
{"name": "Mlle.Baptistine", "group": 1},
{"name": "Mme.Magloire", "group": 1},
{"name": "CountessdeLo", "group": 1},
{"name": "Geborand", "group": 1},
{"name": "Champtercier", "group": 1},
{"name": "Cravatte", "group": 1},
{"name": "Count", "group": 1},
{"name": "OldMan", "group": 1},
{"name": "Labarre", "group": 2},
{"name": "Valjean", "group": 2},
{"name": "Marguerite", "group": 3},
{"name": "Mme.deR", "group": 2},
{"name": "Isabeau", "group": 2},
{"name": "Gervais", "group": 2},
{"name": "Tholomyes", "group": 3},
{"name": "Listolier", "group": 3},
{"name": "Fameuil", "group": 3},
{"name": "Blacheville", "group": 3},
{"name": "Favourite", "group": 3},
{"name": "Dahlia", "group": 3},
{"name": "Zephine", "group": 3},
{"name": "Fantine", "group": 3},
{"name": "Mme.Thenardier", "group": 4},
{"name": "Thenardier", "group": 4},
{"name": "Cosette", "group": 5},
{"name": "Javert", "group": 4},
{"name": "Fauchelevent", "group": 0},
{"name": "Bamatabois", "group": 2},
{"name": "Perpetue", "group": 3},
{"name": "Simplice", "group": 2},
{"name": "Scaufflaire", "group": 2},
{"name": "Woman1", "group": 2},
{"name": "Judge", "group": 2},
{"name": "Champmathieu", "group": 2},
{"name": "Brevet", "group": 2},
{"name": "Chenildieu", "group": 2},
{"name": "Cochepaille", "group": 2},
{"name": "Pontmercy", "group": 4},
{"name": "Boulatruelle", "group": 6},
{"name": "Eponine", "group": 4},
{"name": "Anzelma", "group": 4},
{"name": "Woman2", "group": 5},
{"name": "MotherInnocent", "group": 0},
{"name": "Gribier", "group": 0},
{"name": "Jondrette", "group": 7},
{"name": "Mme.Burgon", "group": 7},
{"name": "Gavroche", "group": 8},
{"name": "Gillenormand", "group": 5},
{"name": "Magnon", "group": 5},
{"name": "Mlle.Gillenormand", "group": 5},
{"name": "Mme.Pontmercy", "group": 5},
{"name": "Mlle.Vaubois", "group": 5},
{"name": "Lt.Gillenormand", "group": 5},
{"name": "Marius", "group": 8},
{"name": "BaronessT", "group": 5},
{"name": "Mabeuf", "group": 8},
{"name": "Enjolras", "group": 8},
{"name": "Combeferre", "group": 8},
{"name": "Prouvaire", "group": 8},
{"name": "Feuilly", "group": 8},
{"name": "Courfeyrac", "group": 8},
{"name": "Bahorel", "group": 8},
{"name": "Bossuet", "group": 8},
{"name": "Joly", "group": 8},
{"name": "Grantaire", "group": 8},
{"name": "MotherPlutarch", "group": 9},
{"name": "Gueulemer", "group": 4},
{"name": "Babet", "group": 4},
{"name": "Claquesous", "group": 4},
{"name": "Montparnasse", "group": 4},
{"name": "Toussaint", "group": 5},
{"name": "Child1", "group": 10},
{"name": "Child2", "group": 10},
{"name": "Brujon", "group": 4},
{"name": "Mme.Hucheloup", "group": 8}
],
"links": [
{"source":1,"target":1,"value":1}
]
}
/* global d3 */
// Credit https://github.com/john-guerra/forceInABox
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;
}
.links line {
stroke: #aaa;
}
.nodes circle {
pointer-events: all;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
</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>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 svg = svgm.append("g");
var useGroupInABox = true,
drawTemplate = true,
drawGrid = false,
snapGrid = false,
template = "force";
d3.select("#checkGroupInABox").property("checked", useGroupInABox);
d3.select("#checkshowGrid").property("checked", drawGrid);
d3.select("#snapGrid").property("checked", snapGrid);
d3.select("#checkShowTreemap").property("checked", drawTemplate);
d3.select("#selectTemplate").property("value", template);
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();
// 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);
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() {
var color = d3.scaleOrdinal(d3.schemeCategory20);
/*var svgm = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);*/
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;
});
}
// 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));
// node tooltip
node.append("svg:text")
.text(function(d) { return d.name; });
// 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;
});
} else {
}
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 (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);
});
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
});
d3.select("#alpha_value").style("flex-basis", (force.alpha()*100) + "%");
}
}
// 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);
}
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);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
(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("link", d3.forceLink(graph.links).distance(50).strength(
// function (l) { return !useGroupInABox? 0.7 :
// l.source.group!==l.target.group ? 0 : 0.1;
// }))
.force("group").enableGrouping(useGroupInABox)
force.alphaTarget(0.5).restart();
});
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