Appearance
Context menu
This example shows how to use the layers API to add a DOM context menu to the graph. The context menu will appear when you right-click on a node and will allow you to remove the node from the graph.
ts
import Ogma, { Overlay, Node } from '@linkurious/ogma';
const ogma = new Ogma({
graph: {
nodes: [{ id: 0 }, { id: 1 }],
edges: [{ source: 0, target: 1 }]
},
container: 'graph-container'
});
ogma.layouts.force({ locate: true });
let contextmenu: Overlay | null = null;
const expand = (node: Node) => {
const maxNId = ogma.getNodes().size;
const maxEId = ogma.getEdges().size;
return ogma.generate
.balancedTree({ children: 3, height: 1 })
.then(subGraph => {
subGraph.nodes.forEach(n => {
n.id = +n.id! + maxNId;
});
subGraph.edges.forEach(e => {
e.id = +e.id! + maxEId;
e.source = +e.source! + maxNId;
e.target = +e.target! + maxNId;
});
subGraph.edges.push({
source: node.getId(),
target: subGraph.nodes[0].id!
});
return ogma.addGraph(subGraph);
})
.then(() => ogma.layouts.force({ locate: true }));
};
function createMenuItem(text: string, action: (evt: MouseEvent) => void) {
const item = document.createElement('span');
item.classList.add('item');
item.innerText = text;
item.addEventListener('click', action);
item.addEventListener('click', evt => {
evt.preventDefault();
evt.stopPropagation();
destroyContextMenu();
});
return item;
}
function showContextMenu(node: Node) {
const expandButton = createMenuItem('Expand', evt => {
evt.preventDefault();
evt.stopPropagation();
expand(node);
});
const destroyButton = createMenuItem('Remove', evt => {
evt.preventDefault();
evt.stopPropagation();
ogma.removeNode(node);
});
const div = document.createElement('div');
div.classList.add('context-menu');
div.appendChild(expandButton);
div.appendChild(destroyButton);
// to be able to measure the element
document.body.appendChild(div);
const screenPos = node.getPositionOnScreen();
const radius = node.getAttribute('radius');
const scaledRadius = +radius * ogma.view.getZoom();
const offsetWidth = Math.min(100, div.offsetWidth);
const offsetHeight = div.offsetHeight;
const { width, height } = ogma.view.getSize();
let x = screenPos.x + scaledRadius;
let y = screenPos.y + scaledRadius;
if (screenPos.x + scaledRadius + offsetWidth > width) {
x = Math.max(0, screenPos.x - scaledRadius - offsetWidth);
}
if (screenPos.y + scaledRadius + offsetHeight > height) {
y = Math.max(0, screenPos.y - scaledRadius - offsetHeight);
}
const position = ogma.view.screenToGraphCoordinates({ x, y });
contextmenu = ogma.layers.addOverlay({
element: div,
position,
scaled: false,
size: { width: 100, height: 'auto' }
});
}
function destroyContextMenu() {
contextmenu?.destroy();
contextmenu = null;
}
ogma.events.on('click', ({ target, domEvent }) => {
const isContextMenuTarget =
contextmenu && contextmenu.element.contains(domEvent.target as HTMLElement);
if (isContextMenuTarget) return; // if the target is the context menu just let its item handle the propagated click
if (contextmenu) return destroyContextMenu(); // otherwise, if there is a context menu, destroy it
if (target && target.isNode) showContextMenu(target); // otherwise, if the target is a node, create it
});
setTimeout(() => {
const node = ogma.getNodes().get(0);
node.setSelected(true);
showContextMenu(node);
}, 500);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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>
<script type="module" src="index.ts"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
css
html,
body {
margin: 0;
padding: 0;
}
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
}
.context-menu {
font-family: Georgia, 'Times New Roman', Times, serif;
border: 1px solid #ccc;
border-radius: 3px;
background-color: #eee;
padding: 1px;
}
.context-menu > .item {
border-bottom: 1px solid #ccc;
background: white;
color: #444;
display: block;
padding: 5px 10px;
cursor: pointer;
}
.context-menu > .item:hover {
background: #ddd;
color: #222;
}
.context-menu > .item:last-child {
border-bottom: none;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40