Appearance
PixiJs integration
This example shows integration with PixiJs rendering library. It allows, once added as an Ogma layer to efficiently draw custom shapes and images.
ts
import Ogma, { Layer } from '@linkurious/ogma';
import * as PIXI from 'pixi.js';
import { GUI } from 'dat.gui';
import { runLayout } from '../iphone-parts/layout';
const ogma = new Ogma({
container: 'graph-container'
});
const NODE_COLOR = '#1E1E32';
const NODE_COLOR_TRANSPARENT = 'rgba(30, 30, 50, 0.5)';
ogma.styles.addNodeRule({
color: NODE_COLOR
});
/**
* @typedef {Object} OgmaPixiLayer
* @extends {Layer}
* @property {Layer} layer Ogma `layer` controller. Use it to change the z-index.
* @property {Pixi.App} app The stage of this app is in Ogma coordinate system
* @property {() => void} destroy Destroy wrapper: removes the Pixi.js layer
* from Ogma and stops the view synchronisation
*/
interface OgmaPixiLayer extends Layer {
app: PIXI.Application;
animations: Record<string, PIXI.AnimatedSprite>;
destroy: () => this;
}
/**
* Adds a Pixi.js layer to Ogma that is synchronised with the
* camera position and movement
*/
async function addPixijsLayer(ogma: Ogma) {
const { width, height } = ogma.view.get();
const app = new PIXI.Application();
await app.init({
backgroundAlpha: 0,
transparent: true,
background: '0x00000000',
width,
height
});
const ogmaLayer = ogma.layers.addLayer(app.canvas);
const synchronizeViews = () => {
const { x, y, zoom, width, height } = ogma.view.get();
app.stage.x = -x * zoom;
app.stage.y = -y * zoom;
app.stage.scale = { x: zoom, y: zoom };
app.stage.pivot = { x: -width / 2 / zoom, y: -height / 2 / zoom };
app.renderer.render(app.stage);
};
ogma.events
.on('frame', synchronizeViews)
.on('dragProgress', () => update(layer, ogma));
const size = { width: 160, height: 160 };
const spriteData = {
frames: {},
meta: {
image: 'files/sprites.png',
size: {
w: 640,
h: 1280
}
},
animations: {}
};
const animations = ['right', 'top', 'left', 'bottom', 'sitting', 'hunting'];
animations.forEach((animation, i) => {
const y = i * size.height;
const arr: string[] = [];
for (let j = 0; j < 4; j++) {
const x = j * size.width;
const frame = {
frame: { x, y, w: size.width, h: size.height },
spriteSourceSize: { x: 0, y: 0, w: size.width, h: size.height },
sourceSize: { w: size.width, h: size.height }
};
const frameName = `${animation}${j}`;
// @ts-expect-error
spriteData.frames[frameName] = frame;
arr.push(frameName);
}
// @ts-expect-error
spriteData.animations[animation] = arr;
});
const texture = await PIXI.Assets.load(spriteData.meta.image);
const spritesheet = new PIXI.Spritesheet(texture, spriteData);
await spritesheet.parse();
return {
...ogmaLayer,
animations: spritesheet.animations,
app,
destroy() {
ogma.events.off(synchronizeViews);
ogmaLayer.destroy();
return this;
}
} as OgmaPixiLayer;
}
const layer = await addPixijsLayer(ogma);
/**
* Renders additional markup with Pixi.js
*/
function render(layer: OgmaPixiLayer, ogma: Ogma) {
const container = new PIXI.Container();
layer.app.stage.addChild(container);
const animations = layer.animations;
const names = Object.keys(animations);
ogma.getNodes().forEach((node, i) => {
const animation = animations[names[i % names.length]];
const pixiSprite = new PIXI.AnimatedSprite(animation);
pixiSprite.animationSpeed = 0.1;
pixiSprite.play();
pixiSprite.anchor.set(0.5, 0.75);
pixiSprite.x = node.getPosition().x;
pixiSprite.y = node.getPosition().y;
pixiSprite.scale.set(0.09);
container.addChild(pixiSprite);
});
}
/**
* Updates the shapes created with PixiJS
* @param {OgmaPixiLayer} layer
* @param {Ogma} ogma
*/
function update(layer: OgmaPixiLayer, ogma: Ogma) {
const container = layer.app.stage.children[0];
const sprites = container.children;
ogma
.getNodes()
.getAttributes(['x', 'y'])
.forEach(({ x, y }, i) => {
sprites[i].x = x;
sprites[i].y = y;
});
}
const settings = {
over: true,
hideLayer: () => layer.hide(),
showLayer: () => layer.show(),
removeLayer: () => layer.destroy(),
runLayout: async () => {
layer.hide();
await ogma.layouts.force({ locate: true });
update(layer, ogma);
layer.show();
}
};
const gui = new GUI();
gui.add(settings, 'hideLayer').name('Hide layer');
gui.add(settings, 'showLayer').name('Show layer');
gui.add(settings, 'runLayout').name('Run layout');
gui.add(settings, 'removeLayer').name('Remove layer');
gui
.add(settings, 'over')
.name('Over/under')
.onChange(value => {
if (value) {
layer.moveToTop();
ogma.getNodes().setAttribute('color', undefined);
} else {
layer.moveToBottom();
ogma.getNodes().setAttribute('color', NODE_COLOR_TRANSPARENT);
}
});
const graph = await ogma.generate.grid({ rows: 4, columns: 4 });
await ogma.setGraph(graph);
await ogma.view.locateGraph();
render(layer, ogma);
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="styles.css" />
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.3.4/dist/pixi.min.js"></script>
<script type="importmap">
{
"imports": {
"pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.3.4/dist/pixi.min.mjs"
}
}
</script>
</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;
}