Продолжая свое путешествие по творческой визуализации с помощью JS, я наткнулся на эту удивительную библиотеку: Canvas Sketch. Это набор инструментов и модулей, который предлагает большие возможности для создания генеративного искусства.

Я хочу поделиться тем, как я начал с простого прыгающего мяча и закончил набором взаимосвязанных узлов.

  1. Начнем с исправления нашего холста: 2048 x 2048. По сути, нам нужна коробка, в которой будет отскакивать наш мяч.
  2. Далее мы хотим нарисовать мяч на нашем холсте. Нам нужно предоставить координаты (x, y) для шара, радиус, начальный угол и конечный угол.
context.fillStyle = 'black';
context.beginPath();
context.arc(1000, y, 20, 0, Math.PI*2); // x, y, radius, startAngle, endAngle
context.fill();

Обратите внимание, что мы хотим, чтобы мяч отскакивал только в вертикальной плоскости, что означает, что мы сохраняем фиксированную координату x и имеем переменную для координаты y.

3. Затем мы инициализируем начальное положение мяча и добавляем переменную скорости, которая будет увеличивать положение мяча с каждым кадром.

let y = 100; //position of ball
let velocity = 10; // velocity of ball
y += velocity; // increment position

4. С приведенным выше кодом наш мяч просто начнет с позиции y = 100 и упадет вниз, покидая холст. Однако мы хотим, чтобы он отскакивал, поэтому нам нужно установить некоторые ограничения.

if (y <= 100 || y >= 2000) {
velocity *= -1 ; //bounce
}

С условием «если» мы устанавливаем границы шара. Мы меняем знак скорости на границах, что равно изменению направления, так как скорость является векторной величиной.

5. Теперь нас больше интересует не один мяч, а 100 мячей, которые беспорядочно движутся и отскакивают от стен. Следовательно, мы создаем «Класс» для шаров, которые будут рисовать, двигаться и отскакивать:

let circles = [];
for (let i = 0; i < 100; i++) {
circles.push(new Circle(Math.random()*2048, Math.random()*2048, Math.random()*20))
}
// class to create circles
class Circle {
constructor(x,y,radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.velocityX = Math.random()*4 - 2; // [-2, 2]
this.velocityY = Math.random()*4 - 2; // [-2, 2]
}
// draw function
draw(context){
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI*2);
context.stroke();
}
//move function
move (){
this.x += this.velocityX; //position x changes 
this.y += this.velocityY; //position y changes 
}
// bounce function
bounce(width,height){
if (this.x <= 0 || this.x >= width) {
this.velocityX *= -1; //bounce in x-direction
}
if (this.y <= 0 || this.y >= height) {
this.velocityY *= -1; //bounce in y-direction
}}}

Обратите внимание, что координаты x-y, радиус и скорость в обоих направлениях шаров сделаны случайными.

6. Далее нам нужно нарисовать линии, соединяющие шары. Сначала нам нужно написать цикл «for», чтобы присвоить имена переменных двум шарам, и мы делаем это для всех шаров в массиве. Используя положение x-y этих двух шаров, мы проводим линию между ними.

// Draw line between circles
for (let i = 0; i < circles.length; i++) {
const circl1 = circles[i];
for (let j = i+1; j < circles.length; j++) {
const circl2 = circles[j];
context.lineWidth = 10 line width
context.beginPath();
context.moveTo(circl1.x,circl1.y);
context.lineTo(circl2.x,circl2.y);
context.stroke();
}}

Обратите внимание, что каждый шар теперь соединен с каждым шаром в нашей коробке. Хотя впечатляет, это не то, что мы хотим.

Я уменьшил ширину этой линии, чтобы увидеть, как отличается визуализация. Все еще приятно до сих пор.

7. Мы хотим, чтобы шары были соединены, только если они достаточно близко. Это означает, что нам нужно рассчитать расстояние между ними по формуле Евклида:

// function to calculate distance using Euclidean
const getDistance = (x1, x2, y1, y2) => {
const x= x1-x2;
const y= y1-y2;
return Math.sqrt(x*x + y*y);
}

8. С помощью функции вычисляем расстояние и задаем некоторый порог отрисовки линий:

//calculate distance
const dist = getDistance(circl1.x, circl2.x, circl1.y, circl2.y);
if (dist < 250) {
context.lineWidth = 10
context.beginPath();
context.moveTo(circl1.x,circl1.y);
context.lineTo(circl2.x,circl2.y);
context.stroke();
}

9. Наконец, мы хотим, чтобы ширина линии была гибкой. То есть чем ближе шарики друг к другу, тем толще будет линия, и наоборот. Мы используем расстояние для установки ширины линии:

context.lineWidth = 10 - dist/25; //responsive line width

И вуаля!

Это #day6 моих проектов #100dataviz по науке о данных и рассказыванию историй с данными. Ваши отзывы приветствуются. Полный код на GitHub. Спасибо за чтение!