@bundle-stats/ui 4.13.0-beta.16 → 4.13.0-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets/icons.svg +70 -67
- package/lib/assets/icons.svg.js +14 -12
- package/lib/assets/icons.svg.js.map +1 -1
- package/lib/components/bundle-assets/bundle-assets.js +39 -7
- package/lib/components/bundle-assets/bundle-assets.js.map +1 -1
- package/lib/components/bundle-assets-totals/bundle-assets-totals.js +4 -4
- package/lib/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
- package/lib/components/bundle-modules/bundle-modules.js +28 -7
- package/lib/components/bundle-modules/bundle-modules.js.map +1 -1
- package/lib/components/bundle-packages/bundle-packages.js +39 -7
- package/lib/components/bundle-packages/bundle-packages.js.map +1 -1
- package/lib/components/metrics-display-selector/metrics-display-selector.js +41 -4
- package/lib/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
- package/lib/components/metrics-display-selector/metrics-display-selector.module.css +15 -7
- package/lib/components/metrics-treemap/index.js +4 -0
- package/lib/components/metrics-treemap/index.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.constants.js +3 -0
- package/lib/components/metrics-treemap/metrics-treemap.constants.js.map +1 -0
- package/lib/components/metrics-treemap/metrics-treemap.js +63 -44
- package/lib/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib/components/metrics-treemap/metrics-treemap.module.css +60 -28
- package/lib/components/metrics-treemap/metrics-treemap.utils.js +70 -0
- package/lib/components/metrics-treemap/metrics-treemap.utils.js.map +1 -0
- package/lib/hooks/metrics-display-type.js +56 -24
- package/lib/hooks/metrics-display-type.js.map +1 -1
- package/lib/hooks/search-params.js +1 -0
- package/lib/hooks/search-params.js.map +1 -1
- package/lib/ui/icon/icon.js +1 -0
- package/lib/ui/icon/icon.js.map +1 -1
- package/lib/ui/input-search/input-search.js +1 -1
- package/lib/ui/input-search/input-search.js.map +1 -1
- package/lib-esm/assets/icons.svg +70 -67
- package/lib-esm/assets/icons.svg.js +14 -12
- package/lib-esm/assets/icons.svg.js.map +1 -1
- package/lib-esm/components/bundle-assets/bundle-assets.js +40 -8
- package/lib-esm/components/bundle-assets/bundle-assets.js.map +1 -1
- package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js +5 -5
- package/lib-esm/components/bundle-assets-totals/bundle-assets-totals.js.map +1 -1
- package/lib-esm/components/bundle-modules/bundle-modules.js +29 -8
- package/lib-esm/components/bundle-modules/bundle-modules.js.map +1 -1
- package/lib-esm/components/bundle-packages/bundle-packages.js +40 -8
- package/lib-esm/components/bundle-packages/bundle-packages.js.map +1 -1
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.js +41 -4
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.js.map +1 -1
- package/lib-esm/components/metrics-display-selector/metrics-display-selector.module.css +15 -7
- package/lib-esm/components/metrics-treemap/index.js +1 -0
- package/lib-esm/components/metrics-treemap/index.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.constants.js +2 -0
- package/lib-esm/components/metrics-treemap/metrics-treemap.constants.js.map +1 -0
- package/lib-esm/components/metrics-treemap/metrics-treemap.js +64 -45
- package/lib-esm/components/metrics-treemap/metrics-treemap.js.map +1 -1
- package/lib-esm/components/metrics-treemap/metrics-treemap.module.css +60 -28
- package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js +65 -0
- package/lib-esm/components/metrics-treemap/metrics-treemap.utils.js.map +1 -0
- package/lib-esm/hooks/metrics-display-type.js +57 -24
- package/lib-esm/hooks/metrics-display-type.js.map +1 -1
- package/lib-esm/hooks/search-params.js +1 -0
- package/lib-esm/hooks/search-params.js.map +1 -1
- package/lib-esm/ui/icon/icon.js +1 -0
- package/lib-esm/ui/icon/icon.js.map +1 -1
- package/lib-esm/ui/input-search/input-search.js +1 -1
- package/lib-esm/ui/input-search/input-search.js.map +1 -1
- package/package.json +7 -7
- package/src/assets/icons/check.svg +4 -0
- package/src/assets/icons.svg +70 -67
- package/src/assets/icons.svg.jsx +12 -12
- package/src/components/bundle-assets/bundle-assets.jsx +76 -20
- package/src/components/bundle-assets-totals/bundle-assets-totals.jsx +5 -4
- package/src/components/bundle-modules/bundle-modules.tsx +76 -16
- package/src/components/bundle-packages/bundle-packages.jsx +71 -16
- package/src/components/metrics-display-selector/metrics-display-selector.module.css +15 -7
- package/src/components/metrics-display-selector/metrics-display-selector.stories.tsx +33 -0
- package/src/components/metrics-display-selector/metrics-display-selector.tsx +83 -25
- package/src/components/metrics-treemap/__tests__/metrics-treemap.utils.ts +143 -0
- package/src/components/metrics-treemap/index.ts +1 -0
- package/src/components/metrics-treemap/metrics-treemap.constants.ts +43 -0
- package/src/components/metrics-treemap/metrics-treemap.module.css +60 -28
- package/src/components/metrics-treemap/metrics-treemap.stories.tsx +44 -10
- package/src/components/metrics-treemap/metrics-treemap.tsx +258 -90
- package/src/components/metrics-treemap/metrics-treemap.utils.ts +107 -0
- package/src/hooks/metrics-display-type.ts +87 -29
- package/src/hooks/search-params.js +1 -0
- package/src/ui/icon/icon.tsx +1 -0
- package/src/ui/input-search/input-search.tsx +1 -0
- package/types/components/metrics-display-selector/metrics-display-selector.d.ts +3 -1
- package/types/components/metrics-treemap/index.d.ts +1 -0
- package/types/components/metrics-treemap/metrics-treemap.constants.d.ts +15 -0
- package/types/components/metrics-treemap/metrics-treemap.d.ts +7 -5
- package/types/components/metrics-treemap/metrics-treemap.utils.d.ts +4 -0
- package/types/hooks/metrics-display-type.d.ts +8 -2
- package/types/ui/icon/icon.d.ts +2 -0
|
@@ -1,24 +1,34 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
type ComponentProps,
|
|
3
|
+
type MouseEventHandler,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
type Ref,
|
|
6
|
+
type RefObject,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useCallback,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
} from 'react';
|
|
2
12
|
import { useDebounce, useHoverDirty, useMeasure, useMouseHovered } from 'react-use';
|
|
3
13
|
import cx from 'classnames';
|
|
4
|
-
import
|
|
5
|
-
import { hierarchy, treemap, treemapSquarify } from 'd3';
|
|
14
|
+
import { HierarchyRectangularNode, hierarchy, treemap, treemapSquarify } from 'd3';
|
|
6
15
|
import { Tooltip, TooltipArrow, TooltipAnchor, useTooltipState } from 'ariakit/tooltip';
|
|
16
|
+
import type { ReportMetricRow, MetricRunInfo, MetricRunInfoBaseline } from '@bundle-stats/utils';
|
|
7
17
|
|
|
8
18
|
import { Stack } from '../../layout/stack';
|
|
9
19
|
import { FileName } from '../../ui/file-name';
|
|
10
20
|
import { Delta } from '../delta';
|
|
11
21
|
import { RunInfo } from '../run-info';
|
|
22
|
+
import type { TreeLeaf, TreeNode, Tree } from './metrics-treemap.constants';
|
|
12
23
|
import css from './metrics-treemap.module.css';
|
|
13
24
|
|
|
14
25
|
const SQUARIFY_RATIO = 1.33;
|
|
15
|
-
const PADDING_OUTER =
|
|
16
|
-
const PADDING_INNER =
|
|
17
|
-
|
|
18
|
-
type TileSizeDisplay = 'minimal' | 'small' | 'default';
|
|
26
|
+
const PADDING_OUTER = 1;
|
|
27
|
+
const PADDING_INNER = 1;
|
|
28
|
+
const GROUPED_PADDING_TOP = 16;
|
|
19
29
|
|
|
20
30
|
/**
|
|
21
|
-
* Resolve the tile size using predefined values to avoid
|
|
31
|
+
* Resolve the tile's size using predefined values to avoid
|
|
22
32
|
* computing the size of the content for every tile
|
|
23
33
|
*/
|
|
24
34
|
const PADDING_TOP = 4;
|
|
@@ -29,6 +39,20 @@ const VERTICAL_SPACING = 4;
|
|
|
29
39
|
const LINE_HEIGHT = 16;
|
|
30
40
|
const LINE_HEIGHT_SMALL = 13.3;
|
|
31
41
|
|
|
42
|
+
function resolveTileGroupSizeDisplay(width: number, height: number): TileSizeDisplay {
|
|
43
|
+
if (height < GROUPED_PADDING_TOP || width < 24) {
|
|
44
|
+
return 'minimal';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (height < GROUPED_PADDING_TOP + 8) {
|
|
48
|
+
return 'small';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return 'default';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type TileSizeDisplay = 'minimal' | 'small' | 'default';
|
|
55
|
+
|
|
32
56
|
function resolveTileSizeDisplay(width: number, height: number): TileSizeDisplay {
|
|
33
57
|
if (
|
|
34
58
|
height > PADDING_TOP + PADDING_BOTTOM + VERTICAL_SPACING + LINE_HEIGHT * 2 &&
|
|
@@ -73,13 +97,26 @@ const TileTooltipContent = (props: TileTooltipContentProps) => {
|
|
|
73
97
|
};
|
|
74
98
|
|
|
75
99
|
interface TileContentProps {
|
|
76
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Node label
|
|
102
|
+
*/
|
|
103
|
+
label: string;
|
|
104
|
+
/**
|
|
105
|
+
* Tile id - metric label or basename for grouped tiles
|
|
106
|
+
*/
|
|
77
107
|
item: ReportMetricRow;
|
|
108
|
+
/**
|
|
109
|
+
* The estimated size of the tile
|
|
110
|
+
*/
|
|
111
|
+
sizeDisplay: 'minimal' | 'small' | 'default';
|
|
112
|
+
/**
|
|
113
|
+
* Metric run info
|
|
114
|
+
*/
|
|
78
115
|
runInfo: MetricRunInfo;
|
|
79
116
|
}
|
|
80
117
|
|
|
81
|
-
const TileContent = forwardRef((props: TileContentProps, ref:
|
|
82
|
-
const { sizeDisplay, item, runInfo } = props;
|
|
118
|
+
const TileContent = forwardRef((props: TileContentProps, ref: Ref<HTMLDivElement>) => {
|
|
119
|
+
const { label, sizeDisplay, item, runInfo } = props;
|
|
83
120
|
|
|
84
121
|
if (sizeDisplay === 'minimal') {
|
|
85
122
|
return <div className={css.tileContent} ref={ref} />;
|
|
@@ -87,12 +124,12 @@ const TileContent = forwardRef((props: TileContentProps, ref: React.Ref<HTMLDivE
|
|
|
87
124
|
|
|
88
125
|
return (
|
|
89
126
|
<div className={css.tileContent} ref={ref}>
|
|
90
|
-
<FileName as="p" className={css.
|
|
127
|
+
<FileName as="p" className={css.tileContentLabel} name={label || item.label} />
|
|
91
128
|
{sizeDisplay !== 'small' && (
|
|
92
|
-
<p className={css.
|
|
93
|
-
<span className={css.
|
|
129
|
+
<p className={css.tileContentValue}>
|
|
130
|
+
<span className={css.tileContentMetric}>{runInfo.displayValue}</span>
|
|
94
131
|
<Delta
|
|
95
|
-
className={css.
|
|
132
|
+
className={css.tileContentDelta}
|
|
96
133
|
displayValue={runInfo.displayDeltaPercentage}
|
|
97
134
|
deltaType={runInfo.deltaType}
|
|
98
135
|
/>
|
|
@@ -103,7 +140,7 @@ const TileContent = forwardRef((props: TileContentProps, ref: React.Ref<HTMLDivE
|
|
|
103
140
|
});
|
|
104
141
|
|
|
105
142
|
const TileContentWithTooltip = (props: TileContentProps & { parentRef: RefObject<Element> }) => {
|
|
106
|
-
const { sizeDisplay, item, runInfo, parentRef } = props;
|
|
143
|
+
const { label, sizeDisplay, item, runInfo, parentRef } = props;
|
|
107
144
|
|
|
108
145
|
const pointer = useMouseHovered(parentRef, { whenHovered: true });
|
|
109
146
|
|
|
@@ -132,7 +169,7 @@ const TileContentWithTooltip = (props: TileContentProps & { parentRef: RefObject
|
|
|
132
169
|
return (
|
|
133
170
|
<>
|
|
134
171
|
<TooltipAnchor state={tooltipState} className={css.tileContentTooltipAnchor}>
|
|
135
|
-
<TileContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
172
|
+
<TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
136
173
|
</TooltipAnchor>
|
|
137
174
|
<Tooltip state={tooltipState} className={css.tooltip}>
|
|
138
175
|
<TooltipArrow state={tooltipState} size={16} className={css.tileTooltipArrow} />
|
|
@@ -142,36 +179,29 @@ const TileContentWithTooltip = (props: TileContentProps & { parentRef: RefObject
|
|
|
142
179
|
);
|
|
143
180
|
};
|
|
144
181
|
|
|
145
|
-
interface TreeNode {
|
|
146
|
-
id: string;
|
|
147
|
-
value: number;
|
|
148
|
-
item: ReportMetricRow;
|
|
149
|
-
children?: Array<TreeNode>;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
interface RootTree {
|
|
153
|
-
id: string;
|
|
154
|
-
value: number;
|
|
155
|
-
children: Array<TreeNode>;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
182
|
interface TileProps {
|
|
159
183
|
left: number;
|
|
160
184
|
top: number;
|
|
161
185
|
width: number;
|
|
162
186
|
height: number;
|
|
163
|
-
data:
|
|
187
|
+
data: TreeLeaf;
|
|
164
188
|
onClick?: (id: string) => void;
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
const Tile = (props: TileProps) => {
|
|
168
192
|
const { left, top, width, height, data, onClick } = props;
|
|
169
193
|
|
|
170
|
-
const { item } = data;
|
|
194
|
+
const { item, label } = data;
|
|
171
195
|
const runInfo = item.runs?.[0] as MetricRunInfo;
|
|
172
196
|
|
|
173
197
|
const sizeDisplay = useMemo(() => resolveTileSizeDisplay(width, height), [width, height]);
|
|
174
|
-
const handleOnClick = useCallback(
|
|
198
|
+
const handleOnClick: MouseEventHandler<HTMLButtonElement> = useCallback(
|
|
199
|
+
(event) => {
|
|
200
|
+
event.stopPropagation();
|
|
201
|
+
onClick?.(item.key);
|
|
202
|
+
},
|
|
203
|
+
[item.key, onClick],
|
|
204
|
+
);
|
|
175
205
|
|
|
176
206
|
const contentRef = useRef<HTMLButtonElement>(null);
|
|
177
207
|
const hover = useHoverDirty(contentRef);
|
|
@@ -179,7 +209,8 @@ const Tile = (props: TileProps) => {
|
|
|
179
209
|
const className = cx(
|
|
180
210
|
css.tile,
|
|
181
211
|
css[`tile-${runInfo.deltaType}`],
|
|
182
|
-
sizeDisplay === 'small' && css.
|
|
212
|
+
sizeDisplay === 'small' && css.tileSizeSmall,
|
|
213
|
+
sizeDisplay === 'default' && css.tileSizeDefault,
|
|
183
214
|
left === 0 && css.tileFirstCol,
|
|
184
215
|
);
|
|
185
216
|
|
|
@@ -193,106 +224,243 @@ const Tile = (props: TileProps) => {
|
|
|
193
224
|
>
|
|
194
225
|
{hover ? (
|
|
195
226
|
<TileContentWithTooltip
|
|
227
|
+
label={label}
|
|
196
228
|
sizeDisplay={sizeDisplay}
|
|
197
229
|
item={item}
|
|
198
230
|
runInfo={runInfo}
|
|
199
231
|
parentRef={contentRef}
|
|
200
232
|
/>
|
|
201
233
|
) : (
|
|
202
|
-
<TileContent sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
234
|
+
<TileContent label={label} sizeDisplay={sizeDisplay} item={item} runInfo={runInfo} />
|
|
203
235
|
)}
|
|
204
236
|
</button>
|
|
205
237
|
);
|
|
238
|
+
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus */
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
interface TileGroupProps extends ComponentProps<'div'> {
|
|
242
|
+
/**
|
|
243
|
+
* Node title
|
|
244
|
+
*/
|
|
245
|
+
title?: string;
|
|
246
|
+
/**
|
|
247
|
+
* Node id
|
|
248
|
+
*/
|
|
249
|
+
id: string;
|
|
250
|
+
/**
|
|
251
|
+
* Node children
|
|
252
|
+
*/
|
|
253
|
+
childNodes: HierarchyRectangularNode<TreeNode>['children'];
|
|
254
|
+
/**
|
|
255
|
+
* On click tile handler
|
|
256
|
+
*/
|
|
257
|
+
onItemClick: TileProps['onClick'];
|
|
258
|
+
/**
|
|
259
|
+
* On click group title
|
|
260
|
+
*/
|
|
261
|
+
onGroupClick?: (groupId: string) => void;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Node rectangle dimensions
|
|
265
|
+
*/
|
|
266
|
+
width?: number;
|
|
267
|
+
height?: number;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Node rectangle relative coordinates (relative to parent)
|
|
271
|
+
*/
|
|
272
|
+
left: number;
|
|
273
|
+
top: number;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Node rectangle absolute coordinates (relative to canvas)
|
|
277
|
+
*/
|
|
278
|
+
absoluteLeft: number;
|
|
279
|
+
absoluteTop: number;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const TileGroup = (props: TileGroupProps) => {
|
|
283
|
+
const {
|
|
284
|
+
title = '',
|
|
285
|
+
id,
|
|
286
|
+
childNodes,
|
|
287
|
+
onItemClick,
|
|
288
|
+
onGroupClick,
|
|
289
|
+
left,
|
|
290
|
+
top,
|
|
291
|
+
width,
|
|
292
|
+
height,
|
|
293
|
+
absoluteLeft,
|
|
294
|
+
absoluteTop,
|
|
295
|
+
} = props;
|
|
296
|
+
|
|
297
|
+
const onClick: MouseEventHandler<HTMLDivElement> = useCallback(
|
|
298
|
+
(event) => {
|
|
299
|
+
onGroupClick?.(id);
|
|
300
|
+
event.stopPropagation();
|
|
301
|
+
},
|
|
302
|
+
[onGroupClick, id],
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const displaySize = useMemo(
|
|
306
|
+
() => resolveTileGroupSizeDisplay(width || 0, height || 0),
|
|
307
|
+
[width, height],
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (title && displaySize === 'minimal') {
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
onClick={onClick}
|
|
314
|
+
role="button"
|
|
315
|
+
aria-label="View children entries"
|
|
316
|
+
className={css.tileGroup}
|
|
317
|
+
style={{ left, top, width, height }}
|
|
318
|
+
/>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (title && displaySize === 'small') {
|
|
323
|
+
return (
|
|
324
|
+
<div
|
|
325
|
+
onClick={onClick}
|
|
326
|
+
role="button"
|
|
327
|
+
aria-label="View children entries"
|
|
328
|
+
style={{ left, top, width, height }}
|
|
329
|
+
className={css.tileGroup}
|
|
330
|
+
>
|
|
331
|
+
<div className={css.tileGroupTitle}>{title}</div>
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<div
|
|
338
|
+
onClick={onClick}
|
|
339
|
+
role="button"
|
|
340
|
+
aria-label="View children entries"
|
|
341
|
+
className={cx(css.tileGroup, css.tileGroupSizeDefault)}
|
|
342
|
+
style={{ left, top, width, height }}
|
|
343
|
+
>
|
|
344
|
+
{title && <div className={css.tileGroupTitle}>{title}</div>}
|
|
345
|
+
{childNodes?.map((childNode) => {
|
|
346
|
+
if ('children' in childNode) {
|
|
347
|
+
return (
|
|
348
|
+
<TileGroup
|
|
349
|
+
title={childNode.data.label}
|
|
350
|
+
id={childNode.data.id}
|
|
351
|
+
childNodes={childNode.children}
|
|
352
|
+
left={childNode.x0 - absoluteLeft}
|
|
353
|
+
top={childNode.y0 - absoluteTop}
|
|
354
|
+
absoluteLeft={childNode.x0}
|
|
355
|
+
absoluteTop={childNode.y0}
|
|
356
|
+
width={childNode.x1 - childNode.x0}
|
|
357
|
+
height={childNode.y1 - childNode.y0}
|
|
358
|
+
onItemClick={onItemClick}
|
|
359
|
+
onGroupClick={onGroupClick}
|
|
360
|
+
key={childNode.data.id}
|
|
361
|
+
/>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<Tile
|
|
367
|
+
data={childNode.data as TreeLeaf}
|
|
368
|
+
left={childNode.x0 - absoluteLeft}
|
|
369
|
+
top={childNode.y0 - absoluteTop}
|
|
370
|
+
width={childNode.x1 - childNode.x0}
|
|
371
|
+
height={childNode.y1 - childNode.y0}
|
|
372
|
+
onClick={onItemClick}
|
|
373
|
+
key={childNode.data.id}
|
|
374
|
+
/>
|
|
375
|
+
);
|
|
376
|
+
})}
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
206
379
|
};
|
|
207
380
|
|
|
208
381
|
interface UseMetricsTreemapHierarchyParams {
|
|
209
|
-
|
|
382
|
+
treeNodes: Tree;
|
|
210
383
|
width: number;
|
|
211
384
|
height: number;
|
|
385
|
+
nested: boolean;
|
|
212
386
|
}
|
|
213
387
|
|
|
214
388
|
function useMetricsTreemapHierarchy(params: UseMetricsTreemapHierarchyParams) {
|
|
215
|
-
const {
|
|
389
|
+
const { treeNodes, width, height, nested } = params;
|
|
216
390
|
|
|
217
391
|
const treemapHierarchy = useMemo(() => {
|
|
218
|
-
const
|
|
219
|
-
/**
|
|
220
|
-
* Set tile value to the largest run value to allow to
|
|
221
|
-
* display the impact of deleted/new entries
|
|
222
|
-
*/
|
|
223
|
-
const values = item.runs.map((run) => run?.value || 0);
|
|
224
|
-
const value = Math.max(...values);
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
id: item.key,
|
|
228
|
-
value,
|
|
229
|
-
item,
|
|
230
|
-
};
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const treemapData = {
|
|
234
|
-
id: 'root',
|
|
235
|
-
name: 'root',
|
|
236
|
-
value: 0,
|
|
237
|
-
children,
|
|
238
|
-
};
|
|
392
|
+
const customHierarchy = hierarchy<Tree>(treeNodes);
|
|
239
393
|
|
|
240
|
-
const customHierarchy = hierarchy<RootTree>(treemapData);
|
|
241
394
|
customHierarchy.sum((node) => node.value);
|
|
395
|
+
// sort by value descending for all cases
|
|
242
396
|
// @ts-expect-error
|
|
243
397
|
customHierarchy.sort((a, b) => b.value - a.value);
|
|
244
398
|
|
|
245
399
|
return customHierarchy;
|
|
246
|
-
}, [
|
|
400
|
+
}, [treeNodes]);
|
|
247
401
|
|
|
248
|
-
const
|
|
249
|
-
|
|
402
|
+
const treemapNodes = useMemo(() => {
|
|
403
|
+
let createTremapLayout = treemap()
|
|
250
404
|
.size([width, height])
|
|
251
405
|
.tile(treemapSquarify.ratio(SQUARIFY_RATIO))
|
|
252
406
|
.paddingOuter(PADDING_OUTER)
|
|
253
407
|
.paddingInner(PADDING_INNER);
|
|
254
|
-
// @ts-expect-error
|
|
255
|
-
const tremapLayout = createTremapLayout(treemapHierarchy);
|
|
256
408
|
|
|
257
|
-
|
|
409
|
+
// Add padding top for group title
|
|
410
|
+
if (nested) {
|
|
411
|
+
createTremapLayout = createTremapLayout.paddingTop(GROUPED_PADDING_TOP);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// @ts-expect-error
|
|
415
|
+
return createTremapLayout(treemapHierarchy) as HierarchyRectangularNode<TreeNode>;
|
|
258
416
|
}, [height, width, treemapHierarchy]);
|
|
259
417
|
|
|
260
|
-
return
|
|
418
|
+
return treemapNodes;
|
|
261
419
|
}
|
|
262
420
|
|
|
263
421
|
interface MetricsTreemapProps {
|
|
264
|
-
|
|
265
|
-
emptyMessage?:
|
|
422
|
+
treeNodes: Tree;
|
|
423
|
+
emptyMessage?: ReactNode;
|
|
424
|
+
nested?: boolean;
|
|
266
425
|
onItemClick?: (entryId: string) => void;
|
|
426
|
+
onGroupClick?: (entryId: string) => void;
|
|
267
427
|
}
|
|
268
428
|
|
|
269
|
-
export const MetricsTreemap = (props: MetricsTreemapProps &
|
|
270
|
-
const {
|
|
429
|
+
export const MetricsTreemap = (props: MetricsTreemapProps & ComponentProps<'div'>) => {
|
|
430
|
+
const {
|
|
431
|
+
className = '',
|
|
432
|
+
treeNodes,
|
|
433
|
+
onItemClick,
|
|
434
|
+
onGroupClick,
|
|
435
|
+
emptyMessage = 'No data',
|
|
436
|
+
nested = false,
|
|
437
|
+
...restProps
|
|
438
|
+
} = props;
|
|
271
439
|
|
|
272
440
|
const [containerRef, { width, height }] = useMeasure<HTMLDivElement>();
|
|
273
|
-
const
|
|
441
|
+
const rootNode = useMetricsTreemapHierarchy({ treeNodes, width, height, nested });
|
|
274
442
|
|
|
275
443
|
return (
|
|
276
|
-
<div
|
|
277
|
-
{
|
|
444
|
+
<div
|
|
445
|
+
className={cx(css.root, nested && css.nested, className)}
|
|
446
|
+
{...restProps}
|
|
447
|
+
ref={containerRef}
|
|
448
|
+
>
|
|
449
|
+
{rootNode.children && rootNode.children.length > 0 ? (
|
|
278
450
|
<div className={css.canvas}>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
key={tile.id}
|
|
293
|
-
/>
|
|
294
|
-
);
|
|
295
|
-
})}
|
|
451
|
+
<TileGroup
|
|
452
|
+
title={nested ? rootNode.data.label : undefined}
|
|
453
|
+
id={rootNode.data.id}
|
|
454
|
+
childNodes={rootNode.children}
|
|
455
|
+
onItemClick={onItemClick}
|
|
456
|
+
onGroupClick={onGroupClick}
|
|
457
|
+
left={rootNode.x0}
|
|
458
|
+
top={rootNode.y0}
|
|
459
|
+
absoluteLeft={rootNode.x0}
|
|
460
|
+
absoluteTop={rootNode.y0}
|
|
461
|
+
width={rootNode.x1 - rootNode.x0}
|
|
462
|
+
height={rootNode.y1 - rootNode.y0}
|
|
463
|
+
/>
|
|
296
464
|
</div>
|
|
297
465
|
) : (
|
|
298
466
|
<div className={css.emptyMessage}>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ReportMetricRow } from '@bundle-stats/utils';
|
|
2
|
+
|
|
3
|
+
import type { TreeLeaf, TreeNodeChildren, Tree } from './metrics-treemap.constants';
|
|
4
|
+
|
|
5
|
+
const ROOT_LABEL = '(root)';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Set node value to the largest run value to allow to
|
|
9
|
+
* display the impact of deleted/new entries
|
|
10
|
+
*/
|
|
11
|
+
function getReportMetricRowMaxValue(item: ReportMetricRow): number {
|
|
12
|
+
const values = item.runs.map((run) => run?.value || 0);
|
|
13
|
+
return Math.max(...values);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate treemap node list(flat)
|
|
18
|
+
*/
|
|
19
|
+
export function getTreemapNodes(items: Array<ReportMetricRow>): Tree {
|
|
20
|
+
const rootChildren: Array<TreeLeaf> = items.map((item) => ({
|
|
21
|
+
id: item.key,
|
|
22
|
+
label: item.key,
|
|
23
|
+
value: getReportMetricRowMaxValue(item),
|
|
24
|
+
item,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
id: '',
|
|
29
|
+
label: ROOT_LABEL,
|
|
30
|
+
value: 0,
|
|
31
|
+
children: rootChildren,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively set treemap nodes
|
|
37
|
+
*/
|
|
38
|
+
function setTreeNode(
|
|
39
|
+
nodes: TreeNodeChildren,
|
|
40
|
+
slugs: Array<string>,
|
|
41
|
+
currentSlugId: number,
|
|
42
|
+
newNode: TreeLeaf,
|
|
43
|
+
): void {
|
|
44
|
+
const baseSlugs = slugs.slice(0, currentSlugId);
|
|
45
|
+
const [currentSlug, ...restSlug] = slugs.slice(currentSlugId);
|
|
46
|
+
|
|
47
|
+
// Add to current nodes if there are no child nodes
|
|
48
|
+
if (!currentSlug) {
|
|
49
|
+
nodes.push(newNode);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const currentNodePath = [...baseSlugs, currentSlug].join('/');
|
|
54
|
+
|
|
55
|
+
// Check for existing parent in current nodes
|
|
56
|
+
let parentNode = nodes.find((treeNode) => treeNode.id === currentNodePath) as Tree;
|
|
57
|
+
|
|
58
|
+
// Create the new parentNode if missing
|
|
59
|
+
if (!parentNode) {
|
|
60
|
+
parentNode = {
|
|
61
|
+
id: currentNodePath,
|
|
62
|
+
label: currentSlug,
|
|
63
|
+
value: 0,
|
|
64
|
+
children: [],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
nodes.push(parentNode);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If there are no other slugs, the new node is as leaf and we add it to the parent
|
|
71
|
+
if (restSlug.length === 0) {
|
|
72
|
+
parentNode.children.push(newNode);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setTreeNode(parentNode.children, slugs, currentSlugId + 1, newNode);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate treemap nodes tree by directory
|
|
81
|
+
*/
|
|
82
|
+
export function getTreemapNodesGroupedByPath(items: Array<ReportMetricRow>): Tree {
|
|
83
|
+
const treeNodes: TreeNodeChildren = [];
|
|
84
|
+
|
|
85
|
+
items.forEach((item) => {
|
|
86
|
+
// const normalizedPath = item.key.replace(/^\.?\//, ''); // replace './' or '/' prefix
|
|
87
|
+
const slugs = item.key.split('/');
|
|
88
|
+
const baseSlugs = slugs.slice(0, -1);
|
|
89
|
+
const baseName = slugs.slice(-1)[0];
|
|
90
|
+
|
|
91
|
+
const treeNode = {
|
|
92
|
+
id: item.key,
|
|
93
|
+
label: baseName,
|
|
94
|
+
value: getReportMetricRowMaxValue(item),
|
|
95
|
+
item,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
setTreeNode(treeNodes, baseSlugs, 0, treeNode);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
id: '',
|
|
103
|
+
label: ROOT_LABEL,
|
|
104
|
+
value: 0,
|
|
105
|
+
children: treeNodes,
|
|
106
|
+
};
|
|
107
|
+
}
|