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