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