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,649 @@
1
+ // MintWaterfall Advanced Interactions
2
+ // Sophisticated D3.js interaction capabilities for enhanced waterfall analysis
3
+
4
+ import * as d3 from 'd3';
5
+ import { drag } from 'd3-drag';
6
+ import { forceSimulation, forceCenter, forceCollide, forceManyBody } from 'd3-force';
7
+
8
+ // ============================================================================
9
+ // TYPE DEFINITIONS
10
+ // ============================================================================
11
+
12
+ export interface DragConfig {
13
+ enabled: boolean;
14
+ axis: 'both' | 'horizontal' | 'vertical';
15
+ constraints?: {
16
+ minValue?: number;
17
+ maxValue?: number;
18
+ snapToGrid?: boolean;
19
+ gridSize?: number;
20
+ };
21
+ onDragStart?: (event: d3.D3DragEvent<any, any, any>, data: any) => void;
22
+ onDrag?: (event: d3.D3DragEvent<any, any, any>, data: any) => void;
23
+ onDragEnd?: (event: d3.D3DragEvent<any, any, any>, data: any) => void;
24
+ }
25
+
26
+ export interface VoronoiConfig {
27
+ enabled: boolean;
28
+ extent: [[number, number], [number, number]];
29
+ showCells?: boolean;
30
+ cellOpacity?: number;
31
+ onCellEnter?: (event: MouseEvent, data: any) => void;
32
+ onCellLeave?: (event: MouseEvent, data: any) => void;
33
+ onCellClick?: (event: MouseEvent, data: any) => void;
34
+ }
35
+
36
+ export interface ForceSimulationConfig {
37
+ enabled: boolean;
38
+ forces: {
39
+ collision?: boolean;
40
+ centering?: boolean;
41
+ positioning?: boolean;
42
+ links?: boolean;
43
+ };
44
+ strength: {
45
+ collision?: number;
46
+ centering?: number;
47
+ positioning?: number;
48
+ links?: number;
49
+ };
50
+ duration?: number;
51
+ onTick?: (simulation: d3.Simulation<any, any>) => void;
52
+ onEnd?: (simulation: d3.Simulation<any, any>) => void;
53
+ }
54
+
55
+ export interface InteractionSystem {
56
+ // Drag functionality
57
+ enableDrag(config: DragConfig): void;
58
+ disableDrag(): void;
59
+ updateDragConstraints(constraints: DragConfig['constraints']): void;
60
+
61
+ // Enhanced hover detection (simplified approach)
62
+ enableEnhancedHover(config: VoronoiConfig): void;
63
+ disableEnhancedHover(): void;
64
+ updateHoverExtent(extent: [[number, number], [number, number]]): void;
65
+
66
+ // Force simulation for dynamic layouts
67
+ startForceSimulation(config: ForceSimulationConfig): d3.Simulation<any, any>;
68
+ stopForceSimulation(): void;
69
+ updateForces(forces: ForceSimulationConfig['forces']): void;
70
+
71
+ // Combined interaction management
72
+ setInteractionMode(mode: 'drag' | 'voronoi' | 'force' | 'combined' | 'none'): void;
73
+ getActiveInteractions(): string[];
74
+
75
+ // Data management
76
+ updateData(data: any[]): void;
77
+
78
+ // Event management
79
+ on(event: string, callback: Function): void;
80
+ off(event: string): void;
81
+ trigger(event: string, data?: any): void;
82
+ }
83
+
84
+ // ============================================================================
85
+ // ADVANCED INTERACTION SYSTEM IMPLEMENTATION
86
+ // ============================================================================
87
+
88
+ export function createAdvancedInteractionSystem(
89
+ container: d3.Selection<any, any, any, any>,
90
+ xScale: d3.ScaleLinear<number, number> | d3.ScaleBand<string>,
91
+ yScale: d3.ScaleLinear<number, number>
92
+ ): InteractionSystem {
93
+
94
+ // Internal state
95
+ let dragBehavior: d3.DragBehavior<any, any, any> | null = null;
96
+ let enhancedHoverEnabled: boolean = false;
97
+ let currentSimulation: d3.Simulation<any, any> | null = null;
98
+ let currentData: any[] = [];
99
+ let eventListeners: Map<string, Function[]> = new Map();
100
+
101
+ // ========================================================================
102
+ // DRAG FUNCTIONALITY (d3.drag)
103
+ // ========================================================================
104
+
105
+ function enableDrag(config: DragConfig): void {
106
+ if (!config || !config.enabled) {
107
+ disableDrag();
108
+ return;
109
+ }
110
+
111
+ // Create drag behavior
112
+ dragBehavior = drag<any, any>()
113
+ .on('start', (event, d) => {
114
+ // Visual feedback on drag start
115
+ d3.select(event.sourceEvent.target)
116
+ .raise()
117
+ .attr('stroke', '#ff6b6b')
118
+ .attr('stroke-width', 2);
119
+
120
+ if (config.onDragStart) {
121
+ config.onDragStart(event, d);
122
+ }
123
+
124
+ trigger('dragStart', { event, data: d });
125
+ })
126
+ .on('drag', (event, d) => {
127
+ const bar = d3.select(event.sourceEvent.target);
128
+ let newValue = d.value || 0;
129
+
130
+ // Handle axis constraints
131
+ if (config.axis === 'vertical' || config.axis === 'both') {
132
+ const newY = event.y;
133
+ newValue = yScale.invert(newY);
134
+
135
+ // Apply constraints
136
+ if (config.constraints) {
137
+ const { minValue, maxValue, snapToGrid, gridSize } = config.constraints;
138
+
139
+ if (minValue !== undefined) newValue = Math.max(minValue, newValue);
140
+ if (maxValue !== undefined) newValue = Math.min(maxValue, newValue);
141
+
142
+ if (snapToGrid && gridSize) {
143
+ newValue = Math.round(newValue / gridSize) * gridSize;
144
+ }
145
+ }
146
+
147
+ // Update visual position
148
+ const barHeight = Math.abs(yScale(0) - yScale(newValue));
149
+ const barY = newValue >= 0 ? yScale(newValue) : yScale(0);
150
+
151
+ bar.attr('y', barY)
152
+ .attr('height', barHeight);
153
+
154
+ // Update data
155
+ d.value = newValue;
156
+ if (d.stacks && d.stacks.length > 0) {
157
+ d.stacks[0].value = newValue;
158
+ }
159
+ }
160
+
161
+ // Handle horizontal movement (for reordering)
162
+ if (config.axis === 'horizontal' || config.axis === 'both') {
163
+ const newX = event.x;
164
+ // Implementation for horizontal dragging/reordering
165
+ const barWidth = parseFloat(bar.attr('width') || '0');
166
+ bar.attr('transform', `translate(${newX - barWidth / 2}, 0)`);
167
+ }
168
+
169
+ if (config.onDrag) {
170
+ config.onDrag(event, d);
171
+ }
172
+
173
+ trigger('drag', { event, data: d, newValue });
174
+ })
175
+ .on('end', (event, d) => {
176
+ // Remove visual feedback
177
+ d3.select(event.sourceEvent.target)
178
+ .attr('stroke', null)
179
+ .attr('stroke-width', null);
180
+
181
+ if (config.onDragEnd) {
182
+ config.onDragEnd(event, d);
183
+ }
184
+
185
+ trigger('dragEnd', { event, data: d });
186
+ });
187
+
188
+ // Apply drag behavior to all bars
189
+ container.selectAll('.bar')
190
+ .call(dragBehavior);
191
+
192
+ trigger('dragEnabled', config);
193
+ }
194
+
195
+ function disableDrag(): void {
196
+ if (dragBehavior) {
197
+ container.selectAll('.bar')
198
+ .on('.drag', null);
199
+ dragBehavior = null;
200
+ trigger('dragDisabled');
201
+ }
202
+ }
203
+
204
+ function updateDragConstraints(constraints: DragConfig['constraints']): void {
205
+ // Constraints are checked during drag events
206
+ trigger('dragConstraintsUpdated', constraints);
207
+ }
208
+
209
+ // ========================================================================
210
+ // ENHANCED HOVER DETECTION (Simplified approach)
211
+ // ========================================================================
212
+
213
+ function enableEnhancedHover(config: VoronoiConfig): void {
214
+ if (!config || !config.enabled || currentData.length === 0) {
215
+ disableEnhancedHover();
216
+ return;
217
+ }
218
+
219
+ enhancedHoverEnabled = true;
220
+
221
+ // Create enhanced hover zones around bars
222
+ const hoverGroup = container.selectAll('.enhanced-hover-group')
223
+ .data([0]);
224
+
225
+ const hoverGroupEnter = hoverGroup.enter()
226
+ .append('g')
227
+ .attr('class', 'enhanced-hover-group');
228
+
229
+ const hoverGroupMerged = hoverGroupEnter.merge(hoverGroup as any);
230
+
231
+ // Add enhanced hover zones
232
+ const zones = hoverGroupMerged.selectAll('.hover-zone')
233
+ .data(currentData);
234
+
235
+ zones.enter()
236
+ .append('rect')
237
+ .attr('class', 'hover-zone')
238
+ .merge(zones as any)
239
+ .attr('x', d => getBarCenterX(d) - getBarWidth(d) * 0.75)
240
+ .attr('y', d => Math.min(getBarCenterY(d), yScale(0)) - 10)
241
+ .attr('width', d => getBarWidth(d) * 1.5)
242
+ .attr('height', d => Math.abs(yScale(0) - getBarCenterY(d)) + 20)
243
+ .style('fill', 'transparent')
244
+ .style('pointer-events', 'all')
245
+ .on('mouseenter', function(event, d) {
246
+ highlightBar(d);
247
+ if (config.onCellEnter) {
248
+ config.onCellEnter(event, d);
249
+ }
250
+ trigger('enhancedHoverEnter', { event, data: d });
251
+ })
252
+ .on('mouseleave', function(event, d) {
253
+ unhighlightBar(d);
254
+ if (config.onCellLeave) {
255
+ config.onCellLeave(event, d);
256
+ }
257
+ trigger('enhancedHoverLeave', { event, data: d });
258
+ })
259
+ .on('click', function(event, d) {
260
+ if (config.onCellClick) {
261
+ config.onCellClick(event, d);
262
+ }
263
+ trigger('enhancedHoverClick', { event, data: d });
264
+ });
265
+
266
+ zones.exit().remove();
267
+
268
+ trigger('enhancedHoverEnabled', config);
269
+ }
270
+
271
+ function disableEnhancedHover(): void {
272
+ try {
273
+ // Only attempt to remove elements if container has proper D3 methods
274
+ if (container && container.selectAll && typeof container.selectAll === 'function') {
275
+ const selection = container.selectAll('.enhanced-hover-group');
276
+ if (selection && selection.remove && typeof selection.remove === 'function') {
277
+ selection.remove();
278
+ }
279
+ }
280
+ } catch (error) {
281
+ // Silently handle any DOM manipulation errors in test environment
282
+ }
283
+ enhancedHoverEnabled = false;
284
+ trigger('enhancedHoverDisabled');
285
+ }
286
+
287
+ function updateHoverExtent(extent: [[number, number], [number, number]]): void {
288
+ if (enhancedHoverEnabled) {
289
+ // Re-enable with current data
290
+ const currentConfig = { enabled: true, extent };
291
+ enableEnhancedHover(currentConfig);
292
+ }
293
+ }
294
+
295
+ // ========================================================================
296
+ // FORCE SIMULATION FOR DYNAMIC LAYOUTS (d3.forceSimulation)
297
+ // ========================================================================
298
+
299
+ function startForceSimulation(config: ForceSimulationConfig): d3.Simulation<any, any> {
300
+ if (!config || !config.enabled || currentData.length === 0) {
301
+ return forceSimulation<any, any>([]);
302
+ }
303
+
304
+ // Stop any existing simulation
305
+ stopForceSimulation();
306
+
307
+ // Create new simulation
308
+ currentSimulation = forceSimulation(currentData);
309
+
310
+ // Add forces based on configuration
311
+ if (config.forces.collision) {
312
+ currentSimulation.force('collision', forceCollide()
313
+ .radius(d => getBarWidth(d) / 2 + 5)
314
+ .strength(config.strength.collision || 0.7));
315
+ }
316
+
317
+ if (config.forces.centering) {
318
+ const centerX = (xScale.range()[0] + xScale.range()[1]) / 2;
319
+ const centerY = (yScale.range()[0] + yScale.range()[1]) / 2;
320
+ currentSimulation.force('center', forceCenter(centerX, centerY)
321
+ .strength(config.strength.centering || 0.1));
322
+ }
323
+
324
+ if (config.forces.positioning) {
325
+ currentSimulation.force('x', d3.forceX(d => getBarCenterX(d))
326
+ .strength(config.strength.positioning || 0.5));
327
+ currentSimulation.force('y', d3.forceY(d => getBarCenterY(d))
328
+ .strength(config.strength.positioning || 0.5));
329
+ }
330
+
331
+ if (config.forces.links && currentData.length > 1) {
332
+ // Create links between consecutive bars
333
+ const links = currentData.slice(1).map((d, i) => ({
334
+ source: currentData[i],
335
+ target: d
336
+ }));
337
+
338
+ currentSimulation.force('link', d3.forceLink(links)
339
+ .distance(50)
340
+ .strength(config.strength.links || 0.3));
341
+ }
342
+
343
+ // Set up tick handler
344
+ currentSimulation.on('tick', () => {
345
+ updateBarPositions();
346
+ if (config.onTick && currentSimulation) {
347
+ config.onTick(currentSimulation);
348
+ }
349
+ trigger('forceTick', currentSimulation);
350
+ });
351
+
352
+ // Set up end handler
353
+ currentSimulation.on('end', () => {
354
+ if (config.onEnd && currentSimulation) {
355
+ config.onEnd(currentSimulation);
356
+ }
357
+ trigger('forceEnd', currentSimulation);
358
+ });
359
+
360
+ // Set alpha decay for animation duration
361
+ if (config.duration) {
362
+ const targetAlpha = 0.001;
363
+ const decay = 1 - Math.pow(targetAlpha, 1 / config.duration);
364
+ currentSimulation.alphaDecay(decay);
365
+ }
366
+
367
+ trigger('forceSimulationStarted', config);
368
+ return currentSimulation;
369
+ }
370
+
371
+ function stopForceSimulation(): void {
372
+ if (currentSimulation) {
373
+ currentSimulation.stop();
374
+ currentSimulation = null;
375
+ trigger('forceSimulationStopped');
376
+ }
377
+ }
378
+
379
+ function updateForces(forces: ForceSimulationConfig['forces']): void {
380
+ if (currentSimulation) {
381
+ // Update or remove forces based on configuration
382
+ if (!forces.collision) currentSimulation.force('collision', null);
383
+ if (!forces.centering) currentSimulation.force('center', null);
384
+ if (!forces.positioning) {
385
+ currentSimulation.force('x', null);
386
+ currentSimulation.force('y', null);
387
+ }
388
+ if (!forces.links) currentSimulation.force('link', null);
389
+
390
+ currentSimulation.alpha(1).restart();
391
+ trigger('forcesUpdated', forces);
392
+ }
393
+ }
394
+
395
+ // ========================================================================
396
+ // INTERACTION MODE MANAGEMENT
397
+ // ========================================================================
398
+
399
+ function setInteractionMode(mode: 'drag' | 'voronoi' | 'force' | 'combined' | 'none'): void {
400
+ // Disable all interactions first
401
+ disableDrag();
402
+ disableEnhancedHover();
403
+ stopForceSimulation();
404
+
405
+ const xRange = (xScale as any).range() || [0, 800];
406
+ const yRange = (yScale as any).range() || [400, 0];
407
+
408
+ switch (mode) {
409
+ case 'drag':
410
+ enableDrag({
411
+ enabled: true,
412
+ axis: 'vertical',
413
+ constraints: { snapToGrid: true, gridSize: 10 }
414
+ });
415
+ break;
416
+
417
+ case 'voronoi':
418
+ enableEnhancedHover({
419
+ enabled: true,
420
+ extent: [[0, 0], [xRange[1], yRange[0]]]
421
+ });
422
+ break;
423
+
424
+ case 'force':
425
+ startForceSimulation({
426
+ enabled: true,
427
+ forces: { collision: true, positioning: true },
428
+ strength: { collision: 0.7, positioning: 0.5 },
429
+ duration: 1000
430
+ });
431
+ break;
432
+
433
+ case 'combined':
434
+ enableEnhancedHover({
435
+ enabled: true,
436
+ extent: [[0, 0], [xRange[1], yRange[0]]]
437
+ });
438
+ enableDrag({
439
+ enabled: true,
440
+ axis: 'vertical'
441
+ });
442
+ break;
443
+
444
+ case 'none':
445
+ default:
446
+ // All interactions disabled
447
+ break;
448
+ }
449
+
450
+ trigger('interactionModeChanged', mode);
451
+ }
452
+
453
+ function getActiveInteractions(): string[] {
454
+ const active: string[] = [];
455
+ if (dragBehavior) active.push('drag');
456
+ if (enhancedHoverEnabled) active.push('hover');
457
+ if (currentSimulation) active.push('force');
458
+ return active;
459
+ }
460
+
461
+ // ========================================================================
462
+ // EVENT MANAGEMENT
463
+ // ========================================================================
464
+
465
+ function on(event: string, callback: Function): void {
466
+ if (!eventListeners.has(event)) {
467
+ eventListeners.set(event, []);
468
+ }
469
+ eventListeners.get(event)!.push(callback);
470
+ }
471
+
472
+ function off(event: string): void {
473
+ eventListeners.delete(event);
474
+ }
475
+
476
+ function trigger(event: string, data?: any): void {
477
+ const callbacks = eventListeners.get(event);
478
+ if (callbacks) {
479
+ callbacks.forEach(callback => callback(data));
480
+ }
481
+ }
482
+
483
+ // ========================================================================
484
+ // UTILITY FUNCTIONS
485
+ // ========================================================================
486
+
487
+ function getBarCenterX(d: any): number {
488
+ const scale = xScale as any; // Type assertion for compatibility
489
+ if (scale.bandwidth) {
490
+ // Band scale
491
+ return (scale(d.label) || 0) + scale.bandwidth() / 2;
492
+ } else {
493
+ // Linear scale - assume equal spacing
494
+ return scale(parseFloat(d.label) || 0);
495
+ }
496
+ }
497
+
498
+ function getBarCenterY(d: any): number {
499
+ const value = d.value || (d.stacks && d.stacks[0] ? d.stacks[0].value : 0);
500
+ return yScale(value / 2);
501
+ }
502
+
503
+ function getBarWidth(d: any): number {
504
+ const scale = xScale as any; // Type assertion for compatibility
505
+ if (scale.bandwidth) {
506
+ return scale.bandwidth();
507
+ }
508
+ return 40; // Default width for linear scales
509
+ }
510
+
511
+ function highlightBar(data: any): void {
512
+ container.selectAll('.bar')
513
+ .filter((d: any) => d === data)
514
+ .transition()
515
+ .duration(150)
516
+ .attr('opacity', 0.8)
517
+ .attr('stroke', '#ff6b6b')
518
+ .attr('stroke-width', 2);
519
+ }
520
+
521
+ function unhighlightBar(data: any): void {
522
+ container.selectAll('.bar')
523
+ .filter((d: any) => d === data)
524
+ .transition()
525
+ .duration(150)
526
+ .attr('opacity', 1)
527
+ .attr('stroke', null)
528
+ .attr('stroke-width', null);
529
+ }
530
+
531
+ function updateBarPositions(): void {
532
+ if (!forceSimulation) return;
533
+
534
+ container.selectAll('.bar')
535
+ .data(currentData)
536
+ .attr('transform', (d: any) => {
537
+ const x = (d as any).x || getBarCenterX(d);
538
+ const y = (d as any).y || getBarCenterY(d);
539
+ return `translate(${x - getBarWidth(d) / 2}, ${y})`;
540
+ });
541
+ }
542
+
543
+ // ========================================================================
544
+ // PUBLIC API
545
+ // ========================================================================
546
+
547
+ // Method to update data for interactions
548
+ function updateData(data: any[]): void {
549
+ currentData = data;
550
+
551
+ // Update active interactions with new data
552
+ if (enhancedHoverEnabled) {
553
+ const config = { enabled: true, extent: [[0, 0], [800, 600]] as [[number, number], [number, number]] };
554
+ enableEnhancedHover(config);
555
+ }
556
+
557
+ if (currentSimulation) {
558
+ currentSimulation.nodes(data);
559
+ currentSimulation.alpha(1).restart();
560
+ }
561
+ }
562
+
563
+ return {
564
+ enableDrag,
565
+ disableDrag,
566
+ updateDragConstraints,
567
+ enableEnhancedHover,
568
+ disableEnhancedHover,
569
+ updateHoverExtent,
570
+ startForceSimulation,
571
+ stopForceSimulation,
572
+ updateForces,
573
+ setInteractionMode,
574
+ getActiveInteractions,
575
+ updateData,
576
+ on,
577
+ off,
578
+ trigger
579
+ };
580
+ }
581
+
582
+ // ============================================================================
583
+ // SPECIALIZED WATERFALL INTERACTION UTILITIES
584
+ // ============================================================================
585
+
586
+ /**
587
+ * Create drag behavior specifically optimized for waterfall charts
588
+ */
589
+ export function createWaterfallDragBehavior(
590
+ onValueChange: (data: any, newValue: number) => void,
591
+ constraints?: { min?: number; max?: number }
592
+ ): DragConfig {
593
+ return {
594
+ enabled: true,
595
+ axis: 'vertical',
596
+ constraints: {
597
+ minValue: constraints?.min,
598
+ maxValue: constraints?.max,
599
+ snapToGrid: true,
600
+ gridSize: 100 // Snap to hundreds
601
+ },
602
+ onDrag: (event, data) => {
603
+ if (onValueChange) {
604
+ onValueChange(data, data.value);
605
+ }
606
+ }
607
+ };
608
+ }
609
+
610
+ /**
611
+ * Create voronoi configuration optimized for waterfall hover detection
612
+ */
613
+ export function createWaterfallVoronoiConfig(
614
+ chartWidth: number,
615
+ chartHeight: number,
616
+ margin: { top: number; right: number; bottom: number; left: number }
617
+ ): VoronoiConfig {
618
+ return {
619
+ enabled: true,
620
+ extent: [
621
+ [margin.left, margin.top],
622
+ [chartWidth - margin.right, chartHeight - margin.bottom]
623
+ ],
624
+ showCells: false,
625
+ cellOpacity: 0.1
626
+ };
627
+ }
628
+
629
+ /**
630
+ * Create force simulation for animated waterfall reordering
631
+ */
632
+ export function createWaterfallForceConfig(
633
+ animationDuration: number = 1000
634
+ ): ForceSimulationConfig {
635
+ return {
636
+ enabled: true,
637
+ forces: {
638
+ collision: true,
639
+ positioning: true,
640
+ centering: false,
641
+ links: false
642
+ },
643
+ strength: {
644
+ collision: 0.8,
645
+ positioning: 0.6
646
+ },
647
+ duration: animationDuration
648
+ };
649
+ }