Related
I've been trying to add a (code pen) animation on my website and I'm honestly not sure what I'm missing on this one. I have tried running it in jsfiddle as well and it tells me that delaunay is not defined. https://codepen.io/hduffin1/pen/QOMZJg I'm not too sure what I'm doing wrong since the code works inside of code pen and I have been able to replicate other ones that I've tried using from code pen but for whatever reason, I can't seem to get this one to work.
Html
<canvas id="stars" width="300" height="300"></canvas>
CSS
html,
body {
margin: 0;
padding: 0;
}
body {
background-color: #31102f; //#280B29
background: radial-gradient(
ellipse at center,
rgba(49, 16, 47, 1) 0%,
rgba(40, 11, 41, 1) 100%
);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 16rem;
height: 100vh;
z-index: 1;
}
JS
/**
* Stars
* Inspired by Steve Courtney's poster art for Celsius GS's Drifter - http://celsiusgs.com/drifter/posters.php
* by Cory Hughart - http://coryhughart.com
*/
// Settings
var particleCount = 40,
flareCount = 10,
motion = 0.05,
tilt = 0.05,
color = '#FFEED4',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 1, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.05,
renderParticles = true,
renderParticleGlare = true,
renderFlares = true,
renderLinks = true,
renderMesh = false,
flicker = true,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 1;
var canvas = document.getElementById('stars'),
//orbits = document.getElementById('orbits'),
context = canvas.getContext('2d'),
mouse = { x: 0, y: 0 },
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {x: 0, y: 0},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
// Fade in background
/*
var background = document.getElementById('background'),
bgImg = new Image(),
bgURL = '/img/background.jpg';
bgImg.onload = function() {
//console.log('background loaded');
background.style.backgroundImage = 'url("'+bgURL+'")';
background.className += ' loaded';
}
bgImg.src = bgURL;
*/
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x*c, p.y*c]);
}
//console.log(JSON.stringify(points));
// Delaunay triangulation
//var Delaunay = require('delaunay-fast');
vertices = Delaunay.triangulate(points);
//console.log(JSON.stringify(vertices));
// Create an array of "triangles" (groups of 3 indices)
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
//console.log(JSON.stringify(triangles));
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
//console.log(JSON.stringify(particles));
if (renderFlares) {
// Create flare positions
for (i = 0; i < flareCount; i++) {
flares.push(new Flare());
}
}
// Motion mode
//if (Modernizr && Modernizr.deviceorientation) {
if ('ontouchstart' in document.documentElement && window.DeviceOrientationEvent) {
console.log('Using device orientation');
window.addEventListener('deviceorientation', function(e) {
mouse.x = (canvas.clientWidth / 2) - ((e.gamma / 90) * (canvas.clientWidth / 2) * 2);
mouse.y = (canvas.clientHeight / 2) - ((e.beta / 90) * (canvas.clientHeight / 2) * 2);
//console.log('Center: x:'+(canvas.clientWidth/2)+' y:'+(canvas.clientHeight/2));
//console.log('Orientation: x:'+mouse.x+' ('+e.gamma+') y:'+mouse.y+' ('+e.beta+')');
}, true);
}
else {
// Mouse move listener
console.log('Using mouse movement');
document.body.addEventListener('mousemove', function(e) {
//console.log('moved');
mouse.x = e.clientX;
mouse.y = e.clientY;
});
}
// Random motion
if (randomMotion) {
//var SimplexNoise = require('simplex-noise');
//var simplex = new SimplexNoise();
}
// Animation loop
(function animloop(){
requestAnimFrame(animloop);
resize();
render();
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
//console.log('NOISE x:'+nPos.x+' y:'+nPos.y);
}
// Clear
context.clearRect(0, 0, canvas.width, canvas.height);
if (blurSize > 0) {
context.shadowBlur = blurSize;
context.shadowColor = color;
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
if (renderMesh) {
// Render all lines
context.beginPath();
for (var v = 0; v < vertices.length-1; v++) {
// Splits the array into triplets
if ((v + 1) % 3 === 0) { continue; }
var p1 = particles[vertices[v]],
p2 = particles[vertices[v+1]];
//console.log('Line: '+p1.x+','+p1.y+'->'+p2.x+','+p2.y);
var pos1 = position(p1.x, p1.y, p1.z),
pos2 = position(p2.x, p2.y, p2.z);
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x, pos2.y);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
}
if (renderLinks) {
// Possibly start a new link
if (random(0, linkChance) == linkChance) {
var length = random(linkLengthMin, linkLengthMax);
var start = random(0, particles.length-1);
startLink(start, length);
}
// Render existing links
// Iterate in reverse so that removing items doesn't affect the loop
for (var l = links.length-1; l >= 0; l--) {
if (links[l] && !links[l].finished) {
links[l].render();
}
else {
delete links[l];
}
}
}
if (renderFlares) {
// Render flares
for (var j = 0; j < flareCount; j++) {
flares[j].render();
}
}
/*
if (orbitTilt) {
var tiltX = -(((canvas.clientWidth / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * tilt),
tiltY = (((canvas.clientHeight / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * tilt);
orbits.style.transform = 'rotateY('+tiltX+'deg) rotateX('+tiltY+'deg)';
}
*/
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
function startLink(vertex, length) {
//console.log('LINK from '+vertex+' (length '+length+')');
links.push(new Link(vertex, length));
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0,4);
this.color = color;
this.opacity = random(0.1,1,true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
if (flicker) {
var newVal = random(-0.5, 0.5, true);
this.flicker += (newVal - this.flicker) / flickerSmoothing;
if (this.flicker > 0.5) this.flicker = 0.5;
if (this.flicker < -0.5) this.flicker = -0.5;
o += this.flicker;
if (o > 1) o = 1;
if (o < 0) o = 0;
}
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
/*
context.ellipse(pos.x, pos.y, r * 30, r, 90 * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
*/
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
var Flare = function() {
this.x = random(-0.25, 1.25, true);
this.y = random(-0.25, 1.25, true);
this.z = random(0,2);
this.color = color;
this.opacity = random(0.001, 0.01, true);
};
Flare.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * flareSizeMultiplier) + flareSizeBase) * (sizeRatio() / 1000);
// Feathered circles
/*
var grad = context.createRadialGradient(x+r,y+r,0,x+r,y+r,r);
grad.addColorStop(0, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(0.8, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = grad;
context.beginPath();
context.fillRect(x, y, r*2, r*2);
context.closePath();
*/
context.beginPath();
context.globalAlpha = this.opacity;
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.globalAlpha = 1;
};
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
Link.prototype.render = function() {
// Stages:
// 0. Vertex collection
// 1. Render line reaching from vertex to vertex
// 2. Fade out
// 3. Finished (delete me)
var i, p, pos, points;
switch (this.stage) {
// VERTEX COLLECTION STAGE
case 0:
// Grab the last member of the link
var last = particles[this.verts[this.verts.length-1]];
//console.log(JSON.stringify(last));
if (last && last.neighbors && last.neighbors.length > 0) {
// Grab a random neighbor
var neighbor = last.neighbors[random(0, last.neighbors.length-1)];
// If we haven't seen that particle before, add it to the link
if (this.verts.indexOf(neighbor) == -1) {
this.verts.push(neighbor);
}
// If we have seen that article before, we'll just wait for the next frame
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (0)');
this.stage = 3;
this.finished = true;
}
if (this.verts.length >= this.length) {
// Calculate all distances at once
for (i = 0; i < this.verts.length-1; i++) {
var p1 = particles[this.verts[i]],
p2 = particles[this.verts[i+1]],
dx = p1.x - p2.x,
dy = p1.y - p2.y,
dist = Math.sqrt(dx*dx + dy*dy);
this.distances.push(dist);
}
//console.log('Distances: '+JSON.stringify(this.distances));
//console.log('verts: '+this.verts.length+' distances: '+this.distances.length);
//console.log(this.verts[0]+' moving to stage 1');
this.stage = 1;
}
break;
// RENDER LINE ANIMATION STAGE
case 1:
if (this.distances.length > 0) {
points = [];
//var a = 1;
// Gather all points already linked
for (i = 0; i < this.linked.length; i++) {
p = particles[this.linked[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
var linkSpeedRel = linkSpeed * 0.00001 * canvas.width;
this.traveled += linkSpeedRel;
var d = this.distances[this.linked.length-1];
// Calculate last point based on linkSpeed and distance travelled to next point
if (this.traveled >= d) {
this.traveled = 0;
// We've reached the next point, add coordinates to array
//console.log(this.verts[0]+' reached vertex '+(this.linked.length+1)+' of '+this.verts.length);
this.linked.push(this.verts[this.linked.length]);
p = particles[this.linked[this.linked.length-1]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
if (this.linked.length >= this.verts.length) {
//console.log(this.verts[0]+' moving to stage 2 (1)');
this.stage = 2;
}
}
else {
// We're still travelling to the next point, get coordinates at travel distance
// http://math.stackexchange.com/a/85582
var a = particles[this.linked[this.linked.length-1]],
b = particles[this.verts[this.linked.length]],
t = d - this.traveled,
x = ((this.traveled * b.x) + (t * a.x)) / d,
y = ((this.traveled * b.y) + (t * a.y)) / d,
z = ((this.traveled * b.z) + (t * a.z)) / d;
pos = position(x, y, z);
//console.log(this.verts[0]+' traveling to vertex '+(this.linked.length+1)+' of '+this.verts.length+' ('+this.traveled+' of '+this.distances[this.linked.length]+')');
points.push([pos.x, pos.y]);
}
this.drawLine(points);
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (1)');
this.stage = 3;
this.finished = true;
}
break;
// FADE OUT STAGE
case 2:
if (this.verts.length > 1) {
if (this.fade < linkFade) {
this.fade++;
// Render full link between all vertices and fade over time
points = [];
var alpha = (1 - (this.fade / linkFade)) * linkOpacity;
for (i = 0; i < this.verts.length; i++) {
p = particles[this.verts[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
this.drawLine(points, alpha);
}
else {
//console.log(this.verts[0]+' moving to stage 3 (2a)');
this.stage = 3;
this.finished = true;
}
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (2b)');
this.stage = 3;
this.finished = true;
}
break;
// FINISHED STAGE
case 3:
default:
this.finished = true;
break;
}
};
Link.prototype.drawLine = function(points, alpha) {
if (typeof alpha !== 'number') alpha = linkOpacity;
if (points.length > 1 && alpha > 0) {
//console.log(this.verts[0]+': Drawing line '+alpha);
context.globalAlpha = alpha;
context.beginPath();
for (var i = 0; i < points.length-1; i++) {
context.moveTo(points[i][0], points[i][1]);
context.lineTo(points[i+1][0], points[i+1][1]);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
context.globalAlpha = 1;
}
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
//value = simplex.noise2D(nScale * cosA + nScale, nScale * sinA + nScale),
//rad = nRad + value;
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
When I entered 'https://codepen.io/hduffin1/pen/QOMZJg', 'delaunay.js' is included in the setting.
Add the following script and it should work.
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
/**
* Stars
* Inspired by Steve Courtney's poster art for Celsius GS's Drifter - http://celsiusgs.com/drifter/posters.php
* by Cory Hughart - http://coryhughart.com
*/
// Settings
var particleCount = 40,
flareCount = 10,
motion = 0.05,
tilt = 0.05,
color = '#FFEED4',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 1, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.05,
renderParticles = true,
renderParticleGlare = true,
renderFlares = true,
renderLinks = true,
renderMesh = false,
flicker = true,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 1;
var canvas = document.getElementById('stars'),
//orbits = document.getElementById('orbits'),
context = canvas.getContext('2d'),
mouse = { x: 0, y: 0 },
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {x: 0, y: 0},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
// Fade in background
/*
var background = document.getElementById('background'),
bgImg = new Image(),
bgURL = '/img/background.jpg';
bgImg.onload = function() {
//console.log('background loaded');
background.style.backgroundImage = 'url("'+bgURL+'")';
background.className += ' loaded';
}
bgImg.src = bgURL;
*/
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x*c, p.y*c]);
}
//console.log(JSON.stringify(points));
// Delaunay triangulation
//var Delaunay = require('delaunay-fast');
vertices = Delaunay.triangulate(points);
//console.log(JSON.stringify(vertices));
// Create an array of "triangles" (groups of 3 indices)
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
//console.log(JSON.stringify(triangles));
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
//console.log(JSON.stringify(particles));
if (renderFlares) {
// Create flare positions
for (i = 0; i < flareCount; i++) {
flares.push(new Flare());
}
}
// Motion mode
//if (Modernizr && Modernizr.deviceorientation) {
if ('ontouchstart' in document.documentElement && window.DeviceOrientationEvent) {
console.log('Using device orientation');
window.addEventListener('deviceorientation', function(e) {
mouse.x = (canvas.clientWidth / 2) - ((e.gamma / 90) * (canvas.clientWidth / 2) * 2);
mouse.y = (canvas.clientHeight / 2) - ((e.beta / 90) * (canvas.clientHeight / 2) * 2);
//console.log('Center: x:'+(canvas.clientWidth/2)+' y:'+(canvas.clientHeight/2));
//console.log('Orientation: x:'+mouse.x+' ('+e.gamma+') y:'+mouse.y+' ('+e.beta+')');
}, true);
}
else {
// Mouse move listener
console.log('Using mouse movement');
document.body.addEventListener('mousemove', function(e) {
//console.log('moved');
mouse.x = e.clientX;
mouse.y = e.clientY;
});
}
// Random motion
if (randomMotion) {
//var SimplexNoise = require('simplex-noise');
//var simplex = new SimplexNoise();
}
// Animation loop
(function animloop(){
requestAnimFrame(animloop);
resize();
render();
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
//console.log('NOISE x:'+nPos.x+' y:'+nPos.y);
}
// Clear
context.clearRect(0, 0, canvas.width, canvas.height);
if (blurSize > 0) {
context.shadowBlur = blurSize;
context.shadowColor = color;
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
if (renderMesh) {
// Render all lines
context.beginPath();
for (var v = 0; v < vertices.length-1; v++) {
// Splits the array into triplets
if ((v + 1) % 3 === 0) { continue; }
var p1 = particles[vertices[v]],
p2 = particles[vertices[v+1]];
//console.log('Line: '+p1.x+','+p1.y+'->'+p2.x+','+p2.y);
var pos1 = position(p1.x, p1.y, p1.z),
pos2 = position(p2.x, p2.y, p2.z);
context.moveTo(pos1.x, pos1.y);
context.lineTo(pos2.x, pos2.y);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
}
if (renderLinks) {
// Possibly start a new link
if (random(0, linkChance) == linkChance) {
var length = random(linkLengthMin, linkLengthMax);
var start = random(0, particles.length-1);
startLink(start, length);
}
// Render existing links
// Iterate in reverse so that removing items doesn't affect the loop
for (var l = links.length-1; l >= 0; l--) {
if (links[l] && !links[l].finished) {
links[l].render();
}
else {
delete links[l];
}
}
}
if (renderFlares) {
// Render flares
for (var j = 0; j < flareCount; j++) {
flares[j].render();
}
}
/*
if (orbitTilt) {
var tiltX = -(((canvas.clientWidth / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * tilt),
tiltY = (((canvas.clientHeight / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * tilt);
orbits.style.transform = 'rotateY('+tiltX+'deg) rotateX('+tiltY+'deg)';
}
*/
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
function startLink(vertex, length) {
//console.log('LINK from '+vertex+' (length '+length+')');
links.push(new Link(vertex, length));
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0,4);
this.color = color;
this.opacity = random(0.1,1,true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
if (flicker) {
var newVal = random(-0.5, 0.5, true);
this.flicker += (newVal - this.flicker) / flickerSmoothing;
if (this.flicker > 0.5) this.flicker = 0.5;
if (this.flicker < -0.5) this.flicker = -0.5;
o += this.flicker;
if (o > 1) o = 1;
if (o < 0) o = 0;
}
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
/*
context.ellipse(pos.x, pos.y, r * 30, r, 90 * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
*/
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
var Flare = function() {
this.x = random(-0.25, 1.25, true);
this.y = random(-0.25, 1.25, true);
this.z = random(0,2);
this.color = color;
this.opacity = random(0.001, 0.01, true);
};
Flare.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * flareSizeMultiplier) + flareSizeBase) * (sizeRatio() / 1000);
// Feathered circles
/*
var grad = context.createRadialGradient(x+r,y+r,0,x+r,y+r,r);
grad.addColorStop(0, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(0.8, 'rgba(255,255,255,'+f.o+')');
grad.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = grad;
context.beginPath();
context.fillRect(x, y, r*2, r*2);
context.closePath();
*/
context.beginPath();
context.globalAlpha = this.opacity;
context.arc(pos.x, pos.y, r, 0, 2 * Math.PI, false);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.globalAlpha = 1;
};
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
Link.prototype.render = function() {
// Stages:
// 0. Vertex collection
// 1. Render line reaching from vertex to vertex
// 2. Fade out
// 3. Finished (delete me)
var i, p, pos, points;
switch (this.stage) {
// VERTEX COLLECTION STAGE
case 0:
// Grab the last member of the link
var last = particles[this.verts[this.verts.length-1]];
//console.log(JSON.stringify(last));
if (last && last.neighbors && last.neighbors.length > 0) {
// Grab a random neighbor
var neighbor = last.neighbors[random(0, last.neighbors.length-1)];
// If we haven't seen that particle before, add it to the link
if (this.verts.indexOf(neighbor) == -1) {
this.verts.push(neighbor);
}
// If we have seen that particle before, we'll just wait for the next frame
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (0)');
this.stage = 3;
this.finished = true;
}
if (this.verts.length >= this.length) {
// Calculate all distances at once
for (i = 0; i < this.verts.length-1; i++) {
var p1 = particles[this.verts[i]],
p2 = particles[this.verts[i+1]],
dx = p1.x - p2.x,
dy = p1.y - p2.y,
dist = Math.sqrt(dx*dx + dy*dy);
this.distances.push(dist);
}
//console.log('Distances: '+JSON.stringify(this.distances));
//console.log('verts: '+this.verts.length+' distances: '+this.distances.length);
//console.log(this.verts[0]+' moving to stage 1');
this.stage = 1;
}
break;
// RENDER LINE ANIMATION STAGE
case 1:
if (this.distances.length > 0) {
points = [];
//var a = 1;
// Gather all points already linked
for (i = 0; i < this.linked.length; i++) {
p = particles[this.linked[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
var linkSpeedRel = linkSpeed * 0.00001 * canvas.width;
this.traveled += linkSpeedRel;
var d = this.distances[this.linked.length-1];
// Calculate last point based on linkSpeed and distance travelled to next point
if (this.traveled >= d) {
this.traveled = 0;
// We've reached the next point, add coordinates to array
//console.log(this.verts[0]+' reached vertex '+(this.linked.length+1)+' of '+this.verts.length);
this.linked.push(this.verts[this.linked.length]);
p = particles[this.linked[this.linked.length-1]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
if (this.linked.length >= this.verts.length) {
//console.log(this.verts[0]+' moving to stage 2 (1)');
this.stage = 2;
}
}
else {
// We're still travelling to the next point, get coordinates at travel distance
// http://math.stackexchange.com/a/85582
var a = particles[this.linked[this.linked.length-1]],
b = particles[this.verts[this.linked.length]],
t = d - this.traveled,
x = ((this.traveled * b.x) + (t * a.x)) / d,
y = ((this.traveled * b.y) + (t * a.y)) / d,
z = ((this.traveled * b.z) + (t * a.z)) / d;
pos = position(x, y, z);
//console.log(this.verts[0]+' traveling to vertex '+(this.linked.length+1)+' of '+this.verts.length+' ('+this.traveled+' of '+this.distances[this.linked.length]+')');
points.push([pos.x, pos.y]);
}
this.drawLine(points);
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (1)');
this.stage = 3;
this.finished = true;
}
break;
// FADE OUT STAGE
case 2:
if (this.verts.length > 1) {
if (this.fade < linkFade) {
this.fade++;
// Render full link between all vertices and fade over time
points = [];
var alpha = (1 - (this.fade / linkFade)) * linkOpacity;
for (i = 0; i < this.verts.length; i++) {
p = particles[this.verts[i]];
pos = position(p.x, p.y, p.z);
points.push([pos.x, pos.y]);
}
this.drawLine(points, alpha);
}
else {
//console.log(this.verts[0]+' moving to stage 3 (2a)');
this.stage = 3;
this.finished = true;
}
}
else {
//console.log(this.verts[0]+' prematurely moving to stage 3 (2b)');
this.stage = 3;
this.finished = true;
}
break;
// FINISHED STAGE
case 3:
default:
this.finished = true;
break;
}
};
Link.prototype.drawLine = function(points, alpha) {
if (typeof alpha !== 'number') alpha = linkOpacity;
if (points.length > 1 && alpha > 0) {
//console.log(this.verts[0]+': Drawing line '+alpha);
context.globalAlpha = alpha;
context.beginPath();
for (var i = 0; i < points.length-1; i++) {
context.moveTo(points[i][0], points[i][1]);
context.lineTo(points[i+1][0], points[i+1][1]);
}
context.strokeStyle = color;
context.lineWidth = lineWidth;
context.stroke();
context.closePath();
context.globalAlpha = 1;
}
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
//value = simplex.noise2D(nScale * cosA + nScale, nScale * sinA + nScale),
//rad = nRad + value;
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
html,
body {
margin: 0;
padding: 0;
}
body {
background-color: #31102F;
background: radial-gradient(ellipse at center, #31102f 0%, #280b29 100%);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 16rem;
height: 100vh;
z-index: 1;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<canvas id="stars" width="300" height="300"></canvas>
I am using this chart, with the data in the original var oData it works fantastic. But when I add my values the cart is drawn anywhere but not is the chart it self. The strange thing is, when I change this original data the chart is updated correctly! Any suggestions?
Original data with no issues:
var oData = {
"2008": 10,
"2009": 39.9,
"2010": 17,
"2011": 30.0,
"2012": 5.3,
"2013": 38.4,
"2014": 15.7,
"2015": 9.0
};
When I change this data to there are no issues:
var oData = {
"2008": 100,
"2009": 390.9,
"2010": 170,
"2011": 300.0,
"2012": 50.3,
"2013": 380.4,
"2014": 150.7,
"2015": 90.0
};
My data:
var oData = {
"1220": 262,
"1120": 338,
"1020": 244,
"0920": 314,
"0820": 311,
"0720": 302,
"0620": 300,
"0520": 269,
"0420": 232,
"0320": 347
};
var label = document.querySelector(".label");
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 700;
var ch = c.height = 350;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var frames = 0;
ctx.lineWidth = 1;
ctx.strokeStyle = "#999";
ctx.fillStyle = "#ccc";
ctx.font = "14px monospace";
var grd = ctx.createLinearGradient(0, 0, 0, cy);
grd.addColorStop(0, "hsla(167,72%,60%,1)");
grd.addColorStop(1, "hsla(167,72%,60%,0)");
var oData = {
"2008": 10,
"2009": 39.9,
"2010": 17,
"2011": 30.0,
"2012": 5.3,
"2013": 38.4,
"2014": 15.7,
"2015": 9.0
};
var valuesRy = [];
var propsRy = [];
for (var prop in oData) {
valuesRy.push(oData[prop]);
propsRy.push(prop);
}
var vData = 4;
var hData = valuesRy.length;
var offset = 50.5; //offset chart axis
var chartHeight = ch - 2 * offset;
var chartWidth = cw - 2 * offset;
var t = 1 / 7; // curvature : 0 = no curvature
var speed = 2; // for the animation
var A = {
x: offset,
y: offset
}
var B = {
x: offset,
y: offset + chartHeight
}
var C = {
x: offset + chartWidth,
y: offset + chartHeight
}
/*
A ^
| |
+ 25
|
|
|
+ 25
|__|_________________________________ C
B
*/
// CHART AXIS -------------------------
ctx.beginPath();
ctx.moveTo(A.x, A.y);
ctx.lineTo(B.x, B.y);
ctx.lineTo(C.x, C.y);
ctx.stroke();
// vertical ( A - B )
var aStep = (chartHeight - 50) / (vData);
var Max = Math.ceil(arrayMax(valuesRy) / 10) * 10;
var Min = Math.floor(arrayMin(valuesRy) / 10) * 10;
var aStepValue = (Max - Min) / (vData);
console.log("aStepValue: " + aStepValue); //8 units
var verticalUnit = aStep / aStepValue;
var a = [];
ctx.textAlign = "right";
ctx.textBaseline = "middle";
for (var i = 0; i <= vData; i++) {
if (i == 0) {
a[i] = {
x: A.x,
y: A.y + 25,
val: Max
}
} else {
a[i] = {}
a[i].x = a[i - 1].x;
a[i].y = a[i - 1].y + aStep;
a[i].val = a[i - 1].val - aStepValue;
}
drawCoords(a[i], 3, 0);
}
//horizontal ( B - C )
var b = [];
ctx.textAlign = "center";
ctx.textBaseline = "hanging";
var bStep = chartWidth / (hData + 1);
for (var i = 0; i < hData; i++) {
if (i == 0) {
b[i] = {
x: B.x + bStep,
y: B.y,
val: propsRy[0]
};
} else {
b[i] = {}
b[i].x = b[i - 1].x + bStep;
b[i].y = b[i - 1].y;
b[i].val = propsRy[i]
}
drawCoords(b[i], 0, 3)
}
function drawCoords(o, offX, offY) {
ctx.beginPath();
ctx.moveTo(o.x - offX, o.y - offY);
ctx.lineTo(o.x + offX, o.y + offY);
ctx.stroke();
ctx.fillText(o.val, o.x - 2 * offX, o.y + 2 * offY);
}
//----------------------------------------------------------
// DATA
var oDots = [];
var oFlat = [];
var i = 0;
for (var prop in oData) {
oDots[i] = {}
oFlat[i] = {}
oDots[i].x = b[i].x;
oFlat[i].x = b[i].x;
oDots[i].y = b[i].y - oData[prop] * verticalUnit - 25;
oFlat[i].y = b[i].y - 25;
oDots[i].val = oData[b[i].val];
i++
}
///// Animation Chart ///////////////////////////
//var speed = 3;
function animateChart() {
requestId = window.requestAnimationFrame(animateChart);
frames += speed; //console.log(frames)
ctx.clearRect(60, 0, cw, ch - 60);
for (var i = 0; i < oFlat.length; i++) {
if (oFlat[i].y > oDots[i].y) {
oFlat[i].y -= speed;
}
}
drawCurve(oFlat);
for (var i = 0; i < oFlat.length; i++) {
ctx.fillText(oDots[i].val, oFlat[i].x, oFlat[i].y - 25);
ctx.beginPath();
ctx.arc(oFlat[i].x, oFlat[i].y, 3, 0, 2 * Math.PI);
ctx.fill();
}
if (frames >= Max * verticalUnit) {
window.cancelAnimationFrame(requestId);
}
}
requestId = window.requestAnimationFrame(animateChart);
/////// EVENTS //////////////////////
c.addEventListener("mousemove", function(e) {
label.innerHTML = "";
label.style.display = "none";
this.style.cursor = "default";
var m = oMousePos(this, e);
for (var i = 0; i < oDots.length; i++) {
output(m, i);
}
}, false);
function output(m, i) {
ctx.beginPath();
ctx.arc(oDots[i].x, oDots[i].y, 20, 0, 2 * Math.PI);
if (ctx.isPointInPath(m.x, m.y)) {
//console.log(i);
label.style.display = "block";
label.style.top = (m.y + 10) + "px";
label.style.left = (m.x + 10) + "px";
label.innerHTML = "<strong>" + propsRy[i] + "</strong>: " + valuesRy[i] + "%";
c.style.cursor = "pointer";
}
}
// CURVATURE
function controlPoints(p) {
// given the points array p calculate the control points
var pc = [];
for (var i = 1; i < p.length - 1; i++) {
var dx = p[i - 1].x - p[i + 1].x; // difference x
var dy = p[i - 1].y - p[i + 1].y; // difference y
// the first control point
var x1 = p[i].x - dx * t;
var y1 = p[i].y - dy * t;
var o1 = {
x: x1,
y: y1
};
// the second control point
var x2 = p[i].x + dx * t;
var y2 = p[i].y + dy * t;
var o2 = {
x: x2,
y: y2
};
// building the control points array
pc[i] = [];
pc[i].push(o1);
pc[i].push(o2);
}
return pc;
}
function drawCurve(p) {
var pc = controlPoints(p); // the control points array
ctx.beginPath();
//ctx.moveTo(p[0].x, B.y- 25);
ctx.lineTo(p[0].x, p[0].y);
// the first & the last curve are quadratic Bezier
// because I'm using push(), pc[i][1] comes before pc[i][0]
ctx.quadraticCurveTo(pc[1][1].x, pc[1][1].y, p[1].x, p[1].y);
if (p.length > 2) {
// central curves are cubic Bezier
for (var i = 1; i < p.length - 2; i++) {
ctx.bezierCurveTo(pc[i][0].x, pc[i][0].y, pc[i + 1][1].x, pc[i + 1][1].y, p[i + 1].x, p[i + 1].y);
}
// the first & the last curve are quadratic Bezier
var n = p.length - 1;
ctx.quadraticCurveTo(pc[n - 1][0].x, pc[n - 1][0].y, p[n].x, p[n].y);
}
//ctx.lineTo(p[p.length-1].x, B.y- 25);
ctx.stroke();
ctx.save();
ctx.fillStyle = grd;
ctx.fill();
ctx.restore();
}
function arrayMax(array) {
return Math.max.apply(Math, array);
};
function arrayMin(array) {
return Math.min.apply(Math, array);
};
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
body {
margin: 0;
overflow: hidden;
background: #152B39;
font-family: Courier, monospace;
font-size: 14px;
color:#ccc;
}
.wrapper {
display: block;
margin: 5em auto;
border: 1px solid #555;
width: 700px;
height: 350px;
position: relative;
}
p{text-align:center;}
.label {
height: 1em;
padding: .3em;
background: rgba(255, 255, 255, .8);
position: absolute;
display: none;
color:#333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<canvas id='c'></canvas>
<div class="label">text</div>
</div>
<p>Please mouse over the dots</p>
var label = document.querySelector(".label");
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 700;
var ch = c.height = 350;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var frames = 0;
ctx.lineWidth = 1;
ctx.strokeStyle = "#999";
ctx.fillStyle = "#ccc";
ctx.font = "14px monospace";
var grd = ctx.createLinearGradient(0, 0, 0, cy);
grd.addColorStop(0, "hsla(167,72%,60%,1)");
grd.addColorStop(1, "hsla(167,72%,60%,0)");
var oData = {
"2008": 100,
"2009": 390.9,
"2010": 170,
"2011": 300.0,
"2012": 50.3,
"2013": 380.4,
"2014": 150.7,
"2015": 90.0
};
var valuesRy = [];
var propsRy = [];
for (var prop in oData) {
valuesRy.push(oData[prop]);
propsRy.push(prop);
}
var vData = 4;
var hData = valuesRy.length;
var offset = 50.5; //offset chart axis
var chartHeight = ch - 2 * offset;
var chartWidth = cw - 2 * offset;
var t = 1 / 7; // curvature : 0 = no curvature
var speed = 2; // for the animation
var A = {
x: offset,
y: offset
}
var B = {
x: offset,
y: offset + chartHeight
}
var C = {
x: offset + chartWidth,
y: offset + chartHeight
}
/*
A ^
| |
+ 25
|
|
|
+ 25
|__|_________________________________ C
B
*/
// CHART AXIS -------------------------
ctx.beginPath();
ctx.moveTo(A.x, A.y);
ctx.lineTo(B.x, B.y);
ctx.lineTo(C.x, C.y);
ctx.stroke();
// vertical ( A - B )
var aStep = (chartHeight - 50) / (vData);
var Max = Math.ceil(arrayMax(valuesRy) / 10) * 10;
var Min = Math.floor(arrayMin(valuesRy) / 10) * 10;
var aStepValue = (Max - Min) / (vData);
console.log("aStepValue: " + aStepValue); //8 units
var verticalUnit = aStep / aStepValue;
var a = [];
ctx.textAlign = "right";
ctx.textBaseline = "middle";
for (var i = 0; i <= vData; i++) {
if (i == 0) {
a[i] = {
x: A.x,
y: A.y + 25,
val: Max
}
} else {
a[i] = {}
a[i].x = a[i - 1].x;
a[i].y = a[i - 1].y + aStep;
a[i].val = a[i - 1].val - aStepValue;
}
drawCoords(a[i], 3, 0);
}
//horizontal ( B - C )
var b = [];
ctx.textAlign = "center";
ctx.textBaseline = "hanging";
var bStep = chartWidth / (hData + 1);
for (var i = 0; i < hData; i++) {
if (i == 0) {
b[i] = {
x: B.x + bStep,
y: B.y,
val: propsRy[0]
};
} else {
b[i] = {}
b[i].x = b[i - 1].x + bStep;
b[i].y = b[i - 1].y;
b[i].val = propsRy[i]
}
drawCoords(b[i], 0, 3)
}
function drawCoords(o, offX, offY) {
ctx.beginPath();
ctx.moveTo(o.x - offX, o.y - offY);
ctx.lineTo(o.x + offX, o.y + offY);
ctx.stroke();
ctx.fillText(o.val, o.x - 2 * offX, o.y + 2 * offY);
}
//----------------------------------------------------------
// DATA
var oDots = [];
var oFlat = [];
var i = 0;
for (var prop in oData) {
oDots[i] = {}
oFlat[i] = {}
oDots[i].x = b[i].x;
oFlat[i].x = b[i].x;
oDots[i].y = b[i].y - oData[prop] * verticalUnit - 25;
oFlat[i].y = b[i].y - 25;
oDots[i].val = oData[b[i].val];
i++
}
///// Animation Chart ///////////////////////////
//var speed = 3;
function animateChart() {
requestId = window.requestAnimationFrame(animateChart);
frames += speed; //console.log(frames)
ctx.clearRect(60, 0, cw, ch - 60);
for (var i = 0; i < oFlat.length; i++) {
if (oFlat[i].y > oDots[i].y) {
oFlat[i].y -= speed;
}
}
drawCurve(oFlat);
for (var i = 0; i < oFlat.length; i++) {
ctx.fillText(oDots[i].val, oFlat[i].x, oFlat[i].y - 25);
ctx.beginPath();
ctx.arc(oFlat[i].x, oFlat[i].y, 3, 0, 2 * Math.PI);
ctx.fill();
}
if (frames >= Max * verticalUnit) {
window.cancelAnimationFrame(requestId);
}
}
requestId = window.requestAnimationFrame(animateChart);
/////// EVENTS //////////////////////
c.addEventListener("mousemove", function(e) {
label.innerHTML = "";
label.style.display = "none";
this.style.cursor = "default";
var m = oMousePos(this, e);
for (var i = 0; i < oDots.length; i++) {
output(m, i);
}
}, false);
function output(m, i) {
ctx.beginPath();
ctx.arc(oDots[i].x, oDots[i].y, 20, 0, 2 * Math.PI);
if (ctx.isPointInPath(m.x, m.y)) {
//console.log(i);
label.style.display = "block";
label.style.top = (m.y + 10) + "px";
label.style.left = (m.x + 10) + "px";
label.innerHTML = "<strong>" + propsRy[i] + "</strong>: " + valuesRy[i] + "%";
c.style.cursor = "pointer";
}
}
// CURVATURE
function controlPoints(p) {
// given the points array p calculate the control points
var pc = [];
for (var i = 1; i < p.length - 1; i++) {
var dx = p[i - 1].x - p[i + 1].x; // difference x
var dy = p[i - 1].y - p[i + 1].y; // difference y
// the first control point
var x1 = p[i].x - dx * t;
var y1 = p[i].y - dy * t;
var o1 = {
x: x1,
y: y1
};
// the second control point
var x2 = p[i].x + dx * t;
var y2 = p[i].y + dy * t;
var o2 = {
x: x2,
y: y2
};
// building the control points array
pc[i] = [];
pc[i].push(o1);
pc[i].push(o2);
}
return pc;
}
function drawCurve(p) {
var pc = controlPoints(p); // the control points array
ctx.beginPath();
//ctx.moveTo(p[0].x, B.y- 25);
ctx.lineTo(p[0].x, p[0].y);
// the first & the last curve are quadratic Bezier
// because I'm using push(), pc[i][1] comes before pc[i][0]
ctx.quadraticCurveTo(pc[1][1].x, pc[1][1].y, p[1].x, p[1].y);
if (p.length > 2) {
// central curves are cubic Bezier
for (var i = 1; i < p.length - 2; i++) {
ctx.bezierCurveTo(pc[i][0].x, pc[i][0].y, pc[i + 1][1].x, pc[i + 1][1].y, p[i + 1].x, p[i + 1].y);
}
// the first & the last curve are quadratic Bezier
var n = p.length - 1;
ctx.quadraticCurveTo(pc[n - 1][0].x, pc[n - 1][0].y, p[n].x, p[n].y);
}
//ctx.lineTo(p[p.length-1].x, B.y- 25);
ctx.stroke();
ctx.save();
ctx.fillStyle = grd;
ctx.fill();
ctx.restore();
}
function arrayMax(array) {
return Math.max.apply(Math, array);
};
function arrayMin(array) {
return Math.min.apply(Math, array);
};
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
body {
margin: 0;
overflow: hidden;
background: #152B39;
font-family: Courier, monospace;
font-size: 14px;
color:#ccc;
}
.wrapper {
display: block;
margin: 5em auto;
border: 1px solid #555;
width: 700px;
height: 350px;
position: relative;
}
p{text-align:center;}
.label {
height: 1em;
padding: .3em;
background: rgba(255, 255, 255, .8);
position: absolute;
display: none;
color:#333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<canvas id='c'></canvas>
<div class="label">text</div>
</div>
<p>Please mouse over the dots</p>
var label = document.querySelector(".label");
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 700;
var ch = c.height = 350;
var cx = cw / 2,
cy = ch / 2;
var rad = Math.PI / 180;
var frames = 0;
ctx.lineWidth = 1;
ctx.strokeStyle = "#999";
ctx.fillStyle = "#ccc";
ctx.font = "14px monospace";
var grd = ctx.createLinearGradient(0, 0, 0, cy);
grd.addColorStop(0, "hsla(167,72%,60%,1)");
grd.addColorStop(1, "hsla(167,72%,60%,0)");
var oData = {
"1220": 262,
"1120": 338,
"1020": 244,
"0920": 314,
"0820": 311,
"0720": 302,
"0620": 300,
"0520": 269,
"0420": 232,
"0320": 347
};
var valuesRy = [];
var propsRy = [];
for (var prop in oData) {
valuesRy.push(oData[prop]);
propsRy.push(prop);
}
var vData = 4;
var hData = valuesRy.length;
var offset = 50.5; //offset chart axis
var chartHeight = ch - 2 * offset;
var chartWidth = cw - 2 * offset;
var t = 1 / 7; // curvature : 0 = no curvature
var speed = 2; // for the animation
var A = {
x: offset,
y: offset
}
var B = {
x: offset,
y: offset + chartHeight
}
var C = {
x: offset + chartWidth,
y: offset + chartHeight
}
/*
A ^
| |
+ 25
|
|
|
+ 25
|__|_________________________________ C
B
*/
// CHART AXIS -------------------------
ctx.beginPath();
ctx.moveTo(A.x, A.y);
ctx.lineTo(B.x, B.y);
ctx.lineTo(C.x, C.y);
ctx.stroke();
// vertical ( A - B )
var aStep = (chartHeight - 50) / (vData);
var Max = Math.ceil(arrayMax(valuesRy) / 10) * 10;
var Min = Math.floor(arrayMin(valuesRy) / 10) * 10;
var aStepValue = (Max - Min) / (vData);
console.log("aStepValue: " + aStepValue); //8 units
var verticalUnit = aStep / aStepValue;
var a = [];
ctx.textAlign = "right";
ctx.textBaseline = "middle";
for (var i = 0; i <= vData; i++) {
if (i == 0) {
a[i] = {
x: A.x,
y: A.y + 25,
val: Max
}
} else {
a[i] = {}
a[i].x = a[i - 1].x;
a[i].y = a[i - 1].y + aStep;
a[i].val = a[i - 1].val - aStepValue;
}
drawCoords(a[i], 3, 0);
}
//horizontal ( B - C )
var b = [];
ctx.textAlign = "center";
ctx.textBaseline = "hanging";
var bStep = chartWidth / (hData + 1);
for (var i = 0; i < hData; i++) {
if (i == 0) {
b[i] = {
x: B.x + bStep,
y: B.y,
val: propsRy[0]
};
} else {
b[i] = {}
b[i].x = b[i - 1].x + bStep;
b[i].y = b[i - 1].y;
b[i].val = propsRy[i]
}
drawCoords(b[i], 0, 3)
}
function drawCoords(o, offX, offY) {
ctx.beginPath();
ctx.moveTo(o.x - offX, o.y - offY);
ctx.lineTo(o.x + offX, o.y + offY);
ctx.stroke();
ctx.fillText(o.val, o.x - 2 * offX, o.y + 2 * offY);
}
//----------------------------------------------------------
// DATA
var oDots = [];
var oFlat = [];
var i = 0;
for (var prop in oData) {
oDots[i] = {}
oFlat[i] = {}
oDots[i].x = b[i].x;
oFlat[i].x = b[i].x;
oDots[i].y = b[i].y - oData[prop] * verticalUnit - 25;
oFlat[i].y = b[i].y - 25;
oDots[i].val = oData[b[i].val];
i++
}
///// Animation Chart ///////////////////////////
//var speed = 3;
function animateChart() {
requestId = window.requestAnimationFrame(animateChart);
frames += speed; //console.log(frames)
ctx.clearRect(60, 0, cw, ch - 60);
for (var i = 0; i < oFlat.length; i++) {
if (oFlat[i].y > oDots[i].y) {
oFlat[i].y -= speed;
}
}
drawCurve(oFlat);
for (var i = 0; i < oFlat.length; i++) {
ctx.fillText(oDots[i].val, oFlat[i].x, oFlat[i].y - 25);
ctx.beginPath();
ctx.arc(oFlat[i].x, oFlat[i].y, 3, 0, 2 * Math.PI);
ctx.fill();
}
if (frames >= Max * verticalUnit) {
window.cancelAnimationFrame(requestId);
}
}
requestId = window.requestAnimationFrame(animateChart);
/////// EVENTS //////////////////////
c.addEventListener("mousemove", function(e) {
label.innerHTML = "";
label.style.display = "none";
this.style.cursor = "default";
var m = oMousePos(this, e);
for (var i = 0; i < oDots.length; i++) {
output(m, i);
}
}, false);
function output(m, i) {
ctx.beginPath();
ctx.arc(oDots[i].x, oDots[i].y, 20, 0, 2 * Math.PI);
if (ctx.isPointInPath(m.x, m.y)) {
//console.log(i);
label.style.display = "block";
label.style.top = (m.y + 10) + "px";
label.style.left = (m.x + 10) + "px";
label.innerHTML = "<strong>" + propsRy[i] + "</strong>: " + valuesRy[i] + "%";
c.style.cursor = "pointer";
}
}
// CURVATURE
function controlPoints(p) {
// given the points array p calculate the control points
var pc = [];
for (var i = 1; i < p.length - 1; i++) {
var dx = p[i - 1].x - p[i + 1].x; // difference x
var dy = p[i - 1].y - p[i + 1].y; // difference y
// the first control point
var x1 = p[i].x - dx * t;
var y1 = p[i].y - dy * t;
var o1 = {
x: x1,
y: y1
};
// the second control point
var x2 = p[i].x + dx * t;
var y2 = p[i].y + dy * t;
var o2 = {
x: x2,
y: y2
};
// building the control points array
pc[i] = [];
pc[i].push(o1);
pc[i].push(o2);
}
return pc;
}
function drawCurve(p) {
var pc = controlPoints(p); // the control points array
ctx.beginPath();
//ctx.moveTo(p[0].x, B.y- 25);
ctx.lineTo(p[0].x, p[0].y);
// the first & the last curve are quadratic Bezier
// because I'm using push(), pc[i][1] comes before pc[i][0]
ctx.quadraticCurveTo(pc[1][1].x, pc[1][1].y, p[1].x, p[1].y);
if (p.length > 2) {
// central curves are cubic Bezier
for (var i = 1; i < p.length - 2; i++) {
ctx.bezierCurveTo(pc[i][0].x, pc[i][0].y, pc[i + 1][1].x, pc[i + 1][1].y, p[i + 1].x, p[i + 1].y);
}
// the first & the last curve are quadratic Bezier
var n = p.length - 1;
ctx.quadraticCurveTo(pc[n - 1][0].x, pc[n - 1][0].y, p[n].x, p[n].y);
}
//ctx.lineTo(p[p.length-1].x, B.y- 25);
ctx.stroke();
ctx.save();
ctx.fillStyle = grd;
ctx.fill();
ctx.restore();
}
function arrayMax(array) {
return Math.max.apply(Math, array);
};
function arrayMin(array) {
return Math.min.apply(Math, array);
};
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
body {
margin: 0;
overflow: hidden;
background: #152B39;
font-family: Courier, monospace;
font-size: 14px;
color:#ccc;
}
.wrapper {
display: block;
margin: 5em auto;
border: 1px solid #555;
width: 700px;
height: 350px;
position: relative;
}
p{text-align:center;}
.label {
height: 1em;
padding: .3em;
background: rgba(255, 255, 255, .8);
position: absolute;
display: none;
color:#333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<canvas id='c'></canvas>
<div class="label">text</div>
</div>
<p>Please mouse over the dots</p>
N-Body gravity simulation seems to be working fine at first glance, and the same is true for body collisions, but once gravitationally attracted objects start to collide, they start to spiral around each other frantically and the collection of them as a whole have very erratic motion... The code (html-javascript) will be included below, and to reproduce what I'm talking about, you can create a new body by clicking in a random location on the screen.
The math for gravitational attraction is done in the Body.prototype.gravityCalc() method of the Body object type (line 261). The math for the collision resolution is found in the dynamic collision section of the bodyHandle() function (line 337).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// event handling
document.addEventListener('keydown', keyDown);
document.addEventListener('mousedown', mouseDown)
document.addEventListener('mouseup', mouseUp)
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove', touchMove);
document.addEventListener('touchend', touchEnd);
window.addEventListener('resize', resize);
window.onload = function() {reset()}
mouseDown = false;
nothingGrabbed = true;
mouseX = 0;
mouseY = 0;
function keyDown(data) {
if(data.key == "r") {
clearInterval(loop);
reset();
}
else if(data.key == 'g') {
gravityOn = !gravityOn;
}
else if(data.key == 'Delete') {
for(i = 0; i < bodies.length ; i++) {
if(((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies.splice(i, 1);
}
}
}
else if(data.key == 'c') {
gravity_c *= -1;
}
else if(data.key == 'f') {
falling = !falling;
}
else if(data.key == 'a') {
acceleration *= -1;
}
}
function mouseDown(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function mouseUp(data) {
mouseDown = false;
nothingGrabbed = true;
for(i = 0; i < bodies.length; i++) {
bodies[i].grabbed = false
}
}
function mouseMove(data) {
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function touchStart(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchMove(data) {
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchEnd(data) {
mouseDown = false;
nothingGrabbed = true;
for(i=0;i<bodies.length;i++) {
bodies[i].grabbed = false;
}
}
function resize(data) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize Variables
function reset() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.color = 'rgb(70, 70, 70)';
scale = Math.min(canvas.width, canvas.height);
fps = 120;
running = true;
loop = setInterval(main, 1000/fps);
gravityOn = true // if true, objects are gravitationally attracted to each other
gravity_c = 334000 // universe's gravitational constant
boundaryCollision = true // if true, objects collide with edges of canvas
wallDampen = 0.7 // number to multiply by when an objects hit a wall
bodyCollision = true // if true, bodies will collide with each other
bodyDampen = 0.4 // number to multiply when two objects collide
falling = false // if true, objects will fall to the bottom of the screen
acceleration = 400
bodies = [] // a list of each Body object
collidingPairs = [] // a list of pairs of colliding bodies
/*
var bounds = 200;
for(i = 0; i<70; i++) { // randomly place bodies
Body.create({
x: Math.floor(Math.random()*canvas.width),
y: Math.floor(Math.random()*canvas.height),
a: Math.random()*Math.PI*2,
xV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
yV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
mass: Math.ceil(Math.random()*23)
})
} */
/*
Body.create({
x: canvas.width/2 - 50,
xV: 10,
yV: 0,
aV: 3,
y: canvas.height/2 + 0,
mass: 10
});
Body.create({
x: canvas.width/2 + 50,
xV: 0,
aV: 0,
y: canvas.height/2,
mass: 10
});
*/
Body.create({
x: canvas.width/2,
y: canvas.height/2,
mass: 24,
xV: -10.83
});
Body.create({
x: canvas.width/2,
y: canvas.height/2 + 150,
mass: 1,
xV: 260,
color: 'teal'
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Body Type Object
function Body(params) {
this.x = params.x || canvas.width/2;
this.y = params.y || canvas.height/2;
this.a = params.a || 0;
this.xV = params.xV || 0;
this.yV = params.yV || 0;
this.aV = params.aV || 0;
this.xA = params.xA || 0;
this.yA = params.yA || 0;
this.aA = params.aA || 0;
this.grabbed = false;
this.edgeBlock = params.edgeBlock || boundaryCollision;
this.gravity = params.gravityOn || gravityOn;
this.mass = params.mass || 6;
this.density = params.density || 0.008;
this.radius = params.radius || (this.mass/(Math.PI*this.density))**0.5;
this.color = params.color || 'crimson';
this.lineWidth = params.lineWidth || 2;
}
Body.create = function(params) {
bodies.push(new Body(params));
}
Body.prototype.move = function() {
this.xV += this.xA/fps;
this.yV += this.yA/fps;
this.aV += this.aA/fps;
this.x += this.xV/fps;
this.y += this.yV/fps;
this.a += this.aV/fps;
if(this.edgeBlock) {
if(this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.xV *= -wallDampen
}
else if(this.x - this.radius < 0) {
this.x = this.radius;
this.xV *= -wallDampen;
}
if(this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.yV *= -wallDampen;
}
else if(this.y - this.radius < 0) {
this.y = this.radius;
this.yV *= -wallDampen;
}
}
if(this.grabbed) {
this.xA = 0;
this.yA = 0;
this.xV = 0;
this.yV = 0;
this.x = mouseX;
this.y = mouseY;
}
}
Body.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.arc(this.x, canvas.height - this.y, this.radius, 0, Math.PI*2, true);
ctx.fill();
ctx.stroke();
ctx.closePath()
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.linewidth;
ctx.moveTo(this.x, canvas.height - this.y);
ctx.lineTo(this.x + this.radius*Math.cos(this.a), canvas.height - (this.y + this.radius*Math.sin(this.a)))
ctx.stroke();
ctx.closePath();
}
// calculates gravitational attraction to 'otherObject'
Body.prototype.gravityCalc = function(otherObject) {
var x1 = this.x;
var y1 = this.y;
var x2 = otherObject.x;
var y2 = otherObject.y;
var distSquare = ((x2-x1)**2 + (y2-y1)**2);
var val = (gravity_c*otherObject.mass)/((distSquare)**(3/2));
var xA = val * (x2 - x1);
var yA = val * (y2 - y1);
return [xA, yA]
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Physics Code
function bodyHandle() {
for(i = 0; i < bodies.length; i++) {
if(mouseDown && nothingGrabbed) {
if(Math.abs((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies[i].grabbed = true;
nothingGrabbed = false;
}
}
bodies[i].draw()
if(running) {
if(falling) {
bodies[i].yV -= acceleration/fps;
}
bodies[i].move();
}
bodies[i].xA = 0;
bodies[i].yA = 0;
collidingPairs = []
if(gravityOn || bodyCollision) {
for(b = 0; b < bodies.length; b++) {
if(i != b) {
if(bodyCollision) {
var x1 = bodies[i].x;
var y1 = bodies[i].y;
var x2 = bodies[b].x;
var y2 = bodies[b].y;
var rSum = bodies[i].radius + bodies[b].radius;
var dist = { // vector
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
if(dist.mag <= rSum) { // static collision
var overlap = rSum - dist.mag;
bodies[i].x -= overlap/2 * dist.norm.i;
bodies[i].y -= overlap/2 * dist.norm.j;
bodies[b].x += overlap/2 * dist.norm.i;
bodies[b].y += overlap/2 * dist.norm.j;
collidingPairs.push([bodies[i], bodies[b]]);
}
}
if(gravityOn) {
if(bodies[i].gravity) {
var accel = bodies[i].gravityCalc(bodies[b]);
bodies[i].xA += accel[0];
bodies[i].yA += accel[1];
}
}
}
}
}
for(c = 0; c < collidingPairs.length; c++) { // dynamic collision
var x1 = collidingPairs[c][0].x;
var y1 = collidingPairs[c][0].y;
var r1 = collidingPairs[c][0].radius;
var x2 = collidingPairs[c][1].x;
var y2 = collidingPairs[c][1].y;
var r2 = collidingPairs[c][1].radius;
var dist = { // vector from b1 to b2
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
var m1 = collidingPairs[c][0].mass;
var m2 = collidingPairs[c][1].mass;
var norm = { // vector normal along 'wall' of collision
i: -dist.j/(((dist.i)**2 + (-dist.j)**2)**0.5),
j: dist.i/(((dist.i)**2 + (-dist.j)**2)**0.5)
}
var perp = { // vector normal pointing from b1 to b2
i: dist.norm.i,
j: dist.norm.j
}
var vel1 = { // vector of b1 velocity
i: collidingPairs[c][0].xV,
j: collidingPairs[c][0].yV,
dot: function(vect) {
return collidingPairs[c][0].xV * vect.i + collidingPairs[c][0].yV * vect.j
}
}
var vel2 = { // vector of b2 velocity
i: collidingPairs[c][1].xV,
j: collidingPairs[c][1].yV,
dot: function(vect) {
return collidingPairs[c][1].xV * vect.i + collidingPairs[c][1].yV * vect.j
}
}
// new velocities along perp^ of b1 and b2
var nV1Perp = (vel1.dot(perp))*(m1-m2)/(m1+m2) + (vel2.dot(perp))*(2*m2)/(m1+m2);
var nV2Perp = (vel1.dot(perp))*(2*m1)/(m1+m2) + (vel2.dot(perp))*(m2-m1)/(m1+m2);
/* testing rotation after collision
// velocities of the points of collision on b1 and b2
var pVel1M = vel1.dot(norm) + collidingPairs[c][0].aV*r1;
var pVel2M = vel2.dot(norm) + collidingPairs[c][1].aV*r2;
// moment of inertia for b1 and b2
var I1 = 1/2 * m1 * r1**2;
var I2 = 1/2 * m2 * r2**2;
// new velocities of the points of collisions on b1 and b2
var newpVel1M = ((I1-I2)/(I1+I2))*pVel1M + ((2*I2)/(I1+I2))*pVel2M;
var newpVel2M = ((2*I1)/(I1+I2))*pVel1M + ((I2-I1)/(I1+I2))*pVel2M;
var vectToCol1 = { // vector from x1,y1 to point of collision on b1
i: r1*perp.i,
j: r1*perp.j
};
var vectToCol2 = { // vector from x2,y2 to point of collision on b2
i: r2*-perp.i,
j: r2*-perp.j
};
// sign of cross product of pVelM and vectToCol
var vCrossR1 = (pVel1M*norm.i)*(vectToCol1.j) - (pVel1M*norm.j)*(vectToCol1.i);
vCrossR1 = vCrossR1/Math.abs(vCrossR1);
var vCrossR2 = (pVel2M*norm.i)*(vectToCol2.j) - (pVel2M*norm.j)*(vectToCol2.i);
vCrossR2 = vCrossR2/Math.abs(vCrossR2);
collidingPairs[c][0].aV = vCrossR1 * (newpVel1M)/r1;
collidingPairs[c][1].aV = vCrossR2 * (newpVel2M)/r2;
/* draw collision point velocity vectors [debugging]
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x1 + vectToCol1.i, canvas.height - (y1 + vectToCol1.j));
ctx.lineTo((x1+vectToCol1.i) + pVel1M*norm.i, (canvas.height- (y1+vectToCol1.j + pVel1M*norm.j)));
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'white';
ctx.moveTo(x2 + vectToCol2.i, canvas.height - (y2 + vectToCol2.j));
ctx.lineTo((x2+vectToCol2.i) + pVel2M*norm.i, (canvas.height- (y2+vectToCol2.j + pVel2M*norm.j)));
ctx.stroke();
ctx.closePath();
console.log(pVel1M, pVel2M);
clearInterval(loop);
*/
collidingPairs[c][0].xV = vel1.dot(norm)*norm.i + nV1Perp*perp.i * bodyDampen;
collidingPairs[c][0].yV = vel1.dot(norm)*norm.j + nV1Perp*perp.j * bodyDampen;
collidingPairs[c][1].xV = vel2.dot(norm)*norm.i + nV2Perp*perp.i * bodyDampen;
collidingPairs[c][1].yV = vel2.dot(norm)*norm.j + nV2Perp*perp.j * bodyDampen;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Loop
function main() {
// blank out canvas
ctx.fillStyle = canvas.color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
bodyHandle();
if(nothingGrabbed && mouseDown) {
bodies.push(new Body({x: mouseX,
y: mouseY,
mass: 90}));
bodies[bodies.length-1].move();
bodies[bodies.length-1].draw();
}
}
<html>
<meta name='viewport' content='width=device-width,height=device-height'>
<body>
<canvas id="canvas" width='300px' height='300px'></canvas>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
padding: 0;
margin: 0;
}
</style>
</html>
I cannot tell you much about the code. Personally it seems to me that the animations could be correct.
If you want to test your code you could try to test if laws of conservation of energy and momentum are respected. You could, for example, sum the momentum of every object (mass times velocity) and see if the number are maintained constant when there are no forces from the outside (collisions with the wall). To do this I would suggest to make the free space available larger. Another quantity is the total energy (kinetic plus potential) which is a bit harder, but still easy to compute (to compute tot. pot. energy you have to sum over all pairs).
I'm trying to resolve circle to circle collisions. The problem is, the balls never settle to the bottom -- they continue to jiggle around. What can I do to make the balls settle to the bottom at the end of their cycle (without jiggling)?
CodePen: https://codepen.io/KriegersVan/pen/gOpXzwE?editors=0010
var _bouncingBalls = (function() {
document.getElementById("canvas").width = window.innerWidth;
document.getElementById("canvas").height = window.innerHeight;
function _randomNumBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function _negativeOrPositiveOne() {
return Math.random() < 0.50 ? -1 : 1;
}
function _random10Chars() {
return Math.random().toString(36).substr(2, 10);
}
var _balls = {
numOf: 10,
colors: ["#6a00ff", "#9500ff", "#d000ff", "#ff00ff", "#00f2ff", "#f59b42", "#e9f542"],
particles: [],
ctx: document.getElementById("canvas").getContext("2d"),
lastFrameTime: 0,
startTime: 0,
inProgress: false,
init: function(x, y) {
for (var i = 0; i < this.numOf; i++) {
this.particles.push({
x: x+(i*(window.innerWidth * 0.02)), //So they don't overlap at the beginning
y: y,
vy: Math.random() * _negativeOrPositiveOne(),
vx: 2 * Math.random() * _negativeOrPositiveOne(),
color: this.colors[_randomNumBetween(0, this.colors.length - 1)],
radius: window.innerWidth * 0.008,
gravity: 0.70,
damping: 0.50,
id: _random10Chars() + _random10Chars()
});
}
this.startTime = window.performance.now();
if (!this.inProgress) {
this.inProgress = true;
window.requestAnimationFrame(this.render);
}
},
isBallCollision: function(particle) {
//https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
for (var j = 0; j < _balls.particles.length; j++) {
var comparisonParticle = _balls.particles[j];
var dx = particle.x - comparisonParticle.x;
var dy = particle.y - comparisonParticle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (particle.id === comparisonParticle.id) {
continue;
}
var minDist = comparisonParticle.radius + particle.radius;
if (distance < minDist) {
//https://www.youtube.com/watch?v=nlwtgvZCz0k
var distance_x = dx;
var distance_y = dy;
var radii_sum = particle.radius+comparisonParticle.radius;
var length = Math.sqrt(distance_x * distance_x + distance_y * distance_y) || 1;
var unit_x = distance_x/length;
var unit_y = distance_y/length;
particle.x = comparisonParticle.x+(radii_sum+1)*unit_x;
particle.y = comparisonParticle.y+(radii_sum+1)*unit_y;
return comparisonParticle;
}
}
return null;
},
isBottomHit: function(obj, comparisonObj){
return obj.y+obj.radius > comparisonObj.y-comparisonObj.radius &&
obj.y+obj.radius < comparisonObj.y+comparisonObj.radius;
},
isTopHit: function(obj, comparisonObj){
return obj.y-obj.radius < comparisonObj.y+comparisonObj.radius &&
obj.y-obj.radius > comparisonObj.y-comparisonObj.radius;
},
isLeftHit: function(obj, comparisonObj){
return obj.x-obj.radius < comparisonObj.x+comparisonObj.radius &&
obj.x-obj.radius > comparisonObj.x-comparisonObj.radius;
},
isRightHit: function(obj, comparisonObj){
return obj.x+obj.radius > comparisonObj.x-comparisonObj.radius &&
obj.x+obj.radius < comparisonObj.x+comparisonObj.radius;
},
bottomReached: function(obj) {
return (obj.y + obj.radius) >= window.innerHeight;
},
topReached: function(obj) {
return (obj.y - obj.radius) <= 0;
},
leftSideReached: function(obj) {
return (obj.x - obj.radius) <= 0;
},
rightSideReached: function(obj) {
return (obj.x + obj.radius) >= window.innerWidth;
},
draw: function(deltaNum, timestamp) {
var r = _balls;
var ctx = r.ctx;
var elapsed = timestamp - r.startTime;
for (var i = 0; i < r.particles.length; i++) {
var obj = r.particles[i];
ctx.beginPath();
ctx.fillStyle = obj.color;
ctx.moveTo(
obj.x,
obj.y
);
ctx.arc(
obj.x,
obj.y,
obj.radius,
0,
2 * Math.PI,
false
);
ctx.fill();
ctx.closePath();
obj.y += (obj.vy * deltaNum);
obj.vy += (obj.gravity * deltaNum);
obj.x += (obj.vx * deltaNum);
var otherBall = r.isBallCollision(obj);
if (otherBall) {
if (r.isBottomHit(obj, otherBall)){
//Bounce up
obj.vy = -Math.abs(obj.vy*obj.damping);
} else if (r.isTopHit(obj, otherBall)){
//bounce down
obj.vy = Math.abs(obj.vy*obj.damping);
}
if (r.isLeftHit(obj, otherBall)){
//Bounce right
obj.vx = Math.abs(obj.vx*obj.damping);
} else if (r.isRightHit(obj, otherBall)){
//Bounce left
obj.vx = -Math.abs(obj.vx*obj.damping);
}
}
if (r.bottomReached(obj)) {
obj.y = window.innerHeight - obj.radius;
if (obj.vy > 5){
//Bounce
obj.vy = -(obj.vy * 0.70);
} else {
//Stop bouncing
obj.vy = 0;
if (otherBall) {
obj.vx = -(obj.vx * obj.damping);
}
obj.vx += obj.vx > 0 ? -0.008 : 0.008;
if (obj.vx < 0.010 && obj.vx > -0.010) {
obj.vx = 0;
}
}
} else if (r.topReached(obj)) {
obj.vy = Math.abs(obj.vy);
}
if (r.rightSideReached(obj)) {
obj.vx = -obj.vx;
} else if (r.leftSideReached(obj)) {
obj.vx = Math.abs(obj.vx);
}
}
},
render: function(timestamp) {
var a = _balls;
var delta = timestamp - (a.lastFrameTime || timestamp);
a.lastFrameTime = timestamp;
if (a.particles.length) {
a.ctx.clearRect(
0,
0,
window.innerWidth,
window.innerHeight
);
a.draw(delta / 16.6, timestamp);
}
window.requestAnimationFrame(a.render);
}
};
var _mouseControl = (function() {
var _el = document.getElementById("canvas");
_el.addEventListener("click", function(e) {
_balls.init.call(_balls, e.clientX, e.clientY);
});
}());
}());
I need some expert help. When I make canvas background transparent the line colors the whole canvas (shown in code below).
How do I stop/fix this? I want the line stay as a single line that doesn't color the canvas.
Math.clamp = function(x, min, max) {
return x < min ? min : (x > max ? max : x);
};
// canvas settings
var viewWidth = window.innerWidth,
viewHeight = window.innerHeight,
drawingCanvas = document.getElementById("drawing_canvas"),
ctx,
timeStep = (10 / 100),
time = 0;
var lineTension = 0.067,
lineDamping = 0.068,
waveSpreadFactor = 0.1;
var previousMousePosition = {
x: 0,
y: 0
},
currentMousePosition = {
x: 0,
y: 0
},
actualMousePosition = {
x: 0,
y: 0
};
var line,
lineSegmentCount = 64,
lineMaxForce = 32,
lineStrokeGradient;
var audioCtx,
nodeCount = 64,
oscillatorNodes = [],
gainNodes = [];
var segmentsPerNode = lineSegmentCount / nodeCount;
function initGui() {
}
function goBananas() {
lineTension = 0.2;
line.anchors[Math.floor(Math.random() * line.anchors.length)].
vel = lineMaxForce;
}
function resetLine() {
line.reset();
for (var i = 0; i < nodeCount; i++) {
oscillatorNodes[i].detune.value = 100;
gainNodes[i].gain.value = 0;
}
lineTension = 0.0025;
lineDamping = 0.05;
waveSpreadFactor = 0.1;
}
function initDrawingCanvas() {
drawingCanvas.width = viewWidth;
drawingCanvas.height = viewHeight;
window.addEventListener('resize', resizeHandler);
window.addEventListener('mousemove', mouseMoveHandler);
setInterval(updateMousePosition, (1000 / 30));
ctx = drawingCanvas.getContext('2d');
ctx.lineWidth = 5;
line = new Line(0, viewHeight * 0.5, viewWidth, lineSegmentCount);
// line.anchors[0].vel = viewHeight * 0.25;
lineStrokeGradient = ctx.createLinearGradient(0, 0, 0, viewHeight);
lineStrokeGradient.addColorStop(0, '#0ff');
}
function initWebAudio() {
audioCtx = new(window.AudioContext || window.webkitAudioContext)();
for (var i = 0; i < nodeCount; i++) {
var oscillatorNode = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillatorNode.connect(gainNode);
gainNode.connect(audioCtx.destination);
gainNode.gain.value = 0;
oscillatorNode.type = 'saw';
oscillatorNode.detune.value = 200;
oscillatorNode.frequency.value = 1200 * (i / 60);
oscillatorNode.start();
oscillatorNodes[i] = oscillatorNode;
gainNodes[i] = gainNode;
}
}
function resizeHandler() {
drawingCanvas.width = viewWidth = window.innerWidth;
drawingCanvas.height = viewHeight = window.innerHeight;
if (ctx) {
ctx.lineWidth = 4;
line.resize(viewWidth, viewHeight * 0.5);
}
}
function mouseMoveHandler(event) {
actualMousePosition.x = Math.floor(event.clientX);
actualMousePosition.y = Math.floor(event.clientY);
}
function updateMousePosition() {
previousMousePosition.x = currentMousePosition.x;
previousMousePosition.y = currentMousePosition.y;
currentMousePosition.x = actualMousePosition.x;
currentMousePosition.y = actualMousePosition.y;
}
function update() {
var px = Math.min(previousMousePosition.x, currentMousePosition.x),
py = Math.min(previousMousePosition.y, currentMousePosition.y),
pw = Math.max(1, Math.abs(previousMousePosition.x - currentMousePosition.x)),
ph = Math.max(1, Math.abs(previousMousePosition.y - currentMousePosition.y)),
force = Math.clamp(currentMousePosition.y -
previousMousePosition.y, -lineMaxForce, lineMaxForce);
var pixels = ctx.getImageData(px, py, pw, ph),
data = pixels.data;
for (var i = 0; i < data.length; i += 3) {
var r = data[i + 0],
g = data[i + 1],
b = data[i + 2],
x = (i % ph) + px;
if (r + g + b > 0) {
line.ripple(x, force);
}
}
line.update();
for (var j = 0; j < gainNodes.length; j++) {
var anchor = line.anchors[j * segmentsPerNode],
gain = Math.clamp(Math.abs(anchor.vel) / viewHeight * 0.5, 0, 3),
detune = Math.clamp(anchor.pos / viewHeight * 100, 0, 300);
gainNodes[j].gain.value = gain;
oscillatorNodes[j].detune.value = detune;
}
}
function draw() {
ctx.strokeStyle = lineStrokeGradient;
line.draw();
}
window.onload = function() {
initDrawingCanvas();
initWebAudio();
initGui();
requestAnimationFrame(loop);
};
function loop() {
update();
draw();
time += timeStep;
requestAnimationFrame(loop);
}
Line = function(x, y, length, segments) {
this.x = x;
this.y = y;
this.length = length;
this.segments = segments;
this.segmentLength = this.length / this.segments;
this.anchors = [];
for (var i = 0; i <= this.segments; i++) {
this.anchors[i] = {
target: this.y,
pos: this.y,
vel: 0,
update: function() {
var dy = this.pos - this.target,
acc = -lineTension * dy - lineDamping * this.vel;
this.pos += this.vel;
this.vel += acc;
},
reset: function() {
this.pos = this.target;
this.vel = 0;
}
};
}
};
Line.prototype = {
resize: function(length, targetY) {
this.length = length;
this.segmentLength = this.length / this.segments;
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].target = targetY;
}
},
reset: function() {
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].reset();
}
},
ripple: function(origin, amplitude) {
var i = Math.floor((origin - this.x) / this.segmentLength);
if (i >= 0 && i <= this.segments) {
this.anchors[i].vel = amplitude;
}
},
update: function() {
var lDeltas = [],
rDeltas = [],
i;
for (i = 0; i <= this.segments; i++) {
this.anchors[i].update();
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
lDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i - 1].pos);
this.anchors[i - 1].vel += lDeltas[i];
}
if (i < this.segments) {
rDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i + 1].pos);
this.anchors[i + 1].vel += rDeltas[i];
}
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
this.anchors[i - 1].pos += lDeltas[i];
}
if (i < this.segments) {
this.anchors[i + 1].pos += rDeltas[i];
}
}
},
draw: function() {
ctx.beginPath();
for (var i = 0; i <= this.segments; i++) {
ctx.lineTo(
this.x + this.segmentLength * i,
this.anchors[i].pos
);
}
ctx.stroke();
}
};
From the code you posted, the problem seems to be a missing
ctx.clearRect(0, 0, viewWidth, viewHeight)
at the beginning of your "draw" function.
See a working example here