Appearance
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;
}