Skip to content

Commit a75d063

Browse files
authored
Merge pull request #343 from reaviz/anton/aggregate-edges
Aggregate edges Unit test & quick bug fix
2 parents ced7838 + f4ade80 commit a75d063

File tree

2 files changed

+108
-2
lines changed

2 files changed

+108
-2
lines changed

src/utils/aggregateEdges.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, test, expect, beforeEach } from 'vitest';
2+
import Graph from 'graphology';
3+
import { aggregateEdges } from './aggregateEdges';
4+
import { buildGraph } from './graph';
5+
import { GraphNode, GraphEdge } from '../types';
6+
7+
describe('aggregateEdges', () => {
8+
let graph: Graph;
9+
10+
beforeEach(() => {
11+
graph = new Graph({ multi: true });
12+
});
13+
14+
test('should return original edges when no aggregation needed', () => {
15+
const nodes: GraphNode[] = [
16+
{ id: 'A', label: 'Node A' },
17+
{ id: 'B', label: 'Node B' },
18+
{ id: 'C', label: 'Node C' }
19+
];
20+
21+
const edges: GraphEdge[] = [
22+
{
23+
id: 'edge1',
24+
source: 'A',
25+
target: 'B',
26+
size: 2,
27+
label: 'Connection AB'
28+
},
29+
{ id: 'edge2', source: 'B', target: 'C', size: 3, label: 'Link BC' },
30+
{ id: 'edge3', source: 'A', target: 'C', size: 1, label: 'Path AC' }
31+
];
32+
33+
buildGraph(graph, nodes, edges);
34+
const result = aggregateEdges(graph);
35+
36+
expect(result).toHaveLength(3);
37+
// Single edges should preserve their original labels
38+
expect(result[0].label).toBe('Connection AB');
39+
expect(result[1].label).toBe('Link BC');
40+
expect(result[2].label).toBe('Path AC');
41+
// Single edges should not be marked as aggregated
42+
expect(result[0].data.isAggregated).toBe(false);
43+
expect(result[1].data.isAggregated).toBe(false);
44+
expect(result[2].data.isAggregated).toBe(false);
45+
});
46+
47+
test('should aggregate multiple edges between same nodes', () => {
48+
const nodes: GraphNode[] = [
49+
{ id: 'A', label: 'Node A' },
50+
{ id: 'B', label: 'Node B' }
51+
];
52+
53+
const edges: GraphEdge[] = [
54+
{ id: 'edge1', source: 'A', target: 'B', size: 2 },
55+
{ id: 'edge2', source: 'A', target: 'B', size: 2 },
56+
{ id: 'edge3', source: 'A', target: 'B', size: 2 }
57+
];
58+
59+
buildGraph(graph, nodes, edges);
60+
const result = aggregateEdges(graph);
61+
62+
expect(result).toHaveLength(1);
63+
expect(result[0].source).toBe('A');
64+
expect(result[0].target).toBe('B');
65+
expect(result[0].label).toBe('3 edges');
66+
expect(result[0].size).toBe(5); // 2 + (3 * 2 * 0.5) = 5
67+
expect(result[0].data.isAggregated).toBe(true);
68+
expect(result[0].data.count).toBe(3);
69+
});
70+
71+
test('should handle mixed scenario with some aggregation', () => {
72+
const nodes: GraphNode[] = [
73+
{ id: 'A', label: 'Node A' },
74+
{ id: 'B', label: 'Node B' },
75+
{ id: 'C', label: 'Node C' }
76+
];
77+
78+
const edges: GraphEdge[] = [
79+
// Two edges A->B (will be aggregated)
80+
{ id: 'edge1', source: 'A', target: 'B', size: 1 },
81+
{ id: 'edge2', source: 'A', target: 'B', size: 1 },
82+
// One edge B->C (no aggregation)
83+
{ id: 'edge3', source: 'B', target: 'C', size: 3 }
84+
];
85+
86+
buildGraph(graph, nodes, edges);
87+
const result = aggregateEdges(graph);
88+
89+
expect(result).toHaveLength(2);
90+
91+
const abEdge = result.find(e => e.source === 'A' && e.target === 'B');
92+
const bcEdge = result.find(e => e.source === 'B' && e.target === 'C');
93+
94+
expect(abEdge.label).toBe('2 edges');
95+
expect(abEdge.size).toBe(2); // 1 + (2 * 1 * 0.5) = 2
96+
expect(abEdge.data.isAggregated).toBe(true);
97+
98+
expect(bcEdge.label).toBeUndefined(); // Single edge should not have aggregation label
99+
expect(bcEdge.size).toBe(4.5); // 3 + (1 * 3 * 0.5) = 4.5
100+
expect(bcEdge.data.isAggregated).toBe(false); // Single edge is not aggregated
101+
});
102+
});

src/utils/aggregateEdges.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,24 @@ export const aggregateEdges = (
7575
const baseSize = firstEdge.size || 1; // Default to 1 if no size is specified
7676
const aggregatedSize = baseSize + group.length * baseSize * 0.5;
7777

78+
// Only show aggregation label when actually aggregating multiple edges
79+
const aggregated = group.length > 1;
80+
const label = aggregated ? `${group.length} edges` : firstEdge.label;
81+
7882
// Create an aggregated edge that represents the group
7983
const aggregatedEdge: InternalGraphEdge = {
8084
...firstEdge,
8185
source,
8286
target,
83-
label: `${group.length} edges`,
87+
label,
8488
labelVisible: shouldShowEdgeLabels,
8589
size: aggregatedSize,
8690
// Store the original edges in the data property
8791
data: {
8892
...(firstEdge.data || {}),
8993
originalEdges: group,
9094
count: group.length,
91-
isAggregated: true,
95+
isAggregated: aggregated,
9296
originalSize: baseSize
9397
}
9498
};

0 commit comments

Comments
 (0)