Skip to content

Commit 07474e0

Browse files
Whf0403whf01206399
andauthored
feat(site): add algorithm/gaddi demo (#5711)
* feat: add algorithm/gaddi demo * feat: add algorithm/shortestPath demo * feat: adjust examples/algorithm structure & add label-propagation demo * feat: add algorithm/louvain demo --------- Co-authored-by: whf01206399 <[email protected]>
1 parent 07c77ee commit 07474e0

File tree

8 files changed

+388
-0
lines changed

8 files changed

+388
-0
lines changed

packages/site/.dumirc.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,14 @@ export default defineConfig({
271271
en: 'Performance',
272272
},
273273
},
274+
{
275+
slug: 'algorithm',
276+
icon: 'gallery',
277+
title: {
278+
zh: '算法',
279+
en: 'Algorithm',
280+
},
281+
},
274282
],
275283
mdPlayground: {
276284
// 第一个分块的大小
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* LP 自动聚类
3+
*/
4+
import { labelPropagation } from '@antv/algorithm';
5+
import { Graph } from '@antv/g6';
6+
7+
const button = document.createElement('button');
8+
button.innerHTML = `Click Here to Clustering 点此自动聚类`;
9+
document.getElementById('container').appendChild(button);
10+
11+
const subjectColors = [
12+
'#5F95FF', // blue
13+
'#61DDAA',
14+
'#65789B',
15+
'#F6BD16',
16+
'#7262FD',
17+
'#78D3F8',
18+
'#9661BC',
19+
'#F6903D',
20+
'#008685',
21+
'#F08BB4',
22+
];
23+
24+
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json')
25+
.then((res) => res.json())
26+
.then((data) => {
27+
const graph = new Graph({
28+
container: 'container',
29+
data,
30+
autoFit: 'view',
31+
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
32+
layout: {
33+
type: 'force',
34+
linkDistance: 50,
35+
},
36+
});
37+
graph.render();
38+
39+
button.addEventListener('click', (e) => {
40+
const clusteredData = labelPropagation(data, false);
41+
let newNodeData = [];
42+
clusteredData.clusters.forEach((cluster, i) => {
43+
const color = subjectColors[i % subjectColors.length];
44+
const nodes = cluster.nodes.map((node) => ({
45+
id: node.id,
46+
style: {
47+
fill: color,
48+
},
49+
}));
50+
newNodeData.push(...nodes);
51+
});
52+
graph.updateNodeData(newNodeData);
53+
graph.draw();
54+
});
55+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* LOUVAIN 自动聚类
3+
*/
4+
import { louvain } from '@antv/algorithm';
5+
import { Graph } from '@antv/g6';
6+
7+
const button = document.createElement('button');
8+
button.innerHTML = `Click Here to Clustering 点此自动聚类`;
9+
document.getElementById('container').appendChild(button);
10+
11+
const subjectColors = [
12+
'#5F95FF', // blue
13+
'#61DDAA',
14+
'#65789B',
15+
'#F6BD16',
16+
'#7262FD',
17+
'#78D3F8',
18+
'#9661BC',
19+
'#F6903D',
20+
'#008685',
21+
'#F08BB4',
22+
];
23+
24+
fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/relations.json')
25+
.then((res) => res.json())
26+
.then((data) => {
27+
const graph = new Graph({
28+
container: 'container',
29+
data,
30+
autoFit: 'view',
31+
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
32+
layout: {
33+
type: 'force',
34+
linkDistance: 50,
35+
},
36+
});
37+
graph.render();
38+
39+
button.addEventListener('click', (e) => {
40+
const clusteredData = louvain(data, false);
41+
let newNodeData = [];
42+
clusteredData.clusters.forEach((cluster, i) => {
43+
const color = subjectColors[i % subjectColors.length];
44+
const nodes = cluster.nodes.map((node) => ({
45+
id: node.id,
46+
style: {
47+
fill: color,
48+
},
49+
}));
50+
newNodeData.push(...nodes);
51+
});
52+
graph.updateNodeData(newNodeData);
53+
graph.draw();
54+
});
55+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"title": {
3+
"zh": "中文分类",
4+
"en": "Category"
5+
},
6+
"demos": [
7+
{
8+
"filename": "pattern-matching.js",
9+
"title": {
10+
"zh": "图模式匹配",
11+
"en": "Graph pattern matching"
12+
},
13+
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*a9eSQa4f4Y4AAAAAAAAAAAAADmJ7AQ/original"
14+
},
15+
{
16+
"filename": "shortest-path.js",
17+
"title": {
18+
"zh": "最短路径",
19+
"en": "Shortest path"
20+
},
21+
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*wQy2TZkb-VgAAAAAAAAAAAAADmJ7AQ/original"
22+
},
23+
{
24+
"filename": "label-propagation.js",
25+
"title": {
26+
"zh": "LP 自动聚类",
27+
"en": "LP automatic clustering"
28+
},
29+
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1QHsR4wHCKwAAAAAAAAAAAAADmJ7AQ/original"
30+
},
31+
{
32+
"filename": "louvain.js",
33+
"title": {
34+
"zh": "LOUVAIN 自动聚类",
35+
"en": "LOUVAIN automatic clustering"
36+
},
37+
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*1QHsR4wHCKwAAAAAAAAAAAAADmJ7AQ/original"
38+
}
39+
]
40+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* 图模式匹配
3+
*/
4+
import { GADDI } from '@antv/algorithm';
5+
import { Graph } from '@antv/g6';
6+
7+
const colors = ['#5F95FF', '#61DDAA', '#65789B'];
8+
9+
const defaultPalette = {
10+
type: 'group',
11+
field: 'cluster',
12+
color: colors,
13+
};
14+
15+
const button = document.createElement('button');
16+
button.innerHTML = `Click Here to Match 点此开始匹配`;
17+
document.getElementById('container').appendChild(button);
18+
19+
const pattern = {
20+
nodes: [
21+
{
22+
id: 'pn0',
23+
cluster: 'nodeType-0',
24+
},
25+
{
26+
id: 'pn1',
27+
cluster: 'nodeType-1',
28+
},
29+
{
30+
id: 'pn2',
31+
cluster: 'nodeType-2',
32+
},
33+
],
34+
edges: [
35+
{ source: 'pn1', target: 'pn0', cluster: 'edgeType-1' },
36+
{ source: 'pn1', target: 'pn2', cluster: 'edgeType-0' },
37+
{ source: 'pn2', target: 'pn0', cluster: 'edgeType-2' },
38+
],
39+
};
40+
41+
fetch('https://assets.antv.antgroup.com/g6/gaddi.json')
42+
.then((res) => res.json())
43+
.then((data) => {
44+
const graph = new Graph({
45+
container: 'container',
46+
data,
47+
autoFit: 'view',
48+
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element'],
49+
node: {
50+
style: {
51+
labelPlacement: 'center',
52+
labelText: (d) => d.label,
53+
stroke: '#5F95FF',
54+
lineWidth: 1,
55+
},
56+
palette: defaultPalette,
57+
},
58+
edge: {
59+
style: {
60+
endArrow: true,
61+
},
62+
palette: defaultPalette,
63+
},
64+
plugins: [
65+
{
66+
type: 'legend',
67+
nodeField: 'cluster',
68+
edgeField: 'cluster',
69+
position: 'top',
70+
},
71+
{
72+
key: 'hull-0',
73+
type: 'hull',
74+
members: [],
75+
},
76+
{
77+
key: 'hull-1',
78+
type: 'hull',
79+
members: [],
80+
},
81+
],
82+
});
83+
graph.render();
84+
85+
button.addEventListener('click', (e) => {
86+
const matches = GADDI(data, pattern, true, undefined, undefined, 'cluster', 'cluster');
87+
88+
matches.forEach((match, i) => {
89+
graph.updatePlugin({
90+
key: `hull-${i}`,
91+
members: match.nodes.map((node) => node.id),
92+
});
93+
});
94+
graph.render();
95+
button.innerHTML = `The results are marked with hulls 结果已用轮廓标记`;
96+
button.disabled = true;
97+
});
98+
});
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* 最短路径
3+
*/
4+
import { findShortestPath } from '@antv/algorithm';
5+
import { CanvasEvent, Graph, NodeEvent, idOf } from '@antv/g6';
6+
7+
const arrayToObject = (array, value) => {
8+
return array.reduce((obj, key) => {
9+
obj[key] = value;
10+
return obj;
11+
}, {});
12+
};
13+
14+
const formatData = (data) => {
15+
const newData = data;
16+
const { nodes } = newData;
17+
const newNodes = nodes.map((node) => ({
18+
...node,
19+
style: {
20+
x: node.x,
21+
y: node.y,
22+
},
23+
}));
24+
newData.nodes = newNodes;
25+
return newData;
26+
};
27+
28+
const tipDiv = document.createElement('div');
29+
tipDiv.innerHTML = `Press 'shift' and click two nodes to select begin and end nodes. 按住 'shift' 并点选两个节点作为起点和终点。`;
30+
document.getElementById('container').appendChild(tipDiv);
31+
32+
const button = document.createElement('button');
33+
button.innerHTML = `查看最短路径`;
34+
document.getElementById('container').appendChild(button);
35+
36+
fetch('https://gw.alipayobjects.com/os/bmw-prod/b0ca4b15-bd0c-43ec-ae41-c810374a1d55.json')
37+
.then((res) => res.json())
38+
.then((mockData) => {
39+
const data = formatData(mockData);
40+
const graph = new Graph({
41+
container: 'container',
42+
data: formatData(mockData),
43+
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'click-select'],
44+
autoFit: 'view',
45+
});
46+
47+
graph.render();
48+
49+
const clearStates = () => {
50+
graph.setElementState(
51+
Object.fromEntries([...graph.getNodeData(), ...graph.getEdgeData()].map((element) => [idOf(element), []])),
52+
);
53+
};
54+
55+
graph.on(`canvas:${CanvasEvent.CLICK}`, (e) => {
56+
clearStates();
57+
});
58+
59+
// store the selected nodes according to the clicked order
60+
let selectedNodeIds = [];
61+
graph.on(`node:${NodeEvent.CLICK}`, (event) => {
62+
const {
63+
target: { id },
64+
} = event;
65+
const index = selectedNodeIds.indexOf(id);
66+
67+
if (graph.getElementState(id).includes('selected')) {
68+
graph.setElementState(id, []);
69+
selectedNodeIds.splice(index, 1);
70+
} else if (!graph.getElementState(id).includes('selected')) {
71+
graph.setElementState(id, 'selected');
72+
selectedNodeIds.push(id);
73+
}
74+
});
75+
76+
graph.on(`canvas:${CanvasEvent.CLICK}`, (e) => {
77+
selectedNodeIds = [];
78+
});
79+
80+
button.addEventListener('click', (e) => {
81+
if (selectedNodeIds.length !== 2) {
82+
alert('Please select TWO nodes!\n\r请选择有且两个节点!');
83+
return;
84+
}
85+
clearStates();
86+
const { path } = findShortestPath(data, selectedNodeIds[0], selectedNodeIds[1], true);
87+
selectedNodeIds = [];
88+
89+
if (path?.length) {
90+
const pathNodeMap = {};
91+
path.forEach((id) => {
92+
pathNodeMap[id] = true;
93+
});
94+
graph.frontElement(path);
95+
graph.setElementState(arrayToObject(path, 'highlight'));
96+
97+
let highlightEdges = [];
98+
let inactiveEdges = [];
99+
let inactiveNodes = [];
100+
101+
graph.getEdgeData().forEach((edge) => {
102+
const { source, target } = edge;
103+
const sourceInPathIdx = path.indexOf(source);
104+
const targetInPathIdx = path.indexOf(target);
105+
if (sourceInPathIdx === -1 || targetInPathIdx === -1) return;
106+
if (Math.abs(sourceInPathIdx - targetInPathIdx) === 1) {
107+
highlightEdges.push(edge.id);
108+
} else {
109+
inactiveEdges.push(edge.id);
110+
}
111+
});
112+
113+
graph.getNodeData().forEach((node) => {
114+
if (!pathNodeMap[node.id]) {
115+
inactiveNodes.push(node.id);
116+
}
117+
});
118+
119+
graph.setElementState(arrayToObject(highlightEdges, 'highlight'));
120+
graph.setElementState(arrayToObject(inactiveEdges, 'inactive'));
121+
graph.setElementState(arrayToObject(inactiveNodes, 'inactive'));
122+
}
123+
});
124+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: Algorithm Example
3+
order: 5
4+
---

0 commit comments

Comments
 (0)