forked from sgratzl/cytoscape.js-layers
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ctxmenu.ts
114 lines (100 loc) · 3.44 KB
/
ctxmenu.ts
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
/* eslint-disable @typescript-eslint/no-namespace */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
namespace CXTMenu {
declare const cytoscape: typeof import('cytoscape');
declare const CytoscapeLayers: typeof import('../build');
export const cy = cytoscape({
container: document.getElementById('app'),
elements: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{
data: {
id: 'ab',
source: 'a',
target: 'b',
},
},
],
});
const layers = CytoscapeLayers.layers(cy);
const menuLayer = layers.append('svg-static', {
stopClicks: true,
});
menuLayer.node.classList.add('menuItems');
const menuItems = ['A', 'B', 'C'];
for (const item of menuItems) {
menuLayer.node.insertAdjacentHTML(
'beforeend',
`<g class="menuItem">
<path></path>
<text>${item}</text>
<title>${item}</title>
</g>`
);
const g = menuLayer.node.lastElementChild! as SVGElement;
g.addEventListener('click', (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
console.log('clicked', item);
});
}
const wedgeAngle = 360 / menuItems.length;
const DEG2RAD = Math.PI / 180;
const r = Math.round;
function generateArc(i: number, innerRadius: number, outerRadius: number) {
const a0 = DEG2RAD * (wedgeAngle * i - 90);
const a1 = a0 + DEG2RAD * wedgeAngle;
const xa0 = Math.cos(a0);
const ya0 = Math.sin(a0);
const xa1 = Math.cos(a1);
const ya1 = Math.sin(a1);
return `M ${r(xa0 * outerRadius)} ${r(ya0 * outerRadius)}
A ${r(outerRadius)} ${r(outerRadius)} 0 0 1 ${r(xa1 * outerRadius)} ${r(ya1 * outerRadius)}
L ${r(xa1 * innerRadius)} ${r(ya1 * innerRadius)}
A ${r(innerRadius)} ${r(innerRadius)} 0 0 0 ${r(xa0 * innerRadius)} ${r(ya0 * innerRadius)}
Z
`;
}
let currentSelection: cytoscape.NodeSingular | null = null;
function updateMenu(e: { target: any }) {
const node = e.target as cytoscape.NodeSingular;
const pos = node.renderedPosition();
menuLayer.node.setAttribute('transform', `translate(${pos.x}, ${pos.y})`);
const innerRadius = Math.max(node.renderedWidth(), node.renderedHeight()) / 2;
const outerRadius = innerRadius + 50;
const labelRadius = innerRadius + 25;
// update menu positions and arcs
Array.from(menuLayer.node.children).forEach((g, i) => {
const path = g.querySelector('path')!;
const labelAngle = DEG2RAD * (-90 + i * wedgeAngle + wedgeAngle / 2);
path.setAttribute('d', generateArc(i, innerRadius, outerRadius));
const text = g.querySelector('text')!;
text.setAttribute('x', r(Math.cos(labelAngle) * labelRadius).toString());
text.setAttribute('y', r(Math.sin(labelAngle) * labelRadius).toString());
});
}
menuLayer.node.style.opacity = '0';
cy.on('viewport', () => {
if (currentSelection) {
updateMenu({ target: currentSelection });
}
});
cy.on('select', 'node', (e) => {
const node = e.target as cytoscape.NodeSingular;
if (currentSelection) {
currentSelection.off('position', undefined, updateMenu);
}
currentSelection = node;
updateMenu(e);
currentSelection.on('position', updateMenu);
menuLayer.node.style.opacity = '1';
});
cy.on('unselect', 'node', () => {
if (currentSelection) {
currentSelection.off('position', undefined, updateMenu);
}
currentSelection = null;
menuLayer.node.style.opacity = '0';
});
}