Skip to content
  1. Examples
  2. Node styles

Badge

This example shows how to add Badges to nodes. Nodes can have up to 4 badges (top left, top right, bottom left, bottom-right). Badges have a stroke, a background (color or image) and can contain text or icons. They can also be fixed in size or scale with the node.

ts
import { Ogma, Node } from '@linkurious/ogma';

const COLORS = [
  '#965E04',
  '#C89435',
  '#F7A456',
  '#AFCF8A',
  '#7B39DE',
  '#B095C0',
  '#D24556',
  '#93C2FA',
  '#9DB09E',
  '#F8C821'
];

const placeholder = document.createElement('span');
document.body.appendChild(placeholder);
placeholder.style.visibility = 'hidden';

// helper routine to get the icon HEX code
function getIconCode(className: string) {
  placeholder.className = `icon-${className}`;
  const code = getComputedStyle(placeholder, ':before').content;
  return code[1];
}

const ICONS = [
  getIconCode('mic'),
  getIconCode('mic-off'),
  getIconCode('shield-half'),
  getIconCode('keyboard'),
  getIconCode('circle-help'),
  getIconCode('info'),
  getIconCode('flag'),
  getIconCode('settings')
];

const ROW_LENGTH = 5;

const ogma = new Ogma({
  container: 'graph-container',
  options: {
    interactions: {
      zoom: { minValue: () => 1e-5, maxValue: () => 1e7 }
    }
  }
});

const layer = ogma.layers.addCanvasLayer(ctx => {
  const bounds = ogma.getNodes().getBoundingBox();
  const rows = ogma.getNodes().size / ROW_LENGTH;

  if (!isFinite(bounds.width) || bounds.width === 0) return;

  ctx.beginPath();

  const maxSize = bounds.maxScaledSize / 2;

  const minX = bounds.minX - maxSize;
  const minY = bounds.minY - maxSize * 2;
  const height = bounds.height + maxSize * 2;
  const stepY = height / rows + 20;

  ctx.font = `${9}px IBM Plex Sans`;
  ctx.fillStyle = ctx.strokeStyle = 'rgba(0, 0, 0, 0.75)';
  ctx.lineWidth = 0.3;
  //ctx.rect(minX, minY, width, height);
  for (let i = 0; i < rows; i++) {
    let x = minX - 5;
    let y = minY + i * stepY;
    ctx.fillText(i % 2 === 0 ? 'Fixed' : 'Scaled', x, y);
    ctx.moveTo(x, y + 2);
    ctx.lineTo(x + bounds.width + maxSize * 2, y + 2);
  }
  ctx.stroke();
});
layer.moveToBottom();

function getColor(node: Node) {
  const id = +node.getId();
  return COLORS[id % COLORS.length];
}

function isEvenRow(node: Node) {
  return Math.floor(+node.getId() / ROW_LENGTH) % 2 === 0;
}

// Rules to specify the node style. Functions must be deterministic.
ogma.styles.addRule({
  nodeAttributes: {
    color: getColor,
    text: {
      content: n => 'Node ' + n.getId(),
      font: 'IBM Plex Sans'
    },
    //radius: 15,
    badges: {
      bottomRight: {
        color: '#fff',
        scalingMethod: n => (isEvenRow(n) ? 'fixed' : 'scaled'),
        text: {
          font: 'Lucide', // Use FontAwesome icons.
          color: '#336', // Use the node color.
          // Retrieve the icon based on the node id.
          content: n => ICONS[+n.getId() % ICONS.length]
        },
        minVisibleSize: 30,
        stroke: {
          color: getColor, // Use the node color
          width: 2
        },
        scale: n => {
          if (isEvenRow(n))
            return ogma.styles.fontSizeToBadgeScale(
              18,
              +n.getAttribute('radius')
            );
          return 0.45;
        }
      }
    }
  },
  nodeDependencies: {
    self: { attributes: 'all' }
  }
});

// Generate a random graph
const graph = await ogma.generate.random({ nodes: ROW_LENGTH * 2, edges: 0 });
graph.nodes.forEach((node, i) => {
  node.attributes = { radius: 8 + (i % ROW_LENGTH) * 2 };
});
await ogma.setGraph(graph);
await ogma.layouts.grid({
  locate: { padding: { top: 120 } },
  colDistance: 20,
  rowDistance: 50,
  rows: 2
});
// Change the badge of a single node: use a
// background image and some text content.
ogma.getNode('0')!.setAttributes({
  badges: {
    bottomRight: {
      image: 'flags/fr.svg',
      text: {
        font: 'IBM Plex Sans',
        style: 'bold',
        content: 'FR',
        color: 'black'
      }
    }
  }
});
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <link type="text/css" rel="stylesheet" href="styles.css" />
    <link
      href="https://cdn.jsdelivr.net/npm/lucide-static@0.483.0/font/lucide.css"
      rel="stylesheet"
    />
    <link
      href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
      rel="stylesheet"
    />
  </head>

  <body>
    <div id="graph-container"></div>
    <script src="index.ts"></script>
  </body>
</html>
css
#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}