Skip to content
  1. Examples

Flowlines

This example shows how you to use Ogma layers to draw flowlines.

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

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

ogma.styles.addEdgeRule({
  color: 'black',
  width: 0.1
});

function drawUnidirectionalEdge({
  edge,
  ctx,
  width = 2,
  color,
  margin = 3.5,
  direction = 1
}: {
  edge: Edge;
  ctx: CanvasRenderingContext2D;
  width?: number;
  color?: Color;
  margin?: number;
  direction?: number;
}) {
  // @ts-expect-error
  const curvature = edge.getAttribute('curvature');
  const [source, target] = edge.getExtremities().getPosition();
  color = color || edge.getAttribute('color');

  const cp = Ogma.geometry.getQuadraticCurveControlPoint(
    source.x,
    source.y,
    target.x,
    target.y,
    curvature
  );
  const normal = Ogma.geometry.getNormalOnEdge(edge, 0.5);

  const rTarget =
    direction > 0
      ? edge.getTarget().getAttribute('radius')
      : edge.getSource().getAttribute('radius');

  let t =
    1 -
    (+rTarget + margin) /
      Ogma.geometry.distance(source.x, source.y, target.x, target.y);
  if (direction < 0) {
    normal.x *= -1;
    normal.y *= -1;
    t = 1 - t;
  }
  const tangent = Ogma.geometry.getTangentOnEdge(edge, t);
  const normalArrow = Ogma.geometry.getNormalOnEdge(edge, t);
  const angle = Math.atan2(tangent.y, tangent.x);
  const point = Ogma.geometry.getPointOnEdge(edge, t);

  // draw the curved line
  ctx.save();
  ctx.beginPath();
  ctx.translate((normal.x * width) / 2, (normal.y * width) / 2);
  ctx.strokeStyle = color as string;
  ctx.moveTo(source.x, source.y);
  ctx.quadraticCurveTo(cp.x, cp.y, target.x, target.y);
  ctx.lineWidth = width;
  ctx.stroke();
  ctx.restore();

  // draw the arrow line
  ctx.save();
  ctx.beginPath();
  ctx.translate(
    point.x + direction * normalArrow.x * width,
    point.y + direction * normalArrow.y * width
  );
  ctx.rotate(angle);
  ctx.fillStyle = color as string;
  ctx.moveTo(0, (-direction * width) / 2);
  ctx.lineTo(-direction * 4 * width, direction * 2 * width);
  ctx.lineTo(-direction * 4 * width, (-direction * width) / 2);
  ctx.fill();
  ctx.restore();
}

/**
 * This function computes the width and the color of an edge depending on its importance.
 */
function getOptions(importance = 0) {
  return {
    width:
      importance < 3 ? 0.5 : importance < 5 ? 1.0 : importance < 8 ? 1.5 : 2,

    color:
      importance < 3
        ? 'grey'
        : importance < 5
          ? 'green'
          : importance < 8
            ? 'orange'
            : 'red'
  };
}
function draw(ctx: CanvasRenderingContext2D) {
  ctx.font = '2px sans-serif';
  ogma
    .getEdges('all')
    // .filter(e => e.isVirtual())
    .forEach(edge => {
      drawUnidirectionalEdge({
        edge,
        ctx,
        ...getOptions(edge.getData('importanceBA')),
        direction: -1
      });
      drawUnidirectionalEdge({
        edge,
        ctx,
        ...getOptions(edge.getData('importanceAB'))
      });
    });
}

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, data: { importanceBA: 1, importanceAB: 10 } },
    { source: 0, target: 1, data: { importanceBA: 5, importanceAB: 3 } },
    { source: 1, target: 2, data: { importanceAB: 2 } },
    { source: 1, target: 3, data: { importanceBA: 6 } },
    { source: 1, target: 3, data: { importanceAB: 6 } },
    { source: 1, target: 3, data: { importanceBA: 1 } },
    { source: 1, target: 3, data: { importanceBA: 2 } },
    { source: 3, target: 1, data: { importanceAB: 7 } }
  ]
};

// setup the graph
await ogma.setGraph(graph);
await ogma.layouts.force({ locate: true });
// create the layer
const edgeTextLayer = ogma.layers.addCanvasLayer(draw);
edgeTextLayer.moveToBottom();
// hide and show on draging
ogma.events.on('nodesDragStart', () => {
  edgeTextLayer.hide();
});
ogma.events.on('nodesDragEnd', () => {
  edgeTextLayer.show();
});
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;
}