Appearance
Editable notes and in-place forms
This example shows how to use layers to add rich texts to nodes. Click node to open a rich text form. Change the text to add tooltips to the nodes. Move and drag nodes around to see how tooltips follow them. Have a look at our annotations plugin.
ts
import Ogma, { Node, NodeId, Overlay } from '@linkurious/ogma';
const ogma = new Ogma({
graph: {
nodes: [
{ id: 0, data: { tooltip: `Your <b> text</b> here` } },
{
id: 1,
data: { tooltip: `Your <i>text</i> here` },
attributes: { x: 150, y: 0 }
}
],
edges: [{ source: 0, target: 1 }]
},
container: 'graph-container'
});
await ogma.view.locateGraph({ padding: { top: 200 }, ignoreZoomLimits: true });
ogma.styles.addNodeRule({
radius: 20
});
interface Tooltip {
layer: Overlay;
id: NodeId;
}
const tooltips: Tooltip[] = [];
const tooltipByNode: Record<NodeId, Tooltip> = {};
const addTooltipToNode = (node: Node, html: string) => {
const position = node.getPosition();
const nodeId = node.getId();
let tooltip = tooltipByNode[nodeId];
position.y -= 75;
position.x += 15;
const size = { width: 100, height: 50 };
if (tooltip) {
tooltip.layer.setPosition(position).setSize(size);
tooltip.layer.element.innerHTML = `<div class="tooltip">${html}</div>`;
} else {
const layer = ogma.layers.addOverlay({
element: `<div><div class="tooltip">${html}</div></div>`,
size: { width: 100, height: 50 },
position
});
tooltip = { layer, id: nodeId };
}
node.setData('tooltip', html);
tooltipByNode[nodeId] = tooltip;
tooltips.push(tooltip);
};
const formWidth = 300;
const formHeight = 300;
let layer: Overlay | null, currentNode: Node;
const showFormForNode = (node: Node) => {
const text = node.getData('tooltip');
currentNode = node;
// create new form
layer = ogma.layers.addLayer(`<form class="note-form">
<div id="editor">${text}</div>
<button type="submit" class="submit">Submit</button>
</form>`);
const position = node.getPositionOnScreen();
position.y -= formHeight + 15;
position.x += 15;
layer.setPosition(position).setSize({ width: formWidth, height: formHeight });
const editor = new Quill('#editor', {
theme: 'snow'
});
layer.element.addEventListener('submit', evt => {
evt.preventDefault();
//get quill editor contents
const contents = editor.getContents().ops;
// render to HTML
const html = new QuillDeltaToHtmlConverter(contents, {}).convert();
layer.destroy();
layer = null;
// create scalable tooltip layer
addTooltipToNode(node, html);
});
// disable the camera
ogma.setOptions({
interactions: {
drag: { enabled: false },
//pan: { enabled: false },
zoom: { enabled: false }
}
});
};
// move form when the camera has been moved
const onCameraMove = () => {
if (layer) {
const position = currentNode.getPositionOnScreen();
position.y -= formHeight + 15;
position.x += 15;
layer.setPosition(position);
}
};
ogma.events.on(['dragProgress', 'zoom'], onCameraMove);
// click handler
ogma.events.on('click', ({ target, domEvent }) => {
// hide form if present and the click does not originate from it
if (layer && !layer.element.contains(domEvent.target as HTMLElement)) {
layer.destroy();
layer = null;
}
// disable the camera
ogma.setOptions({
interactions: {
drag: { enabled: true },
//pan: { enabled: true },
zoom: { enabled: true }
}
});
// not a node
if (!target || !target.isNode) return;
showFormForNode(target);
});
// node has been dragged, move tooltips
ogma.events.on('nodesDragProgress', ({ dx, dy }) => {
// show and update tooltips
requestAnimationFrame(() => {
const nodes = ogma.getNodes(tooltips.map(({ id }) => id));
const positions = nodes.getPosition();
tooltips.forEach(({ layer }, i) => {
const position = positions[i];
position.x += 15;
position.y -= 75;
layer.setPosition(position);
layer.show();
});
});
});
setTimeout(() => {
const node = ogma.getNodes().get(0);
node.setSelected(true);
showFormForNode(node);
}, 500);
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="styles.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css"
/>
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quill-delta-to-html@0.12.1/dist/browser/QuillDeltaToHtmlConverter.bundle.js"></script>
</head>
<body>
<div id="graph-container"></div>
<script type="module" src="index.ts"></script>
</body>
</html>
css
html,
body {
margin: 0;
padding: 0;
}
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
}
.note, .note-form {
box-shadow: 0 0 5px rgba(0,0,0,0.25);
border-radius: 10px 10px 10px 0;
overflow: hidden;
}
.note-form {
position: absolute;
background: rgba(255,255,255,0.86);
}
.note-form #editor {
height: 200px;
}
.note-form .submit {
margin: 8px;
float: right;
}
.tooltip {
border-radius: 15px;
background-color: lightyellow;
border: 1px solid #666;
padding: 0 8px;
border-bottom-left-radius: 0;
}