Skip to content
  1. Examples

Custom charts

This example shows how to generate custom textures that you can apply on nodes to create features that are not native to Ogma, such as donut charts.
It uses the image attribute to display the generated images.

js
import Ogma from '@linkurious/ogma';
import { pieChart, donutChart } from './charts.js';

const ogma = new Ogma({
  container: 'graph-container'
});

const MAX = 220;
const PIE = 0;
const DONUT = 1;

ogma.generate
  .random({ nodes: 10, edges: 30 })
  .then(graph => {
    // fill dummy values for the charts
    graph.nodes.forEach(node => {
      const value1 = Math.random() * MAX;
      const value2 = (MAX - value1) * Math.random();
      node.data = {
        value1,
        value2,
        chartType: Math.random() > 0.5 ? PIE : DONUT
      };
    });
    return ogma.addGraph(graph);
  })
  .then(() => ogma.layouts.force({ locate: true }));

const textureCache = {};

//On the Ogma code you could re-define your style rule as follow:
ogma.styles.addNodeRule({
  // raw number for the wedges here, the function will compute the %
  image: node => {
    // chart wedges
    const data = [node.getData('value1'), node.getData('value2'), MAX];
    // custom colors for the wedges
    const palette = ['#f05365', '#fca311', '#605770'];
    const type = node.getData('chartType');

    // if you have a lot of data, we recommend to cache the texture
    const textureId = [type, ...data].join('_');
    if (!textureCache[textureId]) {
      // render the texture if not cached
      textureCache[textureId] =
        type === DONUT
          ? donutChart(data, palette, '#fff', 0.6)
          : pieChart(data, palette);
    }

    return textureCache[textureId];
  },
  radius: 10
});
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 src="index.js"></script>
  </body>
</html>
css
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}
js
function renderPieChart(data, palette, ctx, size) {
  let lastend = -Math.PI / 2; // start at 12:00
  let total = 0;

  for (let e = 0; e < data.length; e++) total += data[e];

  const halfWidth = size / 2;
  const halfHeight = size / 2;

  for (let i = 0; i < data.length; i++) {
    const slice = data[i] / total;
    ctx.fillStyle = palette[i];
    ctx.beginPath();
    ctx.moveTo(halfWidth, halfHeight);
    ctx.arc(
      halfWidth,
      halfHeight,
      halfHeight,
      lastend,
      lastend + Math.PI * 2 * slice,
      false
    );
    ctx.lineTo(halfWidth, halfHeight);
    ctx.fill();
    lastend += Math.PI * 2 * slice;
  }
}

export function pieChart(data, palette) {
  const canvas = document.createElement('canvas');
  // make it 256px per side to be crispy on retina displays
  const size = 256;
  canvas.width = canvas.height = size;
  const ctx = canvas.getContext('2d');
  renderPieChart(data, palette, ctx, size);
  return canvas.toDataURL();
}

export function donutChart(data, palette, centerFillColor, innerRadius = 0.5) {
  const canvas = document.createElement('canvas');
  // make it 256px per side to be crispy on retina displays
  const size = 256;
  canvas.width = canvas.height = size;
  const ctx = canvas.getContext('2d', { willReadFrequently: true });
  const halfWidth = size / 2;
  const halfHeight = size / 2;

  renderPieChart(data, palette, ctx, size);

  // now draw a circle at the center of the pie chart to make it become a donut chart
  ctx.fillStyle = centerFillColor;
  ctx.beginPath();
  ctx.moveTo(halfWidth, halfHeight);
  ctx.arc(halfWidth, halfHeight, innerRadius * halfHeight, 0, Math.PI * 2);
  ctx.lineTo(halfWidth, halfHeight);
  ctx.fill();

  return canvas.toDataURL();
}
html
This example shows how you can create a custom donut chart on each node.