Skip to content
  1. Examples
  2. Graph operations

Set data

This example shows how to set the nodes and edges data and how to use it to style your vizualisation. Click on a node or an edge to modify the content of its data object. If you set the right key/value pairs, you will see the icons, texts and colors getting updated.

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

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

// Add multiple nodes
ogma.addNodes([{ id: 'n1' }, { id: 'n2' }]);

ogma.addEdge({ id: 'e1', source: 'n1', target: 'n2' });

ogma.layouts.force({ locate: true });

//create the UI with custom component to edit data
const overlay = document.getElementById('overlay')!;
let currentElement: HTMLElement | null = null;

function createUI(element) {
  closeUI();
  overlay.classList.remove('hidden');
  currentElement = element;

  const container = document.getElementById('gui-container')!;

  const data = element.getData() || {};
  const styledKeys = {
    name: {
      found: false,
      default: element.isNode ? 'Mr Washerman' : '1M suspicious transaction'
    },
    type: {
      found: false,
      default: element.isNode ? 'agent' : 'transaction'
    }
  };

  let formHTML = '<form id="data-form">';

  // Add existing data fields
  Object.keys(data).forEach(key => {
    formHTML += `
      <div class="form-group">
        <label for="${key}">${key}</label>
        <input type="text" id="${key}" name="${key}" value="${data[key] || ''}" onchange="updateElementData('${key}', this.value)">
      </div>
    `;
    if (styledKeys[key] !== undefined) {
      styledKeys[key].found = true;
    }
  });

  // finds out which suggestion to put
  const [defaultKey, defaultValue] = Object.entries(styledKeys).reduce(
    (suggested, [key, value]) => {
      if (value.found) return suggested;
      return [key, value.default];
    },
    ['key', 'value']
  );

  // Add separator and new field form
  formHTML += '<div class="form-separator"></div>';
  formHTML += `
    <div class="form-group">
      <label for="new-key">New Key</label>
      <input type="text" id="new-key" value="${defaultKey}">
    </div>
    <div class="form-group">
      <label for="new-value">New Value</label>
      <input type="text" id="new-value" value="${defaultValue}">
    </div>
    <div class="form-buttons">
      <button type="button" class="btn btn-add" onclick="addNewField()" title="Add new field">+</button>
      <button type="button" class="btn btn-secondary" onclick="closeUI()">Close</button>
    </div>
  `;

  formHTML += '</form>';
  container.innerHTML = formHTML;
}

function updateElementData(key, value) {
  if (currentElement) {
    const data = { ...currentElement.getData() };
    data[key] = value;
    currentElement.setData(data);
  }
}

function addNewField() {
  const keyInput = document.getElementById('new-key');
  const valueInput = document.getElementById('new-value');

  if (keyInput.value && currentElement) {
    let newData = {};
    newData[keyInput.value] = valueInput.value;
    newData = {
      ...currentElement.getData(),
      ...newData
    };
    currentElement.setData(newData);
    createUI(currentElement);
  }
}
overlay.addEventListener('click', () => closeUI());

Object.assign(window, {
  createUI,
  updateElementData,
  addNewField,
  closeUI
});

function closeUI() {
  const container = document.getElementById('gui-container')!;
  container.innerHTML = '';
  currentElement = null;
  overlay.classList.add('hidden');
}

// link the UI to Ogma events
ogma.events.on('click', ({ target }) => {
  if (!target) {
    return closeUI();
  }
  createUI(target);
});

// add some styleRule to illustrate the power of data
const nodeStylePerType = {
  agent: {
    color: 'gold',
    icon: {
      content: getIconCode('icon-circle-user-round')
    }
  },
  money: {
    color: 'green',
    icon: {
      content: getIconCode('icon-banknote')
    }
  },
  default: {
    color: 'grey',
    icon: {
      font: 'Lucide',
      color: 'black',
      style: 'bold'
    }
  }
};

const edgeStylePerType = {
  transaction: {
    shape: {
      head: 'arrow'
    },
    color: 'green'
  }
};

ogma.styles.addRule({
  nodeAttributes: node => {
    const style = nodeStylePerType[node.getData('type')] || {};
    return { ...nodeStylePerType.default, ...style };
  },
  edgeAttributes: edge => {
    const style = edgeStylePerType[edge.getData('type')] || {};
    return { ...edgeStylePerType.default, ...style };
  }
});
ogma.styles.addNodeRule({
  text: node => node.getData('name') || 'unnamed'
});
ogma.styles.addEdgeRule({
  text: edge => edge.getData('name') || 'unnamed'
});
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"
    />
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
  </head>
  <body>
    <div id="graph-container"></div>
    <div id="overlay" class="hidden"></div>
    <div id="gui-container"></div>
    <script src="index.ts"></script>
  </body>
</html>
css
:root {
  --primary-color: #007acc;
  --secondary-color: #f5f5f5;
  --text-color: #333;
  --border-color: #ddd;
  --hover-bg-color: #e8e8e8;
  --font: 'IBM Plex Sans', sans-serif;
}

body {
  margin: 0;
  font-family: var(--font);
  background-color: #f0f0f0;
}

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

#overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 0.5;
  background-color: #888;
}

#data-form {
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  z-index: 100;
  opacity: 1;
  background: white;
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 20px;
  min-width: 300px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.hidden {
  display: none;
}

.form-group {
  margin-bottom: 15px;
}

.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: 500;
  color: #333;
}

.form-group input {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  box-sizing: border-box;
}

.form-group input:focus {
  outline: none;
  border-color: #007acc;
  box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
}

.form-separator {
  height: 1px;
  background: #eee;
  margin: 20px 0;
}

.form-buttons {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}

.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: background-color 0.2s;
}

.btn-primary {
  background: #007acc;
  color: white;
}

.btn-primary:hover {
  background: #005a9e;
}

.btn-secondary {
  background: #f5f5f5;
  color: #333;
  border: 1px solid #ddd;
}

.btn-secondary:hover {
  background: #e8e8e8;
}

.btn-add {
  background: #28a745;
  color: white;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  font-weight: bold;
}

.btn-add:hover {
  background: #218838;
}
js
// 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) {
  placeholder.className = className;
  const code = getComputedStyle(placeholder, ':before').content;
  return code[1];
}