chart = {
const canvas = d3
.create('canvas')
.attr('width', width)
.attr('height', height)
.attr(
'style',
'border: 1px solid #eaeaea; background-image: url(https://i2.rozetka.ua/goods/20190919/253286261_images_20190919481.jpg); background-size: 110%; background-position-y: -150px; background-position-x: -20px;'
);
const lonScale = d3
.scaleLinear()
.domain([minLon, maxLon])
.range([paddingLeft, width - paddingRight]);
const latScale = d3
.scaleLinear()
.domain([minLat, maxLat])
.range([height - paddingBottom, paddingTop]);
const ctx = canvas.node().getContext('2d');
const STRUCT_SIZE = 5;
const points = Array.from({ length: pointsNumber * STRUCT_SIZE }, ind => 0);
function getRandomLon() {
return Math.random() * (maxLon - minLon) + minLon;
}
function getRandomLat() {
return Math.random() * (maxLat - minLat) + minLat;
}
let currentSecond = Date.now() / 1000;
function init() {
currentSecond = Date.now() / 1000;
for (let i = 0; i < pointsNumber * STRUCT_SIZE; i += STRUCT_SIZE) {
points[i] = getRandomLon();
points[i + 1] = getRandomLat();
points[i + 2] = 0;
points[i + 3] = 0;
points[i + 4] = Date.now() / 1000;
}
}
init();
canvas.node().addEventListener('click', init);
const dt = 5e-3;
while (true) {
currentSecond = Date.now() / 1000;
// update
for (let i = 0; i < pointsNumber * STRUCT_SIZE; i += STRUCT_SIZE) {
const creationTime = points[i + 4];
const lon = points[i];
const lat = points[i + 1];
const force = getForce(lon, lat);
points[i + 2] = force[0] * dt;
points[i + 3] = force[1] * dt;
points[i] += points[i + 2] * dt;
points[i + 1] += points[i + 3] * dt;
const v2 = points[i + 2] ** 2 + points[i + 3] ** 2;
if (shouldBeRecreated(points, i)) {
setRandomCoord(points, i);
points[i + 2] = 0;
points[i + 3] = 0;
points[i + 4] = currentSecond;
}
}
// draw
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = '#333';
for (let i = 0; i < pointsNumber * STRUCT_SIZE; i += STRUCT_SIZE) {
const lon = points[i];
const lat = points[i + 1];
const x = lonScale(lon);
const y = latScale(lat);
ctx.beginPath();
ctx.arc(x, y, 1, 0, Math.PI * 2, false);
ctx.fill();
ctx.beginPath();
ctx.moveTo(x, y);
const nextX = lonScale(points[i] + points[i + 2]);
const nextY = latScale(points[i + 1] + points[i + 3]);
const dist = Math.sqrt((nextX - x) ** 2 + (nextY - y) ** 2);
const unitX = (nextX - x) / dist;
const unitY = (nextY - y) / dist;
ctx.lineTo(x + unitX * 5, y + unitY * 5);
ctx.stroke();
}
ctx.save();
ctx.fillStyle = '#fff';
for (const d of data) {
const x = lonScale(d.lon);
const y = latScale(d.lat);
if (d.value > 0) {
ctx.strokeStyle = 'crimson';
} else {
ctx.strokeStyle = '#369';
}
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2, false);
ctx.fill();
ctx.stroke();
}
ctx.restore();
yield canvas.node();
}
canvas.node().removeEventListener('click', init);
}