Skip to content
  1. Examples
  2. Transformations

Neighbor generation

This example shows how to use the neighbor generation transformation to generate nodes and edges between elements, making them neighbors.

ts
import Ogma, { NeighborGeneration } from '@linkurious/ogma';
import { getIconCode } from './utils';

interface ND {
  type: 'person' | 'country';
  name: string;
  country?: string;
}

const ogma = new Ogma<ND>({
  container: 'graph-container',
  options: {
    backgroundColor: '#f6f6f6'
  },
  graph: {
    nodes: [
      {
        id: 0,
        data: { type: 'person', name: 'John Smith', country: 'USA' },
        attributes: { x: 0, y: 0 }
      },
      {
        id: 2,
        data: { type: 'person', name: 'James Brown', country: 'USA' },
        attributes: { x: 60, y: 0 }
      }
    ],
    edges: []
  }
});

const ICONS = {
  person: getIconCode('icon-circle-user-round'),
  country: getIconCode('icon-flag')
};

const COLORS = {
  person: 'orange',
  country: 'dodgerblue'
};

ogma.styles.addRule({
  nodeAttributes: {
    text: node => node.getData('name'),
    color: node => COLORS[node.getData('type')],
    icon: {
      content: node => ICONS[node.getData('type')],
      font: 'Lucide'
    }
  }
});

const button = document.getElementById('btn') as HTMLButtonElement;
let transformation: NeighborGeneration<ND, unknown> | null = null;

const enableButton = () => {
  button.disabled = false;
  button.textContent = button.textContent === 'Generate' ? 'Undo' : 'Generate';
};

document.getElementById('btn')!.addEventListener('click', () => {
  if (transformation) {
    transformation.destroy(500).then(enableButton);
    button.disabled = true;
    transformation = null;
  } else {
    transformation = ogma.transformations.addNeighborGeneration({
      selector: node => node.getData('type') === 'person',
      neighborIdFunction: node => node.getData('country')!,
      nodeGenerator: (countryName, nodes) => {
        console.log('node created from nodes', nodes.getData('name'));

        return { data: { type: 'country', name: countryName } };
      },
      duration: 500
    });
    button.disabled = true;
    transformation.whenApplied().then(enableButton);
  }
});
html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <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"
    />

    <link type="text/css" rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="graph-container"></div>
    <div class="panel">
      <button id="btn">Generate</button>
    </div>
    <script type="module" src="index.ts"></script>
  </body>
</html>
css
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: 20px;
  left: 20px;
}

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

.panel {
  margin-top: 1px;
  padding: 5px 10px;
  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);
}
ts
// dummy icon element to retrieve the HEX code, it should be hidden
const placeholder = document.createElement('span');
document.body.appendChild(placeholder);
placeholder.style.visibility = 'hidden';

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