Data visualization: autoconfigurazione

Con classi sempre più complesse e utilizzandole anche per i collegamenti è possibile fare in modo che gli elementi (o nodi in questo caso) si muovano autonomamente fino a raggiungere una configurazione che, ad esempio, li mantenga a una determinata distanza.

    const nodeSize = 10;
const nodeSpeed = 0.1;
const desiredDistance = 100;

const nodeCount = 10;
const linkCount = 30;
let nodes = [];
let links = [];

class Node {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.vx = 0;
    this.vy = 0;
  }

  updatePosition() {
    let connectedNodes = this.getConnectedNodes();

    if (connectedNodes.length > 0) {
      let velVects = [];
      // Vettori di spostamento
      for (let i = 0; i < connectedNodes.length; i++) {
        let connectedNode = connectedNodes[i];
        let connectedVect = createVector(
          connectedNode.x - this.x,
          connectedNode.y - this.y
        );
        let distance = connectedVect.mag();
        connectedVect.setMag(distance - desiredDistance);
        velVects.push(connectedVect);
      }

      // Media degli spostamenti
      let newVel = velVects[0];
      for (let i = 1; i < velVects.length; i++) {
        let vel = velVects[i];
        newVel.x += vel.x;
        newVel.y += vel.y;
      }
      newVel.x /= velVects.length;
      newVel.y /= velVects.length;

      this.vx = newVel.x * nodeSpeed;
      this.vy = newVel.y * nodeSpeed;
    }

    // Spostamento
    this.x += this.vx;
    this.y += this.vy;

    // Bordi dello schermo
    if (this.x < 0 || this.x > width) {
      this.vx *= -1;
    }
    if (this.y < 0 || this.y > height) {
      this.vy *= -1;
    }
  }

  display() {
    noStroke();
    fill(255);
    circle(this.x, this.y, nodeSize);
  }

  getConnectedNodes() {
    let connectedNodes = [];

    for (let i = 0; i < links.length; i++) {
      let link = links[i];
      if (link.startNode === this || link.endNode === this) {
        let connectedNode =
          link.startNode === this ? link.endNode : link.startNode;
        connectedNodes.push(connectedNode);
      }
    }

    return connectedNodes;
  }
}

class Link {
  constructor(startNode, endNode) {
    this.startNode = startNode;
    this.endNode = endNode;
  }

  display() {
    stroke(128);
    line(
      this.startNode.x, this.startNode.y, 
      this.endNode.x, this.endNode.y
    );
  }
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  cursor(HAND);

  // Creazione dei nodi
  for (let i = 0; i < nodeCount; i++) {
    let halfW = width / 2;
    let halfH = height / 2;
    let node = new Node(random(-halfW,halfW), random(-halfH,halfH));
    nodes.push(node);
  }

  // Creazione dei link
  for (let i = 0; i < linkCount; i++) {
    let startNode = floor(random(nodeCount));
    let endNode = floor(random(nodeCount));
    let link = new Link(nodes[startNode], nodes[endNode]);
    links.push(link);
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

function draw() {
  background(0);
  translate(width/2, height/2);

  // Aggiornamento delle posizioni dei nodi
  for (let i = 0; i < nodes.length; i++) {
    let node = nodes[i];
    node.updatePosition();
  }

  // Disegno dei link
  for (let i = 0; i < links.length; i++) {
    let link = links[i];
    link.display();
  }

  // Disegno dei nodi
  for (let i = 0; i < nodes.length; i++) {
    let node = nodes[i];
    node.display();
  }
}

function mouseClicked() {
  // Sposta nodi in relazione alla distanza
  for (let i = 0; i < nodes.length; i++) {
    let node = nodes[i];
    let mouseDist = dist(mouseX-width/2, mouseY-height/2, node.x, node.y);
    if (mouseDist < 200) {
      let delta = 200 / 2;  // più vicino -> spostamento maggiore
      node.x = mouseX - width/2 + random(-delta, delta);
      node.y = mouseY - height/2 + random(-delta, delta);
    }
  }
}