Skip to content
  1. Examples

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