<div id="vizzu-container" style="width: 720px; height: 430px"></div>
import Vizzu from 'https://cdn.jsdelivr.net/npm/vizzu@latest/dist/vizzu.min.js';
   
class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static Random() {
    return new Vector(Math.random(), Math.random());
  }

  mul(multiplier) {
    return new Vector(
      this.x * (multiplier?.x || multiplier), 
      this.y * (multiplier?.y || multiplier));
  }

  add(other) {
    return new Vector(this.x + other.x, this.y + other.y);
  }
}

class BouncingBall {
  static lastIndex = 0;

  constructor(massToSize) {
    this.index = BouncingBall.lastIndex++;
    this.position = Vector.Random();
    this.speed = (Vector.Random()).mul(new Vector(3, 5));
    this.mass = Math.random();
    this.radius = Math.sqrt(this.mass)*massToSize;
  }

  update(timeStep) {
    const g = 9.81;
    const friction = 0.5;
    let acceleration = this.speed.mul(-friction * this.mass).add(new Vector(0, -g));
    this.speed = this.speed.add(acceleration.mul(timeStep));
    this.position = this.position.add(this.speed.mul(timeStep));
    this.collision('y', v => v);
    this.collision('x', v => v);
    this.collision('x', v => 1 - v);
  }

  collision(coordinate, conversion) {
    const collisionDumping = 0.6;
    let side = conversion(this.position[coordinate]) - this.radius;
    if (side < 0) { 
      this.position[coordinate] = conversion(- side + this.radius);
      this.speed[coordinate] *= -collisionDumping;
    }
  }
}

class Model {
  constructor(chart, ballCount = 150) {
  	let massToSize = chart.getComputedStyle().plot.marker.circleMaxRadius;
    this.time = 0;
    this.balls = [];
    for (let i = 0; i < ballCount; i++)
      this.balls.push(new BouncingBall(massToSize));
  }

  update(timeStep) 
  {
    for (let ball of this.balls) ball.update(timeStep);
    this.time += timeStep;
  }
}

function getDataFromModel(model) {
	return {
    series: [
      { name: 'index', type: 'dimension', values: model.balls.map(ball => `${ball.index}`) },
      { name: 'x', type: 'measure', values: model.balls.map(ball => ball.position.x) },
      { name: 'y', type: 'measure', values: model.balls.map(ball => ball.position.y) },
      { name: 'size', type: 'measure', values: model.balls.map(ball => ball.mass) }
    ]
  };
}

function getChartConfig() {
	return {
    title: 'Bouncing balls realtime simulation',
    x: { set: 'x', range: { min: 0, max: 1} },
    y: { set: 'y', range: { min: 0, max: 1} },
    color: 'index',
    size: 'size',
    geometry: 'circle',
    legend: null
  };
}

let vizzu = new Vizzu('vizzu-container');

function update(model, chart) 
{
  const timeStep = 0.01;
  model.update(timeStep);
  if (model.time < 4)
  return chart.animate({ 
    data: getDataFromModel(model),
    config: getChartConfig() 
  }, timeStep)
  .then(chart => update(model, chart));
}

vizzu.initializing.then(chart => {
	let model = new Model(chart);
	return update(model, chart);
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.