How We Solved Large-Scale Call Graph Visualization with AntV G6
Facing performance and layout challenges when visualizing thousands of function call nodes, we iterated through Echarts, Ant Design Charts, and finally adopted AntV G6, achieving smooth interaction, automatic layout, and scalable coverage mapping for our link coverage tool.
Introduction
In daily development testing, code coverage is a core metric for test completeness. The existing line‑level coverage tools are insufficient for our needs; we require a link‑coverage view that shows the call chain of interfaces and the coverage status of involved files.
Requirements from the product side can be summarized as:
Clearly display function call relationships.
Show coverage details on click.
Provide as many features as possible.
Other flexible requirements.
Understanding the Real Requirements
We initially assumed the task was simply drawing a flowchart, but the real needs are threefold:
Present the entire system's function call network, not isolated nodes.
Visually indicate function coverage to quickly locate target functions.
Enable click and hover interactions to display different coverage information.
These translate into three technical challenges:
Handling large‑scale data (performance).
Automatically arranging nodes (layout).
Providing smooth user interaction (UX).
First Attempt: Echarts – A Failure
Tree Diagram
Echarts tree layout only supports hierarchical data, which cannot represent many‑to‑many relationships in our call graph, so this approach was abandoned.
Graph
Using the Graph component required explicit x, y coordinates, which we wanted to avoid. We tried a force‑directed layout.
series: [{
type: 'graph',
layout: 'none'
}]Force layout worked for a few dozen nodes but collapsed into an unreadable mass when the node count reached 500.
Force‑Directed Layout
series: [{
type: 'graph',
layout: 'force', // force‑directed layout
force: { repulsion: 100 } // node repulsion
}]The result was acceptable for small datasets but unsuitable for large ones.
Second Attempt: Ant Design Charts – Still Stuck
We switched to Ant Design Charts' FlowGraph component, which integrates well with our AntD UI library.
<FlowGraph
data={data}
nodeConfig={{...}}
edgeConfig={{...}}
/>While the visual appearance improved, two major issues emerged:
With large data, updating a node caused the entire canvas to re‑render, leading to noticeable lag.
The product demanded a minimap and toolbar, which were difficult to embed seamlessly.
Final Solution: AntV G6
After two unsatisfactory trials, we evaluated AntV G6, a dedicated graph visualization engine.
Problem 1 – Performance with 1000+ Nodes
We tested G6 with over 1000 nodes. Using single‑node updates kept the UI responsive, with smooth dragging and zooming.
const graph = new G6.Graph({
container: 'container',
layout: {
type: 'antv-dagre',
rankdir: 'LR',
ranksep: 100, // layer spacing
nodesep: 10 // node spacing
},
// other configurations...
});Problem 2 – Automatic Node Arrangement
Use antv-dagre layout to arrange the call chain from left to right.
Adjust ranksep and nodesep to control spacing.
Problem 3 – Smooth Interaction
Key interactive features implemented:
Node Highlight : Clicking a function highlights the node and shows line‑coverage details in a popup.
// Update node state
graph.updateNodeData([{ id, states: ['selected'] }]);
// Focus node in viewport
graph.focusElement(id);
// Redraw chart
graph.draw();
showDetailPanel(id);Hovering over a node highlights its call path and displays detailed coverage.
Color Coding : Nodes are colored based on coverage status (yellow for selected, green for 100% covered, blue for partially covered, red for uncovered).
const COVERAGE_COLORS = {
selected: '#fadb14', // yellow
full: '#52c41a', // green
partial: '#1677ff', // blue
none: '#f5222d' // red
};
const getNodeColor = (status, isSelected, isActive) => {
let baseColor = COVERAGE_COLORS[status];
if (isSelected) baseColor = COVERAGE_COLORS.selected;
const alpha = isActive ? '' : '80';
return baseColor + alpha;
};Expand/Collapse : Large sub‑trees can be collapsed to keep the view compact.
const { edges, nodes } = graph.getData();
const nodeList = getDescendants(nodeId);
nodes.forEach(node => {
if (nodeList.includes(node.id)) {
onCollapseNode({ nodeId: node.id, type: 'node' });
}
});
edges.forEach(edge => {
if (nodeList.includes(edge.target)) {
onCollapseNode({ nodeId: edge.id, type: 'edge' });
}
});
const onCollapseNode = ({ type = '', nodeId = '' }) => {
// toggle visibility of node/edge and its descendants
};Additional features such as canvas dragging, zooming, minimap, toolbar, and tooltip were added to enhance the user experience.
const graph = new G6.Graph({
behaviors: [
'drag-canvas',
'zoom-canvas',
{ type: 'hover-activate', degree: 1 }
],
plugins: [
{ type: 'minimap' },
{ type: 'toolbar' },
{ type: 'tooltip' }
]
});Summary
Lessons Learned
Conduct thorough multi‑dimensional research before selecting a tool to avoid blind reliance on familiar libraries.
Prioritize performance over feature richness; a smooth UI is essential for complex graph scenarios.
Future Plans
Integrate link‑coverage analysis with daily code changes so that each commit can be correlated with affected call paths, enabling precise risk assessment of code modifications.
Author: Liu Xiaoyu, Test Development Engineer at ZuanZuan
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
