Appearance
Animated edges new
This example shows how to use canvas layer api to add animated dashed edges to the graph.
ts
import Ogma, { Edge, Color, CanvasLayer, Point } from '@linkurious/ogma';
const ogma = new Ogma({
container: 'graph-container'
});
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
ogma.styles.addRule({
nodeAttributes: {
color: 'orange'
},
edgeAttributes: {
color: getRandomColor,
width: 0
}
});
interface AnimatedDashesOptions {
edge: Edge;
ctx: CanvasRenderingContext2D;
dash?: [number, number];
color?: Color;
speed?: number;
direction?: number;
widthRatio?: number;
}
let time = 0;
function drawAnimatedDashes({
edge,
ctx,
dash = [2, 1],
color,
speed = 0.15,
direction = 1,
widthRatio = 2
}: AnimatedDashesOptions) {
const curvature = edge.getAttribute('curvature') as number;
const width = edge.getAttribute('width') as number;
const [source, target] = edge.getExtremities().getPosition();
color = color || edge.getAttribute('color') || 'black';
// control point
const cp = Ogma.geometry.getQuadraticCurveControlPoint(
source.x,
source.y,
target.x,
target.y,
curvature
);
// mid point
const p = Ogma.geometry.getPointOnQuadraticCurve(
0.5,
source.x,
source.y,
target.x,
target.y,
cp.x,
cp.y
);
const d =
curvature === 0
? Math.hypot(source.x, source.y, target.x, target.y)
: Ogma.geometry.getQuadraticBezierCurveLength(
source.x,
source.y,
p.x,
p.y,
target.x,
target.y
);
const tSource = 0;
const numPoints = Math.floor(d / (dash[0] + dash[1]));
const points = new Array(numPoints);
// tangents
const ts = new Array(numPoints);
// normals
const ns = new Array(numPoints);
const offset = (direction * (time * speed)) % (dash[0] + dash[1]);
let t = tSource + offset / d;
for (let i = 0; i < numPoints; i++) {
points[i] = Ogma.geometry.getPointOnQuadraticCurve(
t,
source.x,
source.y,
target.x,
target.y,
cp.x,
cp.y
);
ts[i] = Ogma.geometry.getTangentOnEdge(edge, t);
ns[i] = Ogma.geometry.getNormalOnEdge(edge, t);
t = Math.min(1, t + (dash[0] + dash[1]) / d);
}
ctx.lineWidth = widthRatio * width;
ctx.strokeStyle = color;
for (let i = 0; i < numPoints; i++) {
ctx.save();
ctx.beginPath();
ctx.translate(points[i].x, points[i].y);
ctx.moveTo((ts[i].x * width) / 2, (ts[i].y * width) / 2);
ctx.lineTo(ts[i].x * dash[0], ts[i].y * dash[0]);
ctx.stroke();
ctx.restore();
}
}
function draw(ctx: CanvasRenderingContext2D) {
ctx.font = '2px sans-serif';
ogma.getEdges('all').forEach(edge =>
drawAnimatedDashes({
edge,
ctx,
widthRatio: 3,
direction: 1
})
);
}
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 },
{ source: 0, target: 1 },
{ source: 1, target: 2 },
{ source: 1, target: 3 },
{ source: 1, target: 3 },
{ source: 1, target: 3 },
{ source: 1, target: 3 },
{ source: 3, target: 1 }
]
};
// setup the graph
let layer: CanvasLayer;
ogma
.setGraph(graph)
.then(() => ogma.layouts.force({ locate: true }))
.then(() => {
// create the layer
layer = ogma.layers.addCanvasLayer(draw);
layer.moveToBottom();
// hide and show on draging
ogma.events
.on('nodesDragStart', () => layer.hide())
.on('nodesDragEnd', () => layer.show());
animate();
});
function animate() {
requestAnimationFrame(() => {
time += 1;
layer.refresh();
animate();
});
}
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;
}