Skip to content
  1. Examples

Parallel views with annotations

Try panning and zooming the separate views in this example. Note als that the node positions are synchronized and dotted pseudo-edges connecting the nodes are spanning from one view to the other. This can be used to show meta-graphs or different views of the same graph.

ts
import Ogma, { NodeId, RawGraph, RawNode } from '@linkurious/ogma';

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

const initGraph = (ogma: Ogma, graph: RawGraph) =>
  ogma
    .setGraph(graph)
    .then(() => ogma.layouts.forceLink())
    .then(() => ogma.view.locateGraph({ easing: 'linear', duration: 300 }));

// synchronize the views
const synchViews = (views: Ogma[]) =>
  views.forEach(view => {
    const synchViews = views.filter(synchView => synchView !== view);
    const updateNode = (node: RawNode) =>
      synchViews.forEach(synchView =>
        synchView.getNode(node.id!)!.setAttributes(node.attributes!)
      );
    view.events.on('nodesDragProgress', evt =>
      evt.nodes.forEach(node =>
        updateNode(node.toJSON({ attributes: ['x', 'y'] }))
      )
    );
  });

// extra links bridging the views
const overlay = document.getElementById('overlay') as HTMLCanvasElement;
overlay.width = window.innerWidth;
overlay.height = window.innerHeight;
const context = overlay.getContext('2d')!;

const drawBridges = (
  view1: Ogma,
  view2: Ogma,
  bridges: Map<NodeId, NodeId[]>
) => {
  context.clearRect(0, 0, overlay.width, overlay.height);
  context.lineWidth = 2;
  context.setLineDash([8, 4]);
  context.strokeStyle = '#888888';
  bridges.forEach((sources, target) => {
    const { x, y } = view2.getNode(target)!.getPositionOnScreen();
    sources.forEach(source => {
      const { x: sx, y: sy } = view1.getNode(source)!.getPositionOnScreen();
      context.beginPath();
      context.moveTo(sx, sy);
      context.lineTo(x, y + window.innerHeight / 2);
      context.stroke();
    });
  });
};

// generate a graph, some bridges and synchronize the views
const bridges = new Map<NodeId, NodeId[]>();
bridges.set(19, [1, 2, 7]);
bridges.set(7, [4, 5, 6]);

view1.generate
  .barabasiAlbert({ nodes: 20, m0: 5, m: 1 })
  .then(graph =>
    Promise.all([initGraph(view1, graph), initGraph(view2, graph)])
  )
  .then(() => {
    const views = [view1, view2];
    synchViews(views);
    views.forEach(view => {
      view.events.on(['nodesDragProgress', 'move', 'zoom'], () =>
        drawBridges(view1, view2, bridges)
      );
    });
    drawBridges(view1, view2, bridges);
  });
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />

    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="container">
      <div id="graph-container-view1" class="graph-container"></div>
      <div id="graph-container-view2" class="graph-container"></div>
      <canvas id="overlay" width="100vw" height="100vh" />
    </div>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
html,
body {
  height: 100%;
  margin: 0;
}

.container {
  height: 100%;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  background: #ddd;
}

.graph-container {
  flex: 1;
  display: flex;
  overflow: hidden;
  border: 1px dotted #aaa;
}

#overlay {
  position: absolute;
  pointer-events: none;
  z-index: 100;
}