Skip to content
  1. Examples

Text on edge extremities

This example shows how to display texts at the edges' extremities. It uses the canvas layer API to do so.

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

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

ogma.styles.addEdgeRule({ shape: { head: 'arrow' } });
function distance(vec1: Point, vec2: Point) {
  const dx = vec2.x - vec1.x;
  const dy = vec2.y - vec1.y;
  return Math.sqrt(dx * dx + dy * dy);
}

const SOURCE_SIDE = 0;
const TARGET_SIDE = 1;

function displayOnEdge({
  edge,
  ctx,
  drawFunction,
  rotateText = true,
  // translation from the center of the edgeExtremity
  distToExtremity = 12,
  // margin between the edge and the text
  margin = 2,
  side = SOURCE_SIDE
}: {
  edge: Edge;
  ctx: CanvasRenderingContext2D;
  drawFunction: (args: { ctx: CanvasRenderingContext2D; edge: Edge }) => void;
  rotateText?: boolean;
  distToExtremity?: number;
  margin?: number;
  side?: typeof SOURCE_SIDE | typeof TARGET_SIDE;
}) {
  // compute the interpolation ratio that represents distToExtremity on the edge.
  const [source, target] = edge
    .getExtremities()
    .map(node => node.getPosition());
  const dist = distance(source, target);
  let t = distToExtremity / dist;
  // if we are on TARGET_SIDE, reverse the interpolation ratio
  if (side === TARGET_SIDE) {
    t = 1 - t;
  }
  // get the point, tangent and normal vectors where we want to display the text
  const point = Ogma.geometry.getPointOnEdge(edge, t);
  const tangent = Ogma.geometry.getTangentOnEdge(edge, t);

  // compute the rotation angle for the text
  let angle = Math.atan2(tangent.y, tangent.x);
  if (angle <= -Math.PI / 2 || angle >= Math.PI / 2) {
    angle += Math.PI;
  }

  ctx.save();
  ctx.beginPath();
  // translate the context to the text center
  ctx.translate(point.x, point.y);
  // rotate it so it's parrallel to the edge
  if (rotateText) {
    ctx.rotate(angle);
  }
  // translate it so there is margin between the edge and the text
  ctx.translate(0, margin);
  // draw the text
  drawFunction({ ctx, edge });
  ctx.stroke();
  ctx.restore();
}

function draw(ctx: CanvasRenderingContext2D) {
  ctx.font = '2px sans-serif';
  ogma.getEdges().forEach(edge => {
    // display on edge will give you a ctx rotated and translated
    // to the right place
    displayOnEdge({
      edge,
      ctx,
      // draw a text at the begining of the edge
      drawFunction: ({ edge, ctx }) => {
        const text = edge.getData('sourceText');
        const w2 = ctx.measureText(text).width / 2;
        ctx.fillText(text, -w2, 0);
      }
    });

    displayOnEdge({
      edge,
      ctx,
      side: TARGET_SIDE,
      // draw a text at the end of the edge
      drawFunction: ({ edge, ctx }) => {
        const text = edge.getData('targetText');
        const w2 = ctx.measureText(text).width / 2;
        ctx.fillText(text, -w2, 0);
      }
    });
  });
}

const graph = {
  nodes: [{ id: 0 }, { id: 1 }],
  edges: [
    {
      source: 0,
      target: 1,
      data: { sourceText: 'Source 0', targetText: 'Target 0' }
    },
    {
      source: 1,
      target: 0,
      data: { sourceText: 'Source 1', targetText: 'Target 1' }
    },
    {
      source: 1,
      target: 0,
      data: { sourceText: 'Source 2', targetText: 'Target 2' }
    },
    {
      source: 1,
      target: 0,
      data: { sourceText: 'Source 3', targetText: 'Target 3' }
    },
    {
      source: 1,
      target: 0,
      data: { sourceText: 'Source 4', targetText: 'Target 4' }
    }
  ]
};

// setup the graph
await ogma.setGraph(graph);
await ogma.layouts.force({ locate: true });
// create the layer
const edgeTextLayer = ogma.layers.addCanvasLayer(draw);
// hide and show on draging
ogma.events
  .on('nodesDragStart', () => {
    edgeTextLayer.hide();
  })
  .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;
}
.brand {
  background-color: rgba(255, 255, 255, 0.7);
  font-size: 10px;
  padding: 5px;
  margin: 0;
  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
}