Skip to content

Commit 4cf5872

Browse files
committed
add bubble clusters example
1 parent d86df46 commit 4cf5872

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

samples/bubbleclusters.html

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Sample</title>
5+
<style>
6+
body {
7+
margin: 0;
8+
padding: 0;
9+
}
10+
11+
#app {
12+
width: 800px;
13+
height: 400px;
14+
display: block;
15+
}
16+
</style>
17+
</head>
18+
<body>
19+
<div id="app"></div>
20+
<script src="https://cdn.jsdelivr.net/npm/cytoscape"></script>
21+
<script src="https://cdn.jsdelivr.net/npm/lodash"></script>
22+
<script src="../dist/cytoscapebubblesets.umd.development.js"></script>
23+
<script>
24+
function dist(a, b) {
25+
const x = a.x - b.x;
26+
const y = a.y - b.y;
27+
const r = (a.r || 0) + (b.r || 0);
28+
return Math.sqrt(x * x + y * y) - r;
29+
}
30+
31+
function boundingCircle(nodes) {
32+
if (nodes.length === 1) {
33+
return nodes[0].position();
34+
}
35+
const positions = nodes.map((d) => d.position());
36+
const x = positions.reduce((acc, p) => acc + p.x, 0);
37+
const y = positions.reduce((acc, p) => acc + p.y, 0);
38+
const center = {
39+
x: x / nodes.length,
40+
y: y / nodes.length,
41+
};
42+
center.r = positions.reduce((acc, p) => Math.max(acc, dist(center, p)), 0);
43+
return center;
44+
}
45+
46+
const MERGE_DIST = 50;
47+
const bbStyle = {
48+
fillStyle: 'steelblue',
49+
};
50+
51+
const cy = cytoscape({
52+
container: document.getElementById('app'), // container to render in
53+
54+
boxSelectionEnabled: true,
55+
panningEnabled: false,
56+
zoomingEnabled: false,
57+
layout: {
58+
name: 'grid',
59+
},
60+
style: [
61+
{
62+
selector: 'node',
63+
style: {
64+
height: 20,
65+
width: 20,
66+
label: 'data(id)',
67+
'background-color': '#18e018',
68+
},
69+
},
70+
{
71+
selector: 'node:selected',
72+
style: {
73+
'background-color': '#388438',
74+
},
75+
},
76+
],
77+
elements: {
78+
nodes: Array(10)
79+
.fill(0)
80+
.map((_, i) => ({
81+
data: {
82+
id: `id${i}`,
83+
},
84+
selectable: true,
85+
grabbable: true,
86+
})),
87+
edges: [],
88+
},
89+
});
90+
cy.ready(() => {
91+
const bb = cy.bubbleSets({
92+
zIndex: 0,
93+
throttle: 10,
94+
});
95+
cy.nodes().forEach((node) => {
96+
const path = bb.addPath(node, null, null, bbStyle);
97+
node.scratch('_bb', path);
98+
});
99+
100+
function mergeClusters(a, b) {
101+
const aPaths = a.map((d) => d.scratch('_bb'));
102+
const bPaths = b.map((d) => d.scratch('_bb'));
103+
const paths = new Set([...aPaths, ...bPaths]);
104+
if (paths.size <= 1) {
105+
// already one cluster
106+
return;
107+
}
108+
// take any and merge the others
109+
const target = cy.collection();
110+
paths.forEach((d) => {
111+
target.merge(d.nodes);
112+
d.remove();
113+
});
114+
const targetPath = bb.addPath(target, null, null, bbStyle);
115+
a.forEach((d) => {
116+
d.scratch('_bb', targetPath);
117+
});
118+
b.forEach((d) => {
119+
d.scratch('_bb', targetPath);
120+
});
121+
}
122+
123+
function splitCluster(path, aNodes, bNodes) {
124+
path.remove();
125+
console.log(aNodes.map((d) => d.id()));
126+
console.log(bNodes.map((d) => d.id()));
127+
const aPath = bb.addPath(aNodes, null, null, bbStyle);
128+
const bPath = bb.addPath(bNodes, null, null, bbStyle);
129+
aNodes.forEach((d) => {
130+
d.scratch('_bb', aPath);
131+
});
132+
bNodes.forEach((d) => {
133+
d.scratch('_bb', bPath);
134+
});
135+
}
136+
137+
cy.on(
138+
'drag',
139+
_.throttle(() => {
140+
const dragged = cy.filter(':grabbed');
141+
// extract from clusters if too far away
142+
const center = boundingCircle(dragged);
143+
const draggedPaths = new Set(dragged.map((node) => node.scratch('_bb')));
144+
draggedPaths.forEach((path) => {
145+
const otherNodes = path.nodes.diff(dragged).left;
146+
const otherCenter = boundingCircle(otherNodes);
147+
if (dist(center, otherCenter) > MERGE_DIST) {
148+
splitCluster(path, path.nodes.diff(otherNodes).left, otherNodes);
149+
}
150+
});
151+
// merge with other nodes
152+
cy.nodes()
153+
.diff(dragged)
154+
.left.forEach((node) => {
155+
if (dist(center, node.position()) < MERGE_DIST) {
156+
mergeClusters(dragged, node);
157+
}
158+
});
159+
}, 10)
160+
);
161+
});
162+
</script>
163+
</body>
164+
</html>

0 commit comments

Comments
 (0)