Skip to content

Commit 7570eb4

Browse files
aksOpsclaude
andcommitted
fix: treemap drill-down and proportional sizing
- Directory nodes no longer set value — ECharts sums from leaf children, giving correct proportional rectangle sizes based on actual node counts - leafDepth: 1 with nodeClick: 'zoomToNode' enables click-to-drill-down into any directory. Breadcrumb navigation at top to go back. - roam disabled (was intercepting clicks) - Larger border widths and labels for better visual hierarchy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a655a94 commit 7570eb4

1 file changed

Lines changed: 73 additions & 55 deletions

File tree

src/main/frontend/src/pages/CodebaseMap.tsx

Lines changed: 73 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const LANG_COLORS: Record<string, string> = {
1212
yaml: '#cb171e', json: '#555', ruby: '#701516', scala: '#c22d40',
1313
cpp: '#f34b7d', shell: '#89e051', markdown: '#083fa1', html: '#e34c26',
1414
css: '#563d7c', sql: '#e38c00', proto: '#60a0b0', dockerfile: '#384d54',
15+
other: '#888',
1516
};
1617

1718
const EXT_TO_LANG: Record<string, string> = {
@@ -25,7 +26,7 @@ const EXT_TO_LANG: Record<string, string> = {
2526

2627
interface EChartsTreeNode {
2728
name: string;
28-
value: number;
29+
value?: number;
2930
children?: EChartsTreeNode[];
3031
itemStyle?: { color: string };
3132
}
@@ -35,40 +36,47 @@ function inferLang(name: string): string {
3536
return EXT_TO_LANG[ext] ?? 'other';
3637
}
3738

38-
function fileTreeToECharts(nodes: FileTreeNode[]): EChartsTreeNode[] {
39-
return nodes
40-
.filter(n => n.nodeCount > 0 || (n.children && n.children.length > 0))
41-
.map(n => {
42-
if (n.type === 'directory' && n.children && n.children.length > 0) {
43-
const children = fileTreeToECharts(n.children);
44-
// Dominant language from children names
45-
const langCounts: Record<string, number> = {};
46-
function countLangs(items: FileTreeNode[]) {
47-
for (const item of items) {
48-
if (item.type === 'file') {
49-
const lang = inferLang(item.name);
50-
langCounts[lang] = (langCounts[lang] ?? 0) + (item.nodeCount || 1);
51-
}
52-
if (item.children) countLangs(item.children);
53-
}
54-
}
55-
countLangs(n.children);
56-
const dominant = Object.entries(langCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'other';
57-
58-
return {
59-
name: n.name,
60-
value: n.nodeCount,
61-
children: children.length > 0 ? children : undefined,
62-
itemStyle: { color: LANG_COLORS[dominant] ?? '#666' },
63-
};
39+
function dominantLang(nodes: FileTreeNode[]): string {
40+
const counts: Record<string, number> = {};
41+
function walk(items: FileTreeNode[]) {
42+
for (const item of items) {
43+
if (item.type === 'file') {
44+
const lang = inferLang(item.name);
45+
counts[lang] = (counts[lang] ?? 0) + (item.nodeCount || 1);
6446
}
47+
if (item.children) walk(item.children);
48+
}
49+
}
50+
walk(nodes);
51+
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'other';
52+
}
53+
54+
function fileTreeToECharts(nodes: FileTreeNode[]): EChartsTreeNode[] {
55+
const result: EChartsTreeNode[] = [];
56+
for (const n of nodes) {
57+
if (n.nodeCount <= 0 && (!n.children || n.children.length === 0)) continue;
58+
59+
if (n.type === 'directory' && n.children && n.children.length > 0) {
60+
const children = fileTreeToECharts(n.children);
61+
if (children.length === 0) continue;
62+
const lang = dominantLang(n.children);
63+
// Directory nodes: NO value — ECharts sums from children for correct proportions
64+
result.push({
65+
name: n.name,
66+
children,
67+
itemStyle: { color: LANG_COLORS[lang] ?? '#666' },
68+
});
69+
} else {
70+
// Leaf file node: value = nodeCount (determines rectangle size)
6571
const lang = inferLang(n.name);
66-
return {
72+
result.push({
6773
name: n.name,
6874
value: Math.max(n.nodeCount, 1),
6975
itemStyle: { color: LANG_COLORS[lang] ?? '#666' },
70-
};
71-
});
76+
});
77+
}
78+
}
79+
return result;
7280
}
7381

7482
function collectLanguages(nodes: FileTreeNode[]): string[] {
@@ -97,68 +105,78 @@ export default function CodebaseMap() {
97105
const tree = treeData?.tree ?? [];
98106
const totalFiles = treeData?.total_files ?? 0;
99107
const uniqueLangs = useMemo(() => collectLanguages(tree), [tree]);
100-
101-
const treemapData = useMemo(() => {
102-
// TODO: apply langFilter if needed
103-
return fileTreeToECharts(tree);
104-
}, [tree, langFilter]);
108+
const treemapData = useMemo(() => fileTreeToECharts(tree), [tree]);
105109

106110
const chartOption = useMemo(() => ({
107111
tooltip: {
108112
formatter: (info: { name: string; value: number; treePathInfo?: Array<{ name: string }> }) => {
109113
const path = info.treePathInfo?.map(p => p.name).filter(Boolean).join('/') ?? info.name;
110-
return `<b>${path}</b><br/>Nodes: ${info.value?.toLocaleString()}`;
114+
return `<b>${path}</b><br/>Nodes: ${(info.value ?? 0).toLocaleString()}`;
111115
},
112116
},
113117
series: [{
114118
type: 'treemap',
115119
data: treemapData,
116-
roam: 'move',
117-
leafDepth: 2,
118-
drillDownIcon: '▶',
120+
leafDepth: 1,
121+
drillDownIcon: '▶ ',
122+
roam: false,
123+
nodeClick: 'zoomToNode',
119124
breadcrumb: {
120125
show: true,
121126
top: 4,
122127
left: 4,
123128
itemStyle: {
124-
color: isDark ? '#2a2a2e' : '#f5f5f5',
125-
borderColor: isDark ? '#3f3f46' : '#d9d9d9',
129+
color: isDark ? '#1a1a1a' : '#f5f5f5',
130+
borderColor: isDark ? '#303030' : '#d9d9d9',
131+
},
132+
textStyle: {
133+
color: isDark ? '#e0e0e0' : '#333',
134+
fontSize: 13,
126135
},
127-
textStyle: { color: isDark ? '#e0e0e0' : '#333' },
128136
},
129137
levels: [
130138
{
131139
itemStyle: {
132-
borderColor: isDark ? '#444' : '#ccc',
133-
borderWidth: 2,
134-
gapWidth: 2,
140+
borderColor: isDark ? '#303030' : '#bbb',
141+
borderWidth: 3,
142+
gapWidth: 3,
135143
},
136144
upperLabel: {
137145
show: true,
138-
height: 28,
146+
height: 30,
139147
color: isDark ? '#e0e0e0' : '#333',
140-
fontSize: 13,
148+
fontSize: 14,
141149
fontWeight: 'bold' as const,
142150
},
143151
},
144152
{
145153
itemStyle: {
146-
borderColor: isDark ? '#555' : '#ddd',
147-
borderWidth: 1,
148-
gapWidth: 1,
154+
borderColor: isDark ? '#404040' : '#ccc',
155+
borderWidth: 2,
156+
gapWidth: 2,
157+
},
158+
upperLabel: {
159+
show: true,
160+
height: 24,
161+
fontSize: 12,
162+
color: isDark ? '#ccc' : '#555',
149163
},
150-
upperLabel: { show: true, height: 22, fontSize: 12 },
151164
},
152165
{
153166
itemStyle: {
154-
borderColor: isDark ? '#666' : '#eee',
155-
borderWidth: 0.5,
156-
gapWidth: 0.5,
167+
borderColor: isDark ? '#4a4a4a' : '#ddd',
168+
borderWidth: 1,
169+
gapWidth: 1,
157170
},
158171
label: { show: true, fontSize: 11 },
159172
},
160173
],
161-
label: { show: true, formatter: '{b}', fontSize: 12 },
174+
label: {
175+
show: true,
176+
formatter: '{b}',
177+
fontSize: 12,
178+
color: isDark ? '#ddd' : '#333',
179+
},
162180
}],
163181
}), [treemapData, isDark]);
164182

0 commit comments

Comments
 (0)