Skip to content

Instantly share code, notes, and snippets.

@vasturiano
Last active October 22, 2017 04:15
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 vasturiano/ea5f3488bf649fa3a5567dbaef7bfe17 to your computer and use it in GitHub Desktop.
Save vasturiano/ea5f3488bf649fa3a5567dbaef7bfe17 to your computer and use it in GitHub Desktop.
Collision Forces Comparison
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.min.js"></script>
<script src="//unpkg.com/d3-force-bounce"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="canvas-wrapper">
<a class="canvas-title" href="https://github.com/vasturiano/d3-force-bounce">d3.forceBounce</a>
<div class="controls">
Elasticity:
<input class="slider" type="range" min="0" max="1" step="0.1" value="1" oninput="onControlChange(this.value, 'bounce', 'elasticity')">
<span class="val">1</span>
</div>
<svg id="canvasBounce">
<g class="trails"></g>
</svg>
</div>
<div class="canvas-wrapper">
<a class="canvas-title" href="https://github.com/d3/d3-force#forceCollide">d3.forceCollide</a>
<div class="controls">
Strength:
<input class="slider" type="range" min="0" max="1" step="0.1" value="1" oninput="onControlChange(this.value, 'collide', 'strength')">
<span class="val">1</span>
</div>
<svg id="canvasCollide">
<g class="trails"></g>
</svg>
</div>
<script src="index.js"></script>
</body>
const BALL_RADIUS = 8,
BALL_COLORS = ['green', 'blue'];
const canvasWidth = window.innerWidth/2 - 12,
canvasHeight = window.innerHeight - 8;
const state = {
bounce: { canvas: d3.select('svg#canvasBounce') },
collide: { canvas: d3.select('svg#canvasCollide') }
};
[state.bounce, state.collide].forEach(state => {
// Size canvi
state.canvas.attr('width', canvasWidth)
.attr('height', canvasHeight);
// Setup force system
state.forceSim = d3.forceSimulation()
.alphaDecay(0)
.velocityDecay(0)
.on('tick', () => { ballDigest(state); });
});
state.bounce.forceSim.force('collision', d3.forceBounce().elasticity(1));
state.collide.forceSim.force('collision', d3.forceCollide().strength(1));
// Set collision radius
[state.bounce, state.collide].forEach(state => {
state.forceSim.force('collision')
.radius(n => n.r || BALL_RADIUS);
});
// Periodical kickstart
kickStart();
setInterval(kickStart, 15000);
// Event handlers
function onControlChange(val, mode, prop) {
const module = state[mode];
d3.select(module.canvas.node().parentNode).select('.val').text(val);
module.forceSim.force('collision')[prop](val);
kickStart();
}
//
function ballDigest(state) {
let ball = state.canvas.selectAll('circle.ball').data(state.forceSim.nodes());
ball.exit().remove();
ball.merge(
ball.enter().append('circle')
.classed('ball', true)
.attr('fill', (d,idx) => BALL_COLORS[idx%BALL_COLORS.length])
)
.attr('r', d => d.r || BALL_RADIUS)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
// Update trails
const trailsG = state.canvas.select('.trails');
state.forceSim.nodes().forEach((node, idx) => {
trailsG.append('circle')
.attr('r', 1)
.attr('cx', node.x)
.attr('cy', node.y)
.attr('fill', BALL_COLORS[idx%BALL_COLORS.length])
.style('opacity', 0.2)
.transition().delay(30000)
.remove();
});
}
function kickStart() {
const numExamples = 7,
h = [ 0.25, 0.5, 0.75].map(r => canvasWidth * r),
v = d3.range(numExamples).map(n => canvasHeight * (n+0.5)/numExamples);
// Clear all trails
d3.selectAll('.trails').selectAll('*').remove();
[state.bounce, state.collide].forEach(state => {
const sim = state.forceSim,
balls = [
{x: h[0], y: v[0] , future: { vx: 3 }}, {x: h[1], y: v[0]},
{x: h[0], y: v[1] - 3 , future: { vx: 3 }}, {x: h[1], y: v[1]},
{x: h[0], y: v[2] , future: { vx: 3 }}, {x: h[2], y: v[2], future: { vx: -3 }},
{x: h[0], y: v[3] , future: { vx: 6 }}, {x: h[2], y: v[3], future: { vx: -2 }},
{x: h[0], y: v[4], r: BALL_RADIUS*4 , future: { vx: 3 }}, {x: h[2], y: v[4], future: { vx: -3 }},
{x: h[0], y: v[5] - BALL_RADIUS , future: { vx: 3 }}, {x: h[2], y: v[5], future: { vx: -3 }},
{x: h[0], y: v[6] , future: { vx: 100 }}, {x: h[1], y: v[6]}
];
// Initial state
sim.nodes(balls);
setTimeout(() => {
// Apply future
balls.filter(ball => ball.future).forEach(ball => {
Object.keys(ball.future).forEach(attr => { ball[attr] = ball.future[attr]});
});
}, 800);
});
}
body {
margin: 0;
text-align: center;
font-family: sans-serif;
}
.canvas-wrapper {
position: relative;
display: inline-block;
box-sizing: border-box;
border: 1px solid grey;
margin-top: 3px;
}
.canvas-title {
position: absolute;
top: 10px;
left: 10px;
font-size: 18px;
}
.controls {
position: absolute;
right: 0;
margin: 8px;
padding: 1px 5px 5px 5px;
background: rgba(230, 230, 250, 0.7);
opacity: 0.5;
border-radius: 3px;
font-size: 14px;
z-index: 1000;
}
.controls:hover {
opacity: 1;
}
.slider {
position: relative;
top: 3px;
cursor: grab;
cursor: -webkit-grab;
}
.slider:active {
cursor: grabbing;
cursor: -webkit-grabbing;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment