Skip to content
  1. Examples

Edge badges

This example shows you how to render edge badges on the edges using layers. Note how the rendering function skips the icons that are outside of the viewport.

ts
import Ogma from '@linkurious/ogma';

const ogma = new Ogma({
  container: 'graph-container',
  graph: {
    nodes: [{ id: 'us' }, { id: 'de' }, { id: 'fr' }],
    edges: [
      { source: 'us', target: 'de' },
      { source: 'de', target: 'fr' },
      { source: 'fr', target: 'us' }
    ]
  },
  options: {
    interactions: {
      zoom: { maxValue: () => Infinity }
    }
  }
});

// Icon font
const font = 'FontAwesome';

// Node and edge styles
function addStyles() {
  ogma.styles.addRule({
    nodeAttributes: {
      radius: 9,
      color: '#4d9de0',
      icon: {
        font: font,
        content: '\uf2bd',
        color: 'white'
      }
    },
    edgeAttributes: {
      width: 0.8
    }
  });
}

/**
 * Checks if the point is inside a rectangle
 */
const isInside = (
  x: number,
  y: number,
  minX: number,
  minY: number,
  maxX: number,
  maxY: number
) => x >= minX && x <= maxX && y >= minY && y <= maxY;

const render = (ctx: CanvasRenderingContext2D, ogma: Ogma) => {
  ogma.getEdges().forEach(edge => {
    const [source, target] = edge.getExtremities().getPosition();
    const cx = (source.x + target.x) / 2;
    const cy = (source.y + target.y) / 2;

    const tangent = Ogma.geometry.getTangentOnEdge(edge, 0.5);
    let angle = Math.atan2(tangent.y, tangent.x);

    // angle correction so that it's not upside down
    if (angle > Math.PI / 2.0 || angle < -Math.PI / 2.0) {
      angle += Math.PI;
    }

    // do not render if the icon is outside of the viewport + tolerance
    const eps = 50;
    const { x, y } = ogma.view.graphToScreenCoordinates({ x: cx, y: cy });
    const { width, height } = ogma.view.getSize();
    if (!isInside(x, y, 0 - eps, 0 - eps, width + eps, height + eps)) return;

    // proceed to rendering
    ctx.save();
    ctx.translate(cx, cy);
    ctx.rotate(angle);

    ctx.font = `900 5px "${font}"`;
    ctx.fillStyle = '#e15554';
    ctx.textBaseline = 'middle';
    // note that I wasn't able to find \uF006 defined in the provided CSS
    // falling back to fa-bug for demo
    ctx.fillText('\uf15b', 0, 0);
    ctx.restore();
  });
};

await document.fonts.ready;
await addStyles();
await ogma.layouts.force({ locate: true });
// add the custom renderer
const layer = ogma.layers.addCanvasLayer(ctx => render(ctx, ogma));
ogma.events.on(['nodesDragProgress'], () => layer.refresh());
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <link
      type="text/css"
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"
    />
    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>

  <body>
    <i class="fa fa-camera-retro fa-1x" style="color: rgba(0, 0, 0, 0)"></i>
    <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;
}