Skip to content
  1. Examples

Animated edges new

This example shows how to use canvas layer api to add animated dashed edges to the graph.

ts
import Ogma, { Edge, Color, CanvasLayer, Point } from '@linkurious/ogma';

const ogma = new Ogma({
  container: 'graph-container'
});

const getRandomColor = () => {
  const letters = '0123456789ABCDEF';
  let color = '#';
  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
};

ogma.styles.addRule({
  nodeAttributes: {
    color: 'orange'
  },
  edgeAttributes: {
    color: getRandomColor,
    width: 0
  }
});

interface AnimatedDashesOptions {
  edge: Edge;
  ctx: CanvasRenderingContext2D;
  dash?: [number, number];
  color?: Color;
  speed?: number;
  direction?: number;
  widthRatio?: number;
}

let time = 0;
function drawAnimatedDashes({
  edge,
  ctx,
  dash = [2, 1],
  color,
  speed = 0.15,
  direction = 1,
  widthRatio = 2
}: AnimatedDashesOptions) {
  const curvature = edge.getAttribute('curvature') as number;
  const width = edge.getAttribute('width') as number;
  const [source, target] = edge.getExtremities().getPosition();
  color = color || edge.getAttribute('color') || 'black';

  // control point
  const cp = Ogma.geometry.getQuadraticCurveControlPoint(
    source.x,
    source.y,
    target.x,
    target.y,
    curvature
  );

  // mid point
  const p = Ogma.geometry.getPointOnQuadraticCurve(
    0.5,
    source.x,
    source.y,
    target.x,
    target.y,
    cp.x,
    cp.y
  );
  const d =
    curvature === 0
      ? Math.hypot(source.x, source.y, target.x, target.y)
      : Ogma.geometry.getQuadraticBezierCurveLength(
          source.x,
          source.y,
          p.x,
          p.y,
          target.x,
          target.y
        );

  const tSource = 0;
  const numPoints = Math.floor(d / (dash[0] + dash[1]));

  const points = new Array(numPoints);
  // tangents
  const ts = new Array(numPoints);
  // normals
  const ns = new Array(numPoints);

  const offset = (direction * (time * speed)) % (dash[0] + dash[1]);
  let t = tSource + offset / d;
  for (let i = 0; i < numPoints; i++) {
    points[i] = Ogma.geometry.getPointOnQuadraticCurve(
      t,
      source.x,
      source.y,
      target.x,
      target.y,
      cp.x,
      cp.y
    );
    ts[i] = Ogma.geometry.getTangentOnEdge(edge, t);
    ns[i] = Ogma.geometry.getNormalOnEdge(edge, t);
    t = Math.min(1, t + (dash[0] + dash[1]) / d);
  }
  ctx.lineWidth = widthRatio * width;
  ctx.strokeStyle = color;

  for (let i = 0; i < numPoints; i++) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(points[i].x, points[i].y);
    ctx.moveTo((ts[i].x * width) / 2, (ts[i].y * width) / 2);
    ctx.lineTo(ts[i].x * dash[0], ts[i].y * dash[0]);
    ctx.stroke();
    ctx.restore();
  }
}

function draw(ctx: CanvasRenderingContext2D) {
  ctx.font = '2px sans-serif';
  ogma.getEdges('all').forEach(edge =>
    drawAnimatedDashes({
      edge,
      ctx,
      widthRatio: 3,
      direction: 1
    })
  );
}

const graph = {
  nodes: [
    { id: 0, attributes: { x: -50, y: 0 } },
    { id: 1, attributes: { x: 50, y: 0 } },
    { id: 2 },
    { id: 3 }
  ],
  edges: [
    { source: 0, target: 1 },
    { source: 0, target: 1 },
    { source: 1, target: 2 },
    { source: 1, target: 3 },
    { source: 1, target: 3 },
    { source: 1, target: 3 },
    { source: 1, target: 3 },
    { source: 3, target: 1 }
  ]
};

// setup the graph
let layer: CanvasLayer;
ogma
  .setGraph(graph)
  .then(() => ogma.layouts.force({ locate: true }))
  .then(() => {
    // create the layer
    layer = ogma.layers.addCanvasLayer(draw);
    layer.moveToBottom();
    // hide and show on draging
    ogma.events
      .on('nodesDragStart', () => layer.hide())
      .on('nodesDragEnd', () => layer.show());
    animate();
  });

function animate() {
  requestAnimationFrame(() => {
    time += 1;
    layer.refresh();
    animate();
  });
}
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="graph-container"></div>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}