|
<!doctype html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>winter force</title> |
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
<script src="http://d3js.org/d3.v4.js"></script> |
|
|
|
<style type="text/css"> |
|
|
|
/* === Meyer Reset & border box (in moderation) === */ |
|
|
|
html, body { |
|
margin: 0; |
|
padding: 0; |
|
border: 0; |
|
font-family: 'Open Sans', sans-serif; |
|
font-size: 100%; |
|
vertical-align: baseline; |
|
height: 100%; |
|
box-sizing: border-box; |
|
} |
|
|
|
*, *:before, *:after{ |
|
box-sizing: inherit; |
|
} |
|
|
|
a:link, a:visited, a:hover, a:active { |
|
color: #000; |
|
} |
|
|
|
|
|
/* === Canvas === */ |
|
|
|
canvas { |
|
border: 1px solid #ccc; |
|
margin: 5px 50px; |
|
} |
|
|
|
h3, input#cluster1, button#split { |
|
margin-left: 50px; |
|
} |
|
|
|
|
|
button { |
|
margin: 0; |
|
padding: .5em; |
|
outline: none; |
|
font-size: .65em; |
|
color: #555; |
|
background-color: rgba(255, 255, 255, .7); |
|
border: none; |
|
transition: all 0.1s ease; |
|
} |
|
|
|
button:hover { |
|
background-color: rgba(204,204,204, .7); |
|
cursor: pointer; |
|
} |
|
|
|
|
|
|
|
</style> |
|
|
|
</head> |
|
<body> |
|
|
|
<h3>Force split and unite</h3> |
|
|
|
<input type="text" id="cluster1" value="1500"> |
|
<input type="text" id="cluster2" value="750"> |
|
<button id="submit">Submit</button> |
|
<div id="container"></div> |
|
<button id="split">Split</button> |
|
<button id="unite">Unite</button> |
|
<button id="bounded">Box-bound</button> |
|
|
|
<script> |
|
|
|
|
|
// === Globals === // |
|
|
|
var data; |
|
var width = 500, height = 250; |
|
var simulation, bounded = true; |
|
|
|
|
|
// === Set up canvas === // |
|
|
|
var canvas = d3.select('#container').append('canvas').attr('width', width).attr('height', height); |
|
|
|
var context = canvas.node().getContext('2d'); |
|
|
|
|
|
// === First call === // |
|
|
|
getSimulationData(1500, 750); |
|
|
|
// === Get simulation data === // |
|
|
|
function getSimulationData(cluster1, cluster2) { |
|
|
|
|
|
var nodes = []; |
|
|
|
d3.range(cluster1).forEach(function(el, i) { |
|
|
|
var obj = {}; |
|
obj.cluster = 0; |
|
obj.radius = 2; |
|
obj.colour = '#9CCFE5'; |
|
obj.x = width/2 + (Math.random() - 10); |
|
obj.y = height/2 + (Math.random()); |
|
|
|
nodes.push(obj); |
|
|
|
}); // get men nodes |
|
|
|
|
|
d3.range(cluster2).forEach(function(el, i) { |
|
|
|
var obj = {}; |
|
obj.cluster = 1; |
|
obj.radius = 2; |
|
obj.colour = '#9FE789'; |
|
obj.x = width/2 + (Math.random() + 10); |
|
obj.y = height/2 + (Math.random()); |
|
|
|
nodes.push(obj); |
|
|
|
}); // get women nodes |
|
|
|
|
|
initSimulation(nodes); // kick off simulation |
|
|
|
} // getSimulationData() |
|
|
|
|
|
|
|
// === Set up simulation params === // |
|
|
|
function initSimulation(nodes) { |
|
|
|
simulation = d3.forceSimulation(nodes) |
|
.alpha(0.3) |
|
.force('charge', d3.forceManyBody().strength(-5)) |
|
.force('xPos', d3.forceX(function(d) { return d.cluster === 0 ? width * 0.45 : width * 0.45; }).strength(0.7) ) |
|
.force('yPos', d3.forceY(height/2).strength(1)) |
|
.force('collide', d3.forceCollide(2)); |
|
|
|
simulation.on('tick', ticked); |
|
|
|
function ticked() { |
|
|
|
context.clearRect(0, 0, width, height); |
|
context.save(); |
|
|
|
nodes.forEach(drawNode); |
|
|
|
context.restore() |
|
|
|
} // ticked() |
|
|
|
|
|
function drawNode(d) { |
|
|
|
if (bounded) { |
|
// keep the nodes with the canvas bounds. Remove to let them free... |
|
d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); |
|
d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); |
|
} |
|
|
|
context.beginPath(); |
|
context.moveTo(d.x + d.radius, d.y); |
|
context.arc(d.x, d.y, d.radius, 0, 2 * Math.PI); |
|
context.fillStyle = d.colour; |
|
context.fill(); |
|
|
|
} // drawNode() |
|
|
|
|
|
} // Set up the simulation |
|
|
|
|
|
|
|
// === Listeners / Handlers === // |
|
|
|
|
|
d3.select('#submit').on('mousedown', function() { |
|
|
|
var cluster1 = parseInt(document.querySelector('#cluster1').value, 10); |
|
var cluster2 = parseInt(document.querySelector('#cluster2').value, 10); |
|
|
|
getSimulationData(cluster1, cluster2); |
|
|
|
}); // submit listener/handler - update new data |
|
|
|
|
|
d3.select('button#split').on('mousedown', function(d) { |
|
|
|
simulation.stop(); |
|
|
|
simulation |
|
.force('xPos', d3.forceX(function(d) { return d.cluster === 0 ? width * 0.3 : width * 0.7; }).strength(0.7) ) |
|
.force('yPos', d3.forceY(height/2).strength(0.7)); |
|
|
|
simulation.alpha(0.2); |
|
|
|
simulation.restart(); |
|
|
|
}); // split button listener/handler |
|
|
|
|
|
d3.select('button#unite').on('mousedown', function(d) { |
|
|
|
simulation.stop(); |
|
|
|
simulation |
|
.force('xPos', d3.forceX(function(d) { return d.cluster === 0 ? width * 0.5 : width * 0.5; }).strength(0.5)) |
|
.force('yPos', d3.forceY(height/2).strength(0.5)); |
|
|
|
simulation.alpha(0.2); |
|
|
|
simulation.restart(); |
|
|
|
}); // unite button listener/handler |
|
|
|
|
|
d3.select('button#bounded').on('mousedown', function(d) { |
|
|
|
simulation.stop(); |
|
|
|
bounded = bounded === false ? true : false; |
|
|
|
var b = d3.select('#bounded'); |
|
bounded ? b.html('Box-bound') : b.html('Not Box-bound'); |
|
|
|
simulation.restart(); |
|
|
|
|
|
}); // button listener/handler |
|
|
|
|
|
</script> |
|
|
|
</body> |
|
</html> |