Skip to content
  1. Examples
  2. Layers

d3 integration

This example shows how you can use your charts and graphs developed with d3 in full sync with Ogma visualisation.

ts
import Ogma, { NodeId } from '@linkurious/ogma';
import * as d3 from 'd3';

const ogma = new Ogma({
  container: 'graph-container',
  graph: {
    nodes: [{ id: 'us' }, { id: 'de' }, { id: 'fr' }],
    edges: [
      { source: 'us', target: 'de' },
      { source: 'de', target: 'fr' },
      { source: 'fr', target: 'us' }
    ]
  },
  options: {
    interactions: {
      zoom: { maxValue: () => Infinity }
    }
  }
});

//
ogma.styles.addRule({
  nodeAttributes: {
    radius: 9,
    color: '#055761'
  },
  edgeAttributes: {
    width: 0.8
  }
});

const colors = [
  '#98abc5',
  '#8a89a6',
  '#7b6888',
  '#6b486b',
  '#a05d56',
  '#d0743c',
  '#ff8c00'
];

interface DataPoint {
  label: string;
  value: number;
}
const data = [
  { label: '<5', value: 2704659 },
  { label: '5-13', value: 4499890 },
  { label: '14-17', value: 2159981 },
  { label: '18-24', value: 3853788 },
  { label: '25-44', value: 14106543 },
  { label: '45-64', value: 8819342 },
  { label: '≥65', value: 612463 }
];

ogma.layouts.force({ locate: true }).then(() => {
  const pie = d3
    .pie<DataPoint>()
    .sort(null)
    .value(d => d.value);

  // randomize data for the sake of example
  const arcsById: Record<NodeId, d3.PieArcDatum<DataPoint>[]> = ogma
    .getNodes()
    .reduce((acc, node) => {
      acc[node.getId()] = pie(
        data.map(({ label }) => ({
          label,
          value: Math.round(Math.random() * 7e6)
        }))
      );
      return acc;
    }, {});

  const radius = 15;
  const arc = d3
    .arc<d3.PieArcDatum<DataPoint>>()
    .outerRadius(radius - radius * 0.1)
    .innerRadius(radius - radius * 0.3);

  const labelArc = d3
    .arc<d3.PieArcDatum<DataPoint>>()
    .outerRadius(radius - radius * 0.2)
    .innerRadius(radius - radius * 0.2);

  function renderDonutChart(
    ctx: CanvasRenderingContext2D,
    arcs: d3.PieArcDatum<DataPoint>[],
    x: number,
    y: number
  ) {
    ctx.save();
    ctx.translate(x, y);

    const drawArc = arc.context(ctx);
    arcs.forEach((d, i) => {
      ctx.beginPath();
      drawArc(d);
      ctx.fillStyle = colors[i];
      ctx.fill();
    });

    ctx.beginPath();
    arcs.forEach(arc);
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 0.1;
    ctx.stroke();

    ctx.font = '1px Georgia, Times, serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillStyle = '#fff';

    const drawLabelArc = labelArc.context(ctx);
    arcs.forEach(d => {
      const [x, y] = drawLabelArc.centroid(d);
      ctx.fillText(d.data.label, x, y);
    });
    ctx.restore();
  }

  const render = (ctx: CanvasRenderingContext2D) =>
    ogma.getNodes().forEach(node => {
      const id = node.getId();
      const { x, y } = node.getPosition();
      renderDonutChart(ctx, arcsById[id], x, y);
    });

  const layer = ogma.layers.addCanvasLayer(ctx => render(ctx));
  ogma.events.on(['nodesDragProgress'], () => layer.refresh());
});
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;
}