Appearance
Overlays + annotations
This example shows how you can dynamically illustrate the data flow in a neural network. It combines area annotations with zoomable node annotations. You can also click on the nodes to show, hide or fade their overlays.
ts
import Ogma from '@linkurious/ogma';
const ogma = new Ogma({
container: 'graph-container',
options: {
backgroundColor: null
}
});
await ogma.addGraph({
nodes: [
{ id: 'source', data: { type: 'source' } },
{
id: 'pattern1',
attributes: { x: 200, y: -300 },
data: { type: 'pattern' }
},
{
id: 'pattern2',
attributes: { x: 200, y: -100 },
data: { type: 'pattern' }
},
{
id: 'pattern3',
attributes: { x: 200, y: 100 },
data: { type: 'pattern' }
},
{
id: 'pattern4',
attributes: { x: 200, y: 300 },
data: { type: 'pattern' }
},
{
id: 'classifier',
attributes: { x: 500, y: 0 },
data: { type: 'classifier' }
}
],
edges: [
{ source: 'source', target: 'pattern1' },
{ source: 'source', target: 'pattern2' },
{ source: 'source', target: 'pattern3' },
{ source: 'source', target: 'pattern4' },
{ source: 'pattern1', target: 'classifier' },
{ source: 'pattern2', target: 'classifier' },
{ source: 'pattern3', target: 'classifier' },
{ source: 'pattern4', target: 'classifier' }
]
});
ogma.styles.addNodeRule({
color: n => (n.getData('type') === 'source' ? 'red' : '#307700'),
radius: n => (n.getData('type') === 'pattern' ? 15 : 30),
icon: { color: 'white' },
text: n => ({
content: n.getId(),
minVisibleSize: 0,
position: n.getData('type') === 'source' ? 'right' : 'left'
})
});
const overlays = ogma.getNodes().map((node, i) => {
const type = node.getData('type');
const pos = node.getPosition();
const imageSize = 700;
const width = 100,
height = 100;
const offset = type === 'source' ? -105 : 5;
const level = type === 'source' ? 0 : 1;
// additional scaling to fit into the bubble
const scale = width / imageSize;
const halfSize = imageSize / 2;
// calculate pattern position
let cssPosition =
level === 0
? getQuadrant(imageSize, imageSize, imageSize, imageSize, 0, scale)
: getQuadrant(imageSize, imageSize, halfSize, halfSize, i - 1, scale);
// link the overlay with the node for updates
node.setData('overlayId', i);
// edge detector picture
if (type === 'classifier')
return ogma.layers.addOverlay({
element: `<div class="image">
<img src="img/aerial-filtered.jpg" />
</div>`,
position: {
x: pos.x + 10,
y: pos.y - 150
},
size: { width: '300px', height: '300px' }
});
return ogma.layers.addOverlay({
element: `<div class="image">
<img src="img/aerial.jpg" style="${cssPosition}" />
</div>`,
position: { x: pos.x + offset, y: pos.y - height / 2 },
size: { width: `${width}px`, height: `${height}px` }
});
});
overlays.forEach(overlay => overlay.moveToBottom());
const zones = ogma.layers.addCanvasLayer(ctx => {
const src = ogma.getNode('source')!.getPosition();
const target = ogma.getNode('classifier')!.getPosition();
ctx.fillStyle = 'rgba(0,120,256, 0.25)';
ctx.beginPath();
ctx.rect(src.x - 130, src.y - 350, 210, 700);
ctx.fill();
ctx.fillStyle = 'rgba(40, 40, 40, 0.85)';
ctx.font = '24px Georgia, serif';
ctx.fillText('Input source', src.x - 120, src.y - 315);
ctx.fillStyle = 'rgba(0,120,256, 0.25)';
ctx.beginPath();
ctx.rect(target.x - 130, target.y - 350, 510, 700);
ctx.fill();
ctx.fillStyle = 'rgba(40, 40, 40, 0.85)';
ctx.font = '24px Georgia, serif';
ctx.fillText(
'Edge detector & classification',
target.x - 120,
target.y - 315
);
});
zones.moveToBottom();
// disable node dragging
await ogma.getNodes().setAttribute('draggable', false);
const FADED = 0.3;
const SHOWN = 1;
// show/hide overlays on click
ogma.events.on('click', ({ target }) => {
if (target && target.isNode) {
const overlay = overlays[target.getData('overlayId')];
// show/hide for patterns
if (target.getData('type') === 'pattern') {
if (overlay.isHidden()) {
target.setAttribute('icon.content', null);
return overlay.show();
}
target.setAttribute('icon.content', '+');
return overlay.hide();
}
// fade/reveal for the others
overlay.setOpacity(overlay.getOpacity() === FADED ? SHOWN : FADED);
}
});
await ogma.view.locateGraph({
padding: {
top: 50,
left: 100,
right: 250,
bottom: 50
}
});
function getQuadrant(
imageWidth: number,
imageHeight: number,
w: number,
h: number,
n: number,
s: number
) {
const zoom = w / imageWidth;
const l = imageWidth / w;
const col = n >> 1,
row = n - l * col;
return `
position: absolute;
width: ${imageWidth / zoom}px;
height: ${imageHeight / zoom}px;
left: ${(-col * w) / zoom}px;
top: ${(-row * h) / zoom}px;
zoom: ${(s / zoom) * zoom};
`.replace(/\n\s+/g, ''); // should be 1 line
}
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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;
}
.image {
border: 1px solid white;
overflow: hidden;
box-shadow: 0 0 10px rgba(0,0,0,0.5)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21