Skip to content
  1. Examples

Betweenness

This example shows how to use the betweenness centrality algorithm. It uses the betweenness method as well as slices rules to color and scale the nodes according to their betweenness centrality score.

ts
import Ogma from '@linkurious/ogma';

const ogma = new Ogma({ container: 'graph-container' });
ogma.styles.addEdgeRule({
  shape: {
    head: 'arrow'
  }
});
const styleRule = ogma.styles.addNodeRule({});
const normalizeInput = document.querySelector(
  '#normalize>input'
) as HTMLInputElement;
const directedInput = document.querySelector(
  '#directed>input'
) as HTMLInputElement;

function computeBetweenness() {
  const nodes = ogma.getNodes();
  const edges = ogma.getEdges();
  const betweenness = ogma.algorithms.betweenness({
    nodes,
    edges,
    directed: directedInput.checked,
    normalized: normalizeInput.checked
  });
  nodes.setData('betweenness', betweenness);
  console.log(betweenness);
  const sliceOptions = {
    field: 'betweenness',
    stops: { min: Math.min(...betweenness), max: Math.max(...betweenness) }
  };
  const radiusRule = ogma.rules.slices({
    ...sliceOptions,
    values: { nbSlices: 4, min: 5, max: 25 },
    fallback: 1
  });
  const colorRule = ogma.rules.slices({
    ...sliceOptions,
    values: ['#48c78e', '#ffe08a', '#7957d5', '#f14668'],
    fallback: 1
  });
  const fontRule = ogma.rules.slices({
    ...sliceOptions,
    values: { nbSlices: 4, min: 5, max: 18 },
    fallback: 1
  });

  styleRule.update({
    nodeAttributes: {
      radius: radiusRule,
      color: colorRule,
      text: {
        content: node => node.getData('betweenness').toPrecision(2),
        size: fontRule,
        margin: 4,
        minVisibleSize: 0
      }
    }
  });
  return styleRule
    .whenApplied()
    .then(() => ogma.layouts.force({ locate: true }));
}

ogma.generate
  .random({
    nodes: 80,
    edges: 120
  })
  .then(graph => ogma.setGraph(graph))
  .then(() => computeBetweenness())
  .then(() => {
    ogma.tools.legend.enable({
      position: 'left',
      borderColor: 'grey',
      titleFunction: propertyPath => {
        // propertyPath is an array
        return (propertyPath as string[]).join('.');
      }
    });
  });

normalizeInput.addEventListener('change', () => {
  computeBetweenness();
});

directedInput.addEventListener('change', () => {
  computeBetweenness();
});
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>
    <div class="toolbar panel" id="ui">
      <h2>Betweenness options</h2>
      <div class="switch-container">
        <label>Normalized</label>
        <div class="button r" id="normalize">
          <input type="checkbox" class="checkbox" checked />
          <div class="knobs"></div>
          <div class="layer"></div>
        </div>
      </div>
      <div class="switch-container">
        <label>Directed</label>
        <div class="button r" id="directed">
          <input type="checkbox" class="checkbox" checked />
          <div class="knobs"></div>
          <div class="layer"></div>
        </div>
      </div>
    </div>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
.switch-container {
  width: 200px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1em;
}

.toggle-button-cover {
  display: table-cell;
  position: relative;
  width: 200px;
  height: 140px;
  box-sizing: border-box;
}

.button-cover {
  height: 100px;
  margin: 20px;
  background-color: #fff;
  box-shadow: 0 10px 20px -8px #c5d6d6;
  border-radius: 4px;
}

.button-cover:before {
  counter-increment: button-counter;
  content: counter(button-counter);
  position: absolute;
  right: 0;
  bottom: 0;
  color: #d7e3e3;
  font-size: 12px;
  line-height: 1;
  padding: 5px;
}

.button-cover,
.knobs,
.layer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.button {
  position: relative;
  top: 50%;
  width: 74px;
  height: 36px;
  overflow: hidden;
}

.button,
.layer {
  border-radius: 100px;
}

.checkbox {
  position: relative;
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 3;
}

.knobs {
  z-index: 2;
}

.layer {
  width: 100%;
  transition: 0.3s ease all;
  background-color: #fcebeb;
  z-index: 1;
}

.knobs:before {
  content: 'NO';
  position: absolute;
  top: 4px;
  left: 4px;
  width: 20px;
  height: 10px;
  color: #fff;
  font-size: 10px;
  font-weight: bold;
  text-align: center;
  line-height: 1;
  padding: 9px 4px;
  background-color: #f44336;
  border-radius: 50%;
  transition: 0.3s cubic-bezier(0.18, 0.89, 0.35, 1.15) all;
}

.checkbox:checked + .knobs:before {
  content: 'YES';
  left: 42px;
  background-color: #03a9f4;
}

.checkbox:checked ~ .layer {
  background-color: #ebf7fc;
}

.knobs,
.knobs:before,
.layer {
  transition: 0.3s ease all;
}

.toolbar {
  display: block;
  position: absolute;
  top: 20px;
  right: 20px;
  padding: 10px;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
  border-radius: 4px;
  background: #ffffff;
  color: #222222;
  font-weight: 300;
  z-index: 9999;
}

.toolbar .section {
  position: relative;
  display: block;
}

.toolbar .section h3 {
  display: block;
  font-weight: 300;
  border-bottom: 1px solid #ddd;
  color: #606060;
  font-size: 1rem;
}

.toolbar .section .clearable-input {
  border-radius: 4px;
  padding: 5px;
  border: 1px solid #dddddd;
}

.toolbar .controls {
  text-align: center;
  margin-top: 10px;
}

html,
body {
  font-family: 'Inter', sans-serif;
}

:root {
  --base-color: #4999f7;
  --active-color: var(--base-color);
  --gray: #d9d9d9;
  --white: #ffffff;
  --lighter-gray: #f4f4f4;
  --light-gray: #e6e6e6;
  --inactive-color: #cee5ff;
  --group-color: #525fe1;
  --group-inactive-color: #c2c8ff;
  --selection-color: #04ddcb;
  --darker-gray: #b6b6b6;
  --dark-gray: #555;
  --dark-color: #3a3535;
  --edge-color: var(--dark-color);
  --border-radius: 5px;
  --button-border-radius: var(--border-radius);
  --edge-inactive-color: var(--light-gray);
  --button-background-color: #ffffff;
  --shadow-color: rgba(0, 0, 0, 0.25);
  --shadow-hover-color: rgba(0, 0, 0, 0.5);
  --button-shadow: 0 0 4px var(--shadow-color);
  --button-shadow-hover: 0 0 4px var(--shadow-hover-color);
  --button-icon-color: #000000;
  --button-icon-hover-color: var(--active-color);
}

#graph-container {
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: absolute;
  margin: 0;
  overflow: hidden;
}

.ui {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 0.5em;
}

#custom-group-btn {
  top: 40px;
}

.panel {
  background: var(--button-background-color);
  border-radius: var(--button-border-radius);
  box-shadow: var(--button-shadow);
  padding: 10px;
}

.panel {
  position: absolute;
  top: 1em;
  right: 1em;
  width: 15em;
  padding: 1em;
}

.panel h2 {
  text-transform: uppercase;
  font-weight: 400;
  font-size: 14px;
  margin: 0;
  margin-bottom: 1em;
}

.panel {
  margin-top: 1px;
  text-align: center;
}

.panel button {
  background: var(--button-background-color);
  border: none;
  border-radius: var(--button-border-radius);
  border-color: var(--shadow-color);
  padding: 5px 10px;
  cursor: pointer;
  width: 100%;
  color: var(--dark-gray);
  border: 1px solid var(--light-gray);
}

.panel button:hover {
  background: var(--lighter-gray);
  border: 1px solid var(--darker-gray);
}

.panel button[disabled] {
  color: var(--light-gray);
  border: 1px solid var(--light-gray);
  background-color: var(--lighter-gray);
}