Skip to content

Instantly share code, notes, and snippets.

@vasturiano
Last active October 10, 2021 13:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save vasturiano/0a05e58d5122cde888793c374d587aac to your computer and use it in GitHub Desktop.
Save vasturiano/0a05e58d5122cde888793c374d587aac to your computer and use it in GitHub Desktop.
Newton's Cradle
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.min.js"></script>
<script src="//unpkg.com/d3-force-bounce"></script>
<script src="//unpkg.com/d3-force-constant"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<svg id="canvas">
<defs>
<radialGradient id="sphere-gradient">
<stop offset="0%" stop-color="slategrey"></stop>
<stop offset="100%" stop-color="darkslategrey"></stop>
</radialGradient>
</defs>
</svg>
<script src="index.js"></script>
</body>
const BALL_RADIUS = 20,
WIRE_LENGTH = window.innerHeight/ 2,
GRAVITY = 0.6;
const canvasWidth = window.innerWidth,
canvasHeight = window.innerHeight;
// DOM nodes
const svgCanvas = d3.select('svg#canvas')
.attr('width', canvasWidth)
.attr('height', canvasHeight),
wiresG = svgCanvas.append('g'),
ballsG = svgCanvas.append('g');
svgCanvas.append('rect')
.attr('x', canvasWidth/2 - 4*BALL_RADIUS - 10)
.attr('y', canvasHeight*2/3 - WIRE_LENGTH - 5)
.attr('width', BALL_RADIUS*8 + 20)
.attr('height', 10)
.attr('rx', 5)
.attr('ry', 5)
.attr('fill', 'slategrey');
const balls = [
{ id: '0', init: { x: canvasWidth/2 - 4*BALL_RADIUS, y: canvasHeight*2/3 } },
{ id: '1', init: { x: canvasWidth/2 - 2*BALL_RADIUS, y: canvasHeight*2/3 } },
{ id: '2', init: { x: canvasWidth/2 - 0*BALL_RADIUS, y: canvasHeight*2/3 } },
{ id: '3', init: { x: canvasWidth/2 + 2*BALL_RADIUS, y: canvasHeight*2/3 } },
{ id: '4', init: { x: canvasWidth/2 + 4*BALL_RADIUS, y: canvasHeight*2/3 } }
],
anchors = balls.map(ball => ({
id: 'a' + ball.id,
fx: ball.init.x,
fy: ball.init.y - WIRE_LENGTH
})),
wires = balls.map(ball => ({
source: ball.id,
target: 'a' + ball.id
}));
// swing it
balls[0].init.x -= WIRE_LENGTH;
balls[0].init.y -= WIRE_LENGTH;
let init = false;
const forceSim = d3.forceSimulation()
.alphaDecay(0)
.velocityDecay(0)
.nodes([...balls, ...anchors])
.force('gravity', d3.forceConstant()
.strength(GRAVITY)
.direction(90)
)
.force('wires', d3.forceLink(wires)
.id(node => node.id)
.distance(WIRE_LENGTH)
.strength(0.4)
)
.force('bounce', d3.forceBounce()
.radius(node => BALL_RADIUS)
)
.force('init', () => {
if (!init) {
balls.forEach((ball) => {
ball.x = ball.init.x;
ball.y = ball.init.y;
ball.vx = 0;
ball.vy = 0;
});
init = true;
}
})
.on('tick', () => { ballDigest(); wireDigest(); });
//
function ballDigest() {
let ball = ballsG.selectAll('circle.ball').data(balls);
ball.exit().remove();
ball.merge(
ball.enter().append('circle')
.classed('ball', true)
.attr('r', BALL_RADIUS)
.attr('fill', 'url(#sphere-gradient)')
.call(d3.drag()
.on("start", d => { d.fx = d.x; d.fy = d.y; })
.on("drag", d => { d.fx = d3.event.x; d.fy = d3.event.y; })
.on("end", d => { d.fx = null; d.fy = null; })
)
)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
}
function wireDigest() {
let wire = wiresG.selectAll('line.wire').data(wires);
wire.exit().remove();
wire.merge(
wire.enter().append('line')
.classed('wire', true)
.attr('stroke', 'slategrey')
.attr('stroke-width', 1)
)
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
}
body {
margin: 0;
text-align: center;
}
.ball {
cursor: grab;
cursor: -webkit-grab;
}
.ball:active {
cursor: grabbing;
cursor: -webkit-grabbing;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment