mintwaterfall 0.8.6

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/CONTRIBUTING.md +199 -0
  3. package/README.md +363 -0
  4. package/dist/index.d.ts +149 -0
  5. package/dist/mintwaterfall.cjs.js +7978 -0
  6. package/dist/mintwaterfall.esm.js +7907 -0
  7. package/dist/mintwaterfall.min.js +7 -0
  8. package/dist/mintwaterfall.umd.js +7978 -0
  9. package/index.d.ts +149 -0
  10. package/package.json +126 -0
  11. package/src/enterprise/enterprise-core.js +0 -0
  12. package/src/enterprise/enterprise-feature-template.js +0 -0
  13. package/src/enterprise/feature-registry.js +0 -0
  14. package/src/enterprise/features/breakdown.js +0 -0
  15. package/src/features/breakdown.js +0 -0
  16. package/src/features/conditional-formatting.js +0 -0
  17. package/src/index.js +111 -0
  18. package/src/mintwaterfall-accessibility.ts +680 -0
  19. package/src/mintwaterfall-advanced-data.ts +1034 -0
  20. package/src/mintwaterfall-advanced-interactions.ts +649 -0
  21. package/src/mintwaterfall-advanced-performance.ts +582 -0
  22. package/src/mintwaterfall-animations.ts +595 -0
  23. package/src/mintwaterfall-brush.ts +471 -0
  24. package/src/mintwaterfall-chart-core.ts +296 -0
  25. package/src/mintwaterfall-chart.ts +1915 -0
  26. package/src/mintwaterfall-data.ts +1100 -0
  27. package/src/mintwaterfall-export.ts +475 -0
  28. package/src/mintwaterfall-hierarchical-layouts.ts +724 -0
  29. package/src/mintwaterfall-layouts.ts +647 -0
  30. package/src/mintwaterfall-performance.ts +573 -0
  31. package/src/mintwaterfall-scales.ts +437 -0
  32. package/src/mintwaterfall-shapes.ts +385 -0
  33. package/src/mintwaterfall-statistics.ts +821 -0
  34. package/src/mintwaterfall-themes.ts +391 -0
  35. package/src/mintwaterfall-tooltip.ts +450 -0
  36. package/src/mintwaterfall-zoom.ts +399 -0
  37. package/src/types/js-modules.d.ts +25 -0
  38. package/src/utils/compatibility-layer.js +0 -0
@@ -0,0 +1,724 @@
1
+ // MintWaterfall Hierarchical Layout Extensions
2
+ // Advanced D3.js hierarchical layouts for multi-dimensional waterfall analysis
3
+
4
+ import * as d3 from 'd3';
5
+
6
+ // ============================================================================
7
+ // TYPE DEFINITIONS
8
+ // ============================================================================
9
+
10
+ export interface HierarchicalData {
11
+ name: string;
12
+ value?: number;
13
+ children?: HierarchicalData[];
14
+ category?: string;
15
+ level?: number;
16
+ parent?: string;
17
+ metadata?: any;
18
+ }
19
+
20
+ export interface TreemapConfig {
21
+ width: number;
22
+ height: number;
23
+ padding: number;
24
+ tile: any; // Simplified type for compatibility
25
+ round: boolean;
26
+ colorScale?: d3.ScaleOrdinal<string, string>;
27
+ onNodeClick?: (node: d3.HierarchyRectangularNode<HierarchicalData>) => void;
28
+ onNodeHover?: (node: d3.HierarchyRectangularNode<HierarchicalData>) => void;
29
+ }
30
+
31
+ export interface PartitionConfig {
32
+ width: number;
33
+ height: number;
34
+ innerRadius?: number;
35
+ outerRadius?: number;
36
+ type: 'sunburst' | 'icicle';
37
+ colorScale?: d3.ScaleOrdinal<string, string>;
38
+ onNodeClick?: (node: d3.HierarchyRectangularNode<HierarchicalData>) => void;
39
+ }
40
+
41
+ export interface ClusterConfig {
42
+ width: number;
43
+ height: number;
44
+ nodeSize: [number, number];
45
+ separation?: (a: d3.HierarchyPointNode<HierarchicalData>, b: d3.HierarchyPointNode<HierarchicalData>) => number;
46
+ linkColor?: string;
47
+ nodeColor?: string;
48
+ }
49
+
50
+ export interface PackConfig {
51
+ width: number;
52
+ height: number;
53
+ padding: number;
54
+ colorScale?: d3.ScaleOrdinal<string, string>;
55
+ sizeAccessor?: (d: HierarchicalData) => number;
56
+ onNodeClick?: (node: d3.HierarchyCircularNode<HierarchicalData>) => void;
57
+ }
58
+
59
+ export interface HierarchicalLayoutSystem {
60
+ // Treemap layout for nested waterfall breakdowns
61
+ createTreemapLayout(data: HierarchicalData, config: TreemapConfig): d3.HierarchyRectangularNode<HierarchicalData>;
62
+ renderTreemap(container: d3.Selection<any, any, any, any>, layout: d3.HierarchyRectangularNode<HierarchicalData>, config: TreemapConfig): void;
63
+
64
+ // Partition layout for circular/radial waterfall views
65
+ createPartitionLayout(data: HierarchicalData, config: PartitionConfig): d3.HierarchyRectangularNode<HierarchicalData>;
66
+ renderPartition(container: d3.Selection<any, any, any, any>, layout: d3.HierarchyRectangularNode<HierarchicalData>, config: PartitionConfig): void;
67
+
68
+ // Cluster layout for dendogram-style categorization
69
+ createClusterLayout(data: HierarchicalData, config: ClusterConfig): d3.HierarchyPointNode<HierarchicalData>;
70
+ renderCluster(container: d3.Selection<any, any, any, any>, layout: d3.HierarchyPointNode<HierarchicalData>, config: ClusterConfig): void;
71
+
72
+ // Pack layout for bubble-based grouped waterfalls
73
+ createPackLayout(data: HierarchicalData, config: PackConfig): d3.HierarchyCircularNode<HierarchicalData>;
74
+ renderPack(container: d3.Selection<any, any, any, any>, layout: d3.HierarchyCircularNode<HierarchicalData>, config: PackConfig): void;
75
+
76
+ // Utility functions
77
+ transformWaterfallToHierarchy(waterfallData: any[]): HierarchicalData;
78
+ createDrillDownNavigation(data: HierarchicalData): any[];
79
+ calculateHierarchicalMetrics(node: d3.HierarchyNode<HierarchicalData>): any;
80
+ }
81
+
82
+ // ============================================================================
83
+ // HIERARCHICAL LAYOUT SYSTEM IMPLEMENTATION
84
+ // ============================================================================
85
+
86
+ export function createHierarchicalLayoutSystem(): HierarchicalLayoutSystem {
87
+
88
+ // ========================================================================
89
+ // TREEMAP LAYOUT (d3.treemap)
90
+ // ========================================================================
91
+
92
+ function createTreemapLayout(data: HierarchicalData, config: TreemapConfig): d3.HierarchyRectangularNode<HierarchicalData> {
93
+ // Create hierarchy
94
+ const root = d3.hierarchy(data)
95
+ .sum(d => d.value || 0)
96
+ .sort((a, b) => (b.value || 0) - (a.value || 0));
97
+
98
+ // Create treemap layout
99
+ const treemap = d3.treemap<HierarchicalData>()
100
+ .size([config.width, config.height])
101
+ .padding(config.padding)
102
+ .tile(config.tile)
103
+ .round(config.round);
104
+
105
+ return treemap(root);
106
+ }
107
+
108
+ function renderTreemap(
109
+ container: d3.Selection<any, any, any, any>,
110
+ layout: d3.HierarchyRectangularNode<HierarchicalData>,
111
+ config: TreemapConfig
112
+ ): void {
113
+ const colorScale = config.colorScale || d3.scaleOrdinal(d3.schemeCategory10);
114
+
115
+ // Create treemap group
116
+ const treemapGroup = container.selectAll('.treemap-group')
117
+ .data([0]);
118
+
119
+ const treemapGroupEnter = treemapGroup.enter()
120
+ .append('g')
121
+ .attr('class', 'treemap-group');
122
+
123
+ const treemapGroupMerged = treemapGroupEnter.merge(treemapGroup as any);
124
+
125
+ // Get leaf nodes for rendering
126
+ const leaves = layout.leaves();
127
+
128
+ // Create rectangles for leaf nodes
129
+ const cells = treemapGroupMerged.selectAll('.treemap-cell')
130
+ .data(leaves, (d: any) => d.data.name);
131
+
132
+ const cellsEnter = cells.enter()
133
+ .append('g')
134
+ .attr('class', 'treemap-cell');
135
+
136
+ // Add rectangles
137
+ cellsEnter.append('rect')
138
+ .attr('class', 'treemap-rect');
139
+
140
+ // Add labels
141
+ cellsEnter.append('text')
142
+ .attr('class', 'treemap-label');
143
+
144
+ // Add value labels
145
+ cellsEnter.append('text')
146
+ .attr('class', 'treemap-value');
147
+
148
+ const cellsMerged = cellsEnter.merge(cells as any);
149
+
150
+ // Update rectangles
151
+ cellsMerged.select('.treemap-rect')
152
+ .transition()
153
+ .duration(750)
154
+ .attr('x', d => d.x0)
155
+ .attr('y', d => d.y0)
156
+ .attr('width', d => d.x1 - d.x0)
157
+ .attr('height', d => d.y1 - d.y0)
158
+ .attr('fill', d => colorScale(d.data.category || d.data.name))
159
+ .attr('stroke', '#fff')
160
+ .attr('stroke-width', 1)
161
+ .attr('opacity', 0.8);
162
+
163
+ // Update labels
164
+ cellsMerged.select('.treemap-label')
165
+ .attr('x', d => (d.x0 + d.x1) / 2)
166
+ .attr('y', d => (d.y0 + d.y1) / 2 - 8)
167
+ .attr('text-anchor', 'middle')
168
+ .attr('font-size', d => Math.min(12, (d.x1 - d.x0) / 8))
169
+ .attr('fill', '#333')
170
+ .text(d => d.data.name);
171
+
172
+ // Update value labels
173
+ cellsMerged.select('.treemap-value')
174
+ .attr('x', d => (d.x0 + d.x1) / 2)
175
+ .attr('y', d => (d.y0 + d.y1) / 2 + 8)
176
+ .attr('text-anchor', 'middle')
177
+ .attr('font-size', d => Math.min(10, (d.x1 - d.x0) / 10))
178
+ .attr('fill', '#666')
179
+ .text(d => formatValue(d.value || 0));
180
+
181
+ // Add interaction
182
+ cellsMerged
183
+ .style('cursor', 'pointer')
184
+ .on('click', (event, d) => {
185
+ if (config.onNodeClick) {
186
+ config.onNodeClick(d);
187
+ }
188
+ })
189
+ .on('mouseenter', (event, d) => {
190
+ d3.select(event.currentTarget).select('.treemap-rect')
191
+ .attr('opacity', 1)
192
+ .attr('stroke-width', 2);
193
+
194
+ if (config.onNodeHover) {
195
+ config.onNodeHover(d);
196
+ }
197
+ })
198
+ .on('mouseleave', (event, d) => {
199
+ d3.select(event.currentTarget).select('.treemap-rect')
200
+ .attr('opacity', 0.8)
201
+ .attr('stroke-width', 1);
202
+ });
203
+
204
+ cells.exit()
205
+ .transition()
206
+ .duration(300)
207
+ .attr('opacity', 0)
208
+ .remove();
209
+ }
210
+
211
+ // ========================================================================
212
+ // PARTITION LAYOUT (d3.partition)
213
+ // ========================================================================
214
+
215
+ function createPartitionLayout(data: HierarchicalData, config: PartitionConfig): d3.HierarchyRectangularNode<HierarchicalData> {
216
+ // Create hierarchy
217
+ const root = d3.hierarchy(data)
218
+ .sum(d => d.value || 0)
219
+ .sort((a, b) => (b.value || 0) - (a.value || 0));
220
+
221
+ // Create partition layout
222
+ const partition = d3.partition<HierarchicalData>()
223
+ .size([2 * Math.PI, Math.min(config.width, config.height) / 2]);
224
+
225
+ return partition(root);
226
+ }
227
+
228
+ function renderPartition(
229
+ container: d3.Selection<any, any, any, any>,
230
+ layout: d3.HierarchyRectangularNode<HierarchicalData>,
231
+ config: PartitionConfig
232
+ ): void {
233
+ const colorScale = config.colorScale || d3.scaleOrdinal(d3.schemeCategory10);
234
+ const radius = Math.min(config.width, config.height) / 2;
235
+ const innerRadius = config.innerRadius || 0;
236
+
237
+ // Create partition group
238
+ const partitionGroup = container.selectAll('.partition-group')
239
+ .data([0]);
240
+
241
+ const partitionGroupEnter = partitionGroup.enter()
242
+ .append('g')
243
+ .attr('class', 'partition-group')
244
+ .attr('transform', `translate(${config.width / 2}, ${config.height / 2})`);
245
+
246
+ const partitionGroupMerged = partitionGroupEnter.merge(partitionGroup as any);
247
+
248
+ if (config.type === 'sunburst') {
249
+ renderSunburst(partitionGroupMerged, layout, colorScale, radius, innerRadius, config);
250
+ } else {
251
+ renderIcicle(partitionGroupMerged, layout, colorScale, config);
252
+ }
253
+ }
254
+
255
+ function renderSunburst(
256
+ container: d3.Selection<any, any, any, any>,
257
+ layout: d3.HierarchyRectangularNode<HierarchicalData>,
258
+ colorScale: d3.ScaleOrdinal<string, string>,
259
+ radius: number,
260
+ innerRadius: number,
261
+ config: PartitionConfig
262
+ ): void {
263
+ const arc = d3.arc<d3.HierarchyRectangularNode<HierarchicalData>>()
264
+ .startAngle(d => d.x0)
265
+ .endAngle(d => d.x1)
266
+ .innerRadius(d => Math.sqrt(d.y0) * radius / Math.sqrt(layout.y1))
267
+ .outerRadius(d => Math.sqrt(d.y1) * radius / Math.sqrt(layout.y1));
268
+
269
+ const descendants = layout.descendants().filter(d => d.depth > 0);
270
+
271
+ const paths = container.selectAll('.partition-arc')
272
+ .data(descendants, (d: any) => d.data.name);
273
+
274
+ const pathsEnter = paths.enter()
275
+ .append('path')
276
+ .attr('class', 'partition-arc')
277
+ .attr('fill', d => colorScale(d.data.category || d.data.name))
278
+ .attr('stroke', '#fff')
279
+ .attr('stroke-width', 1)
280
+ .style('cursor', 'pointer');
281
+
282
+ pathsEnter.merge(paths as any)
283
+ .transition()
284
+ .duration(750)
285
+ .attr('d', arc);
286
+
287
+ // Add interaction
288
+ pathsEnter.merge(paths as any)
289
+ .on('click', (event, d) => {
290
+ if (config.onNodeClick) {
291
+ config.onNodeClick(d);
292
+ }
293
+ });
294
+
295
+ paths.exit()
296
+ .transition()
297
+ .duration(300)
298
+ .attr('opacity', 0)
299
+ .remove();
300
+ }
301
+
302
+ function renderIcicle(
303
+ container: d3.Selection<any, any, any, any>,
304
+ layout: d3.HierarchyRectangularNode<HierarchicalData>,
305
+ colorScale: d3.ScaleOrdinal<string, string>,
306
+ config: PartitionConfig
307
+ ): void {
308
+ const descendants = layout.descendants();
309
+
310
+ const rects = container.selectAll('.partition-rect')
311
+ .data(descendants, (d: any) => d.data.name);
312
+
313
+ const rectsEnter = rects.enter()
314
+ .append('rect')
315
+ .attr('class', 'partition-rect')
316
+ .attr('fill', d => colorScale(d.data.category || d.data.name))
317
+ .attr('stroke', '#fff')
318
+ .attr('stroke-width', 1)
319
+ .style('cursor', 'pointer');
320
+
321
+ rectsEnter.merge(rects as any)
322
+ .transition()
323
+ .duration(750)
324
+ .attr('x', d => d.y0)
325
+ .attr('y', d => d.x0)
326
+ .attr('width', d => d.y1 - d.y0)
327
+ .attr('height', d => d.x1 - d.x0);
328
+
329
+ // Add interaction
330
+ rectsEnter.merge(rects as any)
331
+ .on('click', (event, d) => {
332
+ if (config.onNodeClick) {
333
+ config.onNodeClick(d);
334
+ }
335
+ });
336
+
337
+ rects.exit()
338
+ .transition()
339
+ .duration(300)
340
+ .attr('opacity', 0)
341
+ .remove();
342
+ }
343
+
344
+ // ========================================================================
345
+ // CLUSTER LAYOUT (d3.cluster)
346
+ // ========================================================================
347
+
348
+ function createClusterLayout(data: HierarchicalData, config: ClusterConfig): d3.HierarchyPointNode<HierarchicalData> {
349
+ // Create hierarchy
350
+ const root = d3.hierarchy(data);
351
+
352
+ // Create cluster layout
353
+ const cluster = d3.cluster<HierarchicalData>()
354
+ .size([config.width, config.height])
355
+ .nodeSize(config.nodeSize);
356
+
357
+ if (config.separation) {
358
+ cluster.separation(config.separation);
359
+ }
360
+
361
+ return cluster(root);
362
+ }
363
+
364
+ function renderCluster(
365
+ container: d3.Selection<any, any, any, any>,
366
+ layout: d3.HierarchyPointNode<HierarchicalData>,
367
+ config: ClusterConfig
368
+ ): void {
369
+ // Create cluster group
370
+ const clusterGroup = container.selectAll('.cluster-group')
371
+ .data([0]);
372
+
373
+ const clusterGroupEnter = clusterGroup.enter()
374
+ .append('g')
375
+ .attr('class', 'cluster-group');
376
+
377
+ const clusterGroupMerged = clusterGroupEnter.merge(clusterGroup as any);
378
+
379
+ const descendants = layout.descendants();
380
+ const links = layout.links();
381
+
382
+ // Render links
383
+ const linkSelection = clusterGroupMerged.selectAll('.cluster-link')
384
+ .data(links, (d: any) => `${d.source.data.name}-${d.target.data.name}`);
385
+
386
+ linkSelection.enter()
387
+ .append('path')
388
+ .attr('class', 'cluster-link')
389
+ .attr('fill', 'none')
390
+ .attr('stroke', config.linkColor || '#999')
391
+ .attr('stroke-width', 1)
392
+ .merge(linkSelection as any)
393
+ .transition()
394
+ .duration(750)
395
+ .attr('d', d3.linkHorizontal<d3.HierarchyPointLink<HierarchicalData>, d3.HierarchyPointNode<HierarchicalData>>()
396
+ .x(d => d.y || 0)
397
+ .y(d => d.x || 0));
398
+
399
+ linkSelection.exit().remove();
400
+
401
+ // Render nodes
402
+ const nodeSelection = clusterGroupMerged.selectAll('.cluster-node')
403
+ .data(descendants, (d: any) => d.data.name);
404
+
405
+ const nodeEnter = nodeSelection.enter()
406
+ .append('g')
407
+ .attr('class', 'cluster-node');
408
+
409
+ nodeEnter.append('circle')
410
+ .attr('r', 5)
411
+ .attr('fill', config.nodeColor || '#69b3a2');
412
+
413
+ nodeEnter.append('text')
414
+ .attr('dy', 3)
415
+ .attr('x', 8)
416
+ .style('font-size', '12px')
417
+ .text(d => d.data.name);
418
+
419
+ const nodeMerged = nodeEnter.merge(nodeSelection as any);
420
+
421
+ nodeMerged
422
+ .transition()
423
+ .duration(750)
424
+ .attr('transform', d => `translate(${d.y},${d.x})`);
425
+
426
+ nodeSelection.exit()
427
+ .transition()
428
+ .duration(300)
429
+ .attr('opacity', 0)
430
+ .remove();
431
+ }
432
+
433
+ // ========================================================================
434
+ // PACK LAYOUT (d3.pack)
435
+ // ========================================================================
436
+
437
+ function createPackLayout(data: HierarchicalData, config: PackConfig): d3.HierarchyCircularNode<HierarchicalData> {
438
+ // Create hierarchy
439
+ const root = d3.hierarchy(data)
440
+ .sum(config.sizeAccessor || (d => d.value || 0))
441
+ .sort((a, b) => (b.value || 0) - (a.value || 0));
442
+
443
+ // Create pack layout
444
+ const pack = d3.pack<HierarchicalData>()
445
+ .size([config.width, config.height])
446
+ .padding(config.padding);
447
+
448
+ return pack(root);
449
+ }
450
+
451
+ function renderPack(
452
+ container: d3.Selection<any, any, any, any>,
453
+ layout: d3.HierarchyCircularNode<HierarchicalData>,
454
+ config: PackConfig
455
+ ): void {
456
+ const colorScale = config.colorScale || d3.scaleOrdinal(d3.schemeCategory10);
457
+
458
+ // Create pack group
459
+ const packGroup = container.selectAll('.pack-group')
460
+ .data([0]);
461
+
462
+ const packGroupEnter = packGroup.enter()
463
+ .append('g')
464
+ .attr('class', 'pack-group');
465
+
466
+ const packGroupMerged = packGroupEnter.merge(packGroup as any);
467
+
468
+ const descendants = layout.descendants().filter(d => d.depth > 0);
469
+
470
+ // Create circles for nodes
471
+ const nodes = packGroupMerged.selectAll('.pack-node')
472
+ .data(descendants, (d: any) => d.data.name);
473
+
474
+ const nodesEnter = nodes.enter()
475
+ .append('g')
476
+ .attr('class', 'pack-node');
477
+
478
+ // Add circles
479
+ nodesEnter.append('circle')
480
+ .attr('class', 'pack-circle')
481
+ .style('cursor', 'pointer');
482
+
483
+ // Add labels
484
+ nodesEnter.append('text')
485
+ .attr('class', 'pack-label')
486
+ .attr('text-anchor', 'middle')
487
+ .attr('dy', '0.3em')
488
+ .style('font-size', '10px')
489
+ .style('fill', '#333')
490
+ .style('pointer-events', 'none');
491
+
492
+ const nodesMerged = nodesEnter.merge(nodes as any);
493
+
494
+ // Update positions and attributes
495
+ nodesMerged
496
+ .transition()
497
+ .duration(750)
498
+ .attr('transform', d => `translate(${d.x},${d.y})`);
499
+
500
+ nodesMerged.select('.pack-circle')
501
+ .transition()
502
+ .duration(750)
503
+ .attr('r', d => d.r)
504
+ .attr('fill', d => colorScale(d.data.category || d.data.name))
505
+ .attr('stroke', '#fff')
506
+ .attr('stroke-width', 1)
507
+ .attr('opacity', 0.7);
508
+
509
+ nodesMerged.select('.pack-label')
510
+ .text(d => d.r > 15 ? d.data.name : '')
511
+ .attr('font-size', d => Math.min(d.r / 3, 12));
512
+
513
+ // Add interaction
514
+ nodesMerged
515
+ .on('click', (event, d) => {
516
+ if (config.onNodeClick) {
517
+ config.onNodeClick(d);
518
+ }
519
+ })
520
+ .on('mouseenter', (event, d) => {
521
+ d3.select(event.currentTarget).select('.pack-circle')
522
+ .attr('opacity', 1)
523
+ .attr('stroke-width', 2);
524
+ })
525
+ .on('mouseleave', (event, d) => {
526
+ d3.select(event.currentTarget).select('.pack-circle')
527
+ .attr('opacity', 0.7)
528
+ .attr('stroke-width', 1);
529
+ });
530
+
531
+ nodes.exit()
532
+ .transition()
533
+ .duration(300)
534
+ .attr('opacity', 0)
535
+ .remove();
536
+ }
537
+
538
+ // ========================================================================
539
+ // UTILITY FUNCTIONS
540
+ // ========================================================================
541
+
542
+ function transformWaterfallToHierarchy(waterfallData: any[]): HierarchicalData {
543
+ // Group data by categories if available
544
+ const grouped = d3.group(waterfallData, d => d.category || 'Default');
545
+
546
+ const children: HierarchicalData[] = [];
547
+
548
+ for (const [category, items] of grouped) {
549
+ const categoryNode: HierarchicalData = {
550
+ name: category,
551
+ value: d3.sum(items, d => Math.abs(extractValue(d))),
552
+ category,
553
+ children: items.map(item => ({
554
+ name: getLabel(item),
555
+ value: Math.abs(extractValue(item)),
556
+ category,
557
+ metadata: item
558
+ }))
559
+ };
560
+
561
+ children.push(categoryNode);
562
+ }
563
+
564
+ return {
565
+ name: 'Root',
566
+ children,
567
+ value: d3.sum(children, d => d.value || 0)
568
+ };
569
+ }
570
+
571
+ function createDrillDownNavigation(data: HierarchicalData): any[] {
572
+ const navigation: any[] = [];
573
+
574
+ function traverse(node: HierarchicalData, path: string[] = []): void {
575
+ const currentPath = [...path, node.name];
576
+
577
+ navigation.push({
578
+ path: currentPath,
579
+ name: node.name,
580
+ value: node.value,
581
+ level: currentPath.length - 1,
582
+ hasChildren: !!(node.children && node.children.length > 0)
583
+ });
584
+
585
+ if (node.children) {
586
+ node.children.forEach(child => traverse(child, currentPath));
587
+ }
588
+ }
589
+
590
+ traverse(data);
591
+ return navigation;
592
+ }
593
+
594
+ function calculateHierarchicalMetrics(node: d3.HierarchyNode<HierarchicalData>): any {
595
+ return {
596
+ depth: node.depth,
597
+ height: node.height,
598
+ value: node.value,
599
+ leafCount: node.leaves().length,
600
+ descendants: node.descendants().length,
601
+ ancestors: node.ancestors().length,
602
+ totalValue: node.descendants().reduce((sum, d) => sum + (d.value || 0), 0),
603
+ averageValue: node.value && node.leaves().length ? node.value / node.leaves().length : 0
604
+ };
605
+ }
606
+
607
+ // Helper functions
608
+ function extractValue(item: any): number {
609
+ if (typeof item === 'number') return item;
610
+ if (item.value !== undefined) return item.value;
611
+ if (item.stacks && Array.isArray(item.stacks)) {
612
+ return item.stacks.reduce((sum: number, stack: any) => sum + (stack.value || 0), 0);
613
+ }
614
+ return 0;
615
+ }
616
+
617
+ function getLabel(item: any): string {
618
+ if (typeof item === 'string') return item;
619
+ if (item.label !== undefined) return item.label;
620
+ if (item.name !== undefined) return item.name;
621
+ return 'Unnamed';
622
+ }
623
+
624
+ function formatValue(value: number): string {
625
+ if (Math.abs(value) >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
626
+ if (Math.abs(value) >= 1000) return `${(value / 1000).toFixed(1)}K`;
627
+ return value.toFixed(0);
628
+ }
629
+
630
+ // ========================================================================
631
+ // RETURN LAYOUT SYSTEM INTERFACE
632
+ // ========================================================================
633
+
634
+ return {
635
+ createTreemapLayout,
636
+ renderTreemap,
637
+ createPartitionLayout,
638
+ renderPartition,
639
+ createClusterLayout,
640
+ renderCluster,
641
+ createPackLayout,
642
+ renderPack,
643
+ transformWaterfallToHierarchy,
644
+ createDrillDownNavigation,
645
+ calculateHierarchicalMetrics
646
+ };
647
+ }
648
+
649
+ // ============================================================================
650
+ // SPECIALIZED WATERFALL HIERARCHICAL UTILITIES
651
+ // ============================================================================
652
+
653
+ /**
654
+ * Create hierarchical waterfall breakdown using treemap
655
+ */
656
+ export function createWaterfallTreemap(
657
+ data: any[],
658
+ container: d3.Selection<any, any, any, any>,
659
+ width: number,
660
+ height: number
661
+ ): void {
662
+ const layoutSystem = createHierarchicalLayoutSystem();
663
+ const hierarchicalData = layoutSystem.transformWaterfallToHierarchy(data);
664
+
665
+ const config: TreemapConfig = {
666
+ width,
667
+ height,
668
+ padding: 2,
669
+ tile: d3.treemapBinary,
670
+ round: true,
671
+ colorScale: d3.scaleOrdinal(d3.schemeSet3)
672
+ };
673
+
674
+ const layout = layoutSystem.createTreemapLayout(hierarchicalData, config);
675
+ layoutSystem.renderTreemap(container, layout, config);
676
+ }
677
+
678
+ /**
679
+ * Create circular waterfall visualization using partition layout
680
+ */
681
+ export function createWaterfallSunburst(
682
+ data: any[],
683
+ container: d3.Selection<any, any, any, any>,
684
+ width: number,
685
+ height: number
686
+ ): void {
687
+ const layoutSystem = createHierarchicalLayoutSystem();
688
+ const hierarchicalData = layoutSystem.transformWaterfallToHierarchy(data);
689
+
690
+ const config: PartitionConfig = {
691
+ width,
692
+ height,
693
+ innerRadius: 40,
694
+ type: 'sunburst',
695
+ colorScale: d3.scaleOrdinal(d3.schemeCategory10)
696
+ };
697
+
698
+ const layout = layoutSystem.createPartitionLayout(hierarchicalData, config);
699
+ layoutSystem.renderPartition(container, layout, config);
700
+ }
701
+
702
+ /**
703
+ * Create bubble chart waterfall using pack layout
704
+ */
705
+ export function createWaterfallBubbles(
706
+ data: any[],
707
+ container: d3.Selection<any, any, any, any>,
708
+ width: number,
709
+ height: number
710
+ ): void {
711
+ const layoutSystem = createHierarchicalLayoutSystem();
712
+ const hierarchicalData = layoutSystem.transformWaterfallToHierarchy(data);
713
+
714
+ const config: PackConfig = {
715
+ width,
716
+ height,
717
+ padding: 3,
718
+ colorScale: d3.scaleOrdinal(d3.schemePastel1),
719
+ sizeAccessor: d => Math.abs(d.value || 0)
720
+ };
721
+
722
+ const layout = layoutSystem.createPackLayout(hierarchicalData, config);
723
+ layoutSystem.renderPack(container, layout, config);
724
+ }