11import React , { useCallback , useMemo , useRef , useState } from 'react' ;
22import EChartsReactCore from 'echarts-for-react/esm/core' ;
33import * as echarts from 'echarts/core' ;
4- import { GraphChart , type GraphSeriesOption } from 'echarts/charts' ;
4+ import {
5+ GraphChart ,
6+ TreemapChart ,
7+ type GraphSeriesOption ,
8+ type TreemapSeriesOption ,
9+ } from 'echarts/charts' ;
510import {
611 TooltipComponent ,
712 LegendComponent ,
@@ -14,17 +19,39 @@ import { Alert, Empty, Input, Space, Switch, Typography } from 'antd';
1419import { SDK } from '@rsdoctor/types' ;
1520import { formatSize } from 'src/utils' ;
1621import { DetailPanel , PackageNodeInfo } from './DetailPanel' ;
22+ import { buildPackageSizeTreemapData } from './packageSizeTreemap' ;
1723
18- echarts . use ( [ GraphChart , TooltipComponent , LegendComponent , CanvasRenderer ] ) ;
24+ echarts . use ( [
25+ GraphChart ,
26+ TreemapChart ,
27+ TooltipComponent ,
28+ LegendComponent ,
29+ CanvasRenderer ,
30+ ] ) ;
1931
2032type GraphOption = ComposeOption <
2133 GraphSeriesOption | TooltipComponentOption | LegendComponentOption
2234> ;
2335
36+ type TreemapOption = ComposeOption <
37+ TreemapSeriesOption | TooltipComponentOption | LegendComponentOption
38+ > ;
39+
2440// Node colors
2541const COLOR_NORMAL = '#4dabf7' ; // blue – regular package
2642const COLOR_DUPLICATE = '#ff6b6b' ; // red – duplicate package
2743const COLOR_SOURCE = '#51cf66' ; // green – user source code (virtual root)
44+ const TREEMAP_COLORS = [
45+ '#4dabf7' ,
46+ '#51cf66' ,
47+ '#ffd43b' ,
48+ '#ff922b' ,
49+ '#845ef7' ,
50+ '#20c997' ,
51+ '#f06595' ,
52+ '#15aabf' ,
53+ '#adb5bd' ,
54+ ] ;
2855
2956// Min/max node symbol sizes
3057const MIN_SIZE = 16 ;
@@ -90,6 +117,14 @@ export const GraphView: React.FC<GraphViewProps> = ({
90117 const sizes = packages . map ( ( p ) => p . size . parsedSize ) ;
91118 const minSize = Math . min ( ...sizes ) ;
92119 const maxSize = Math . max ( ...sizes ) ;
120+ const treemapData = useMemo (
121+ ( ) => buildPackageSizeTreemapData ( visiblePackages ) ,
122+ [ visiblePackages ] ,
123+ ) ;
124+ const visiblePackagesSize = useMemo (
125+ ( ) => treemapData . reduce ( ( total , item ) => total + item . value , 0 ) ,
126+ [ treemapData ] ,
127+ ) ;
93128
94129 const option = useMemo < GraphOption > ( ( ) => {
95130 const nodes : GraphSeriesOption [ 'data' ] = visiblePackages . map ( ( pkg ) => {
@@ -205,6 +240,78 @@ export const GraphView: React.FC<GraphViewProps> = ({
205240 packages ,
206241 ] ) ;
207242
243+ const treemapOption = useMemo < TreemapOption > (
244+ ( ) => ( {
245+ color : TREEMAP_COLORS ,
246+ tooltip : {
247+ trigger : 'item' ,
248+ confine : true ,
249+ formatter : ( params : any ) => {
250+ const data = params . data ;
251+ if ( ! data ) return '' ;
252+ return [
253+ `<b>${ echarts . format . encodeHTML ( data . packageName ) } </b>` ,
254+ `Version: ${ echarts . format . encodeHTML ( data . version ) } ` ,
255+ `Parsed: ${ formatSize ( data . value ) } ` ,
256+ `Share: ${ data . percent . toFixed ( 2 ) } %` ,
257+ `Gzip: ${ formatSize ( data . gzipSize ) } ` ,
258+ `Source: ${ formatSize ( data . sourceSize ) } ` ,
259+ ] . join ( '<br/>' ) ;
260+ } ,
261+ } ,
262+ series : [
263+ {
264+ type : 'treemap' ,
265+ data : treemapData ,
266+ roam : true ,
267+ nodeClick : false ,
268+ breadcrumb : { show : false } ,
269+ left : 0 ,
270+ right : 0 ,
271+ top : 0 ,
272+ bottom : 0 ,
273+ width : '100%' ,
274+ height : '100%' ,
275+ itemStyle : {
276+ borderColor : '#fff' ,
277+ borderWidth : 2 ,
278+ gapWidth : 2 ,
279+ } ,
280+ label : {
281+ show : true ,
282+ color : '#fff' ,
283+ fontSize : 11 ,
284+ overflow : 'truncate' ,
285+ formatter : ( params : any ) => {
286+ const data = params . data ;
287+ if ( ! data ) return params . name ;
288+ return `${ data . packageName } \n${ data . percent . toFixed ( 1 ) } %` ;
289+ } ,
290+ } ,
291+ upperLabel : {
292+ show : false ,
293+ } ,
294+ emphasis : {
295+ itemStyle : {
296+ borderColor : '#1c7ed6' ,
297+ borderWidth : 3 ,
298+ } ,
299+ } ,
300+ levels : [
301+ {
302+ itemStyle : {
303+ borderColor : '#fff' ,
304+ borderWidth : 2 ,
305+ gapWidth : 2 ,
306+ } ,
307+ } ,
308+ ] ,
309+ } ,
310+ ] ,
311+ } ) ,
312+ [ treemapData ] ,
313+ ) ;
314+
208315 const onChartClick = useCallback (
209316 ( params : any ) => {
210317 if ( params . dataType !== 'node' ) return ;
@@ -216,6 +323,23 @@ export const GraphView: React.FC<GraphViewProps> = ({
216323 [ packages , dependencies ] ,
217324 ) ;
218325
326+ const openPackageDetail = useCallback (
327+ ( id : number ) => {
328+ const pkg = packages . find ( ( item ) => item . id === id ) ;
329+ if ( ! pkg ) return ;
330+ setSelectedPkg ( { pkg, dependencies, allPackages : packages } ) ;
331+ setDrawerOpen ( true ) ;
332+ } ,
333+ [ packages , dependencies ] ,
334+ ) ;
335+
336+ const onTreemapClick = useCallback (
337+ ( params : any ) => {
338+ openPackageDetail ( Number ( params . data ?. id ) ) ;
339+ } ,
340+ [ openPackageDetail ] ,
341+ ) ;
342+
219343 if ( packages . length === 0 ) {
220344 return < Empty description = "No package data available" /> ;
221345 }
@@ -278,6 +402,35 @@ export const GraphView: React.FC<GraphViewProps> = ({
278402 notMerge
279403 />
280404
405+ < div style = { { marginTop : 20 } } >
406+ < Space
407+ align = "baseline"
408+ style = { {
409+ width : '100%' ,
410+ justifyContent : 'space-between' ,
411+ marginBottom : 8 ,
412+ } }
413+ >
414+ < Typography . Title level = { 5 } style = { { margin : 0 } } >
415+ Package Size Treemap
416+ </ Typography . Title >
417+ < Typography . Text type = "secondary" style = { { fontSize : 12 } } >
418+ Parsed size total: { formatSize ( visiblePackagesSize ) }
419+ </ Typography . Text >
420+ </ Space >
421+ { treemapData . length > 0 ? (
422+ < EChartsReactCore
423+ echarts = { echarts }
424+ option = { treemapOption }
425+ style = { { height : 420 , width : '100%' } }
426+ onEvents = { { click : onTreemapClick } }
427+ notMerge
428+ />
429+ ) : (
430+ < Empty description = "No package size data available" />
431+ ) }
432+ </ div >
433+
281434 < DetailPanel
282435 info = { selectedPkg }
283436 open = { drawerOpen }
0 commit comments