Appearance
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];
}