|
var nPendulums = 60; |
|
|
|
var pendulums = d3.range(nPendulums).map(x => new Pendulum({m2: 1 + 0.01*x/nPendulums, theta1:0.75*Math.PI})) |
|
|
|
var fadeBackground = true; |
|
|
|
|
|
var svg = d3.select("svg") |
|
width = +svg.attr("width"), |
|
height = +svg.attr("height"), |
|
g = svg.append("g").attr("transform", "translate(" + width*.5 + "," + height*.5 + ")"); |
|
color = d3.scaleSequential(d3.interpolateRainbow).domain([0, nPendulums]); |
|
|
|
svg.on('click', e => { |
|
var mousePos = d3.mouse(svg.node()); |
|
reset(mousePos); |
|
}); |
|
|
|
var canvas = d3.select("canvas"); |
|
var context = canvas.node().getContext('2d'); |
|
|
|
var scale = d3.scaleLinear().domain([0,1]).range([0,100]) |
|
|
|
var path = d3.line() |
|
.x(function(d) { return scale(d.l1*Math.sin(d.theta1)+d.l2*Math.sin(d.theta2)); }) |
|
.y(function(d) { return scale(d.l1*Math.cos(d.theta1)+d.l2*Math.cos(d.theta2)); }) |
|
|
|
var update = function() { |
|
var oldCoords = pendulums.map(p => p.getCoords()); |
|
|
|
pendulums.forEach(p => p.evolve()); |
|
|
|
var coords = pendulums.map(p => p.getCoords()); |
|
draw(oldCoords, coords); |
|
} |
|
|
|
var trailOpacity = 1; |
|
var maxThetaDelta = 0; |
|
var opacityScale = d3.scaleLinear().domain([0, 2*Math.PI]).range([1, 0]) |
|
|
|
var draw = function(oldCoords, coords) { |
|
if (maxThetaDelta < 2*Math.PI) { |
|
if (fadeBackground) { |
|
maxThetaDelta = Math.max(maxThetaDelta, Math.abs(d3.max(pendulums, d => d.theta1) - d3.min(pendulums, d => d.theta1))) |
|
//trailOpacity -= maxThetaDelta / 1500; |
|
trailOpacity = opacityScale(maxThetaDelta) |
|
//trailOpacity = opacityScale(Math.abs(pendulums[nPendulums - 1].theta1 - pendulums[0].theta1)) |
|
} |
|
|
|
canvas.style('opacity', trailOpacity); |
|
} |
|
|
|
for (var i = coords.length - 1; i >= 0; i--) { |
|
context.beginPath(); |
|
context.strokeStyle = color(i); |
|
context.lineWidth = 2; |
|
context.moveTo(scale(oldCoords[i].x2) + width/2, scale(oldCoords[i].y2) + height/2); |
|
context.lineTo(scale(coords[i].x2) + width/2, scale(coords[i].y2) + height/2); |
|
context.stroke(); |
|
} |
|
|
|
var pendulum = g.selectAll(".pendulum").data(coords, function(d, i) { return i; }) |
|
|
|
var pendulumEnter = pendulum.enter() |
|
.append("g").attr("class","pendulum") |
|
|
|
pendulumEnter.append("line").attr("class", "firstShaft shaft") |
|
pendulumEnter.append("line").attr("class", "secondShaft shaft") |
|
pendulumEnter.append("circle").attr("class", "firstBob bob").attr("r",3) |
|
pendulumEnter.append("circle").attr("class", "secondBob bob").attr("r",7) |
|
|
|
var shaft1 = pendulum.select(".firstShaft") |
|
.attr("x1", 0) |
|
.attr("y1", 0) |
|
.attr("x2", d => scale(d.x1)) |
|
.attr("y2", d => scale(d.y1)) |
|
.attr('stroke', (d, i) => color(i)) |
|
|
|
var shaft2 = pendulum.select(".secondShaft") |
|
.attr("x1", d => scale(d.x1)) |
|
.attr("y1", d => scale(d.y1)) |
|
.attr("x2", d => scale(d.x2)) |
|
.attr("y2", d => scale(d.y2)) |
|
.attr('stroke', (d, i) => color(i)) |
|
|
|
var bob1 = pendulum.select(".firstBob") |
|
.attr("cx", d => scale(d.x1)) |
|
.attr("cy", d => scale(d.y1)) |
|
.attr('fill', (d, i) => color(i)) |
|
.attr('opacity', 1) |
|
var bob2 = pendulum.select(".secondBob") |
|
.attr("cx", d => scale(d.x2)) |
|
.attr("cy", d => scale(d.y2)) |
|
.attr('fill', (d, i) => color(i)) |
|
.attr('stroke', (d, i) => d3.color(color(i)).darker()) |
|
.attr('stroke-width', 2) |
|
|
|
} |
|
|
|
var reset = function(mousePos) { |
|
console.log(mousePos) |
|
var theta1 = 0.5*Math.PI + Math.atan2(height/2 - mousePos[1], mousePos[0] - width/2) |
|
trailOpacity = 1; |
|
maxThetaDelta = 0; |
|
pendulums = d3.range(nPendulums).map(x => new Pendulum({m2: 1 + 0.01*x/nPendulums, theta1:theta1})); |
|
context.clearRect(0, 0, width, height); |
|
} |
|
|
|
var run = setInterval(() => { update() }, 2); |
|
|