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.
- package/CHANGELOG.md +223 -0
- package/CONTRIBUTING.md +199 -0
- package/README.md +363 -0
- package/dist/index.d.ts +149 -0
- package/dist/mintwaterfall.cjs.js +7978 -0
- package/dist/mintwaterfall.esm.js +7907 -0
- package/dist/mintwaterfall.min.js +7 -0
- package/dist/mintwaterfall.umd.js +7978 -0
- package/index.d.ts +149 -0
- package/package.json +126 -0
- package/src/enterprise/enterprise-core.js +0 -0
- package/src/enterprise/enterprise-feature-template.js +0 -0
- package/src/enterprise/feature-registry.js +0 -0
- package/src/enterprise/features/breakdown.js +0 -0
- package/src/features/breakdown.js +0 -0
- package/src/features/conditional-formatting.js +0 -0
- package/src/index.js +111 -0
- package/src/mintwaterfall-accessibility.ts +680 -0
- package/src/mintwaterfall-advanced-data.ts +1034 -0
- package/src/mintwaterfall-advanced-interactions.ts +649 -0
- package/src/mintwaterfall-advanced-performance.ts +582 -0
- package/src/mintwaterfall-animations.ts +595 -0
- package/src/mintwaterfall-brush.ts +471 -0
- package/src/mintwaterfall-chart-core.ts +296 -0
- package/src/mintwaterfall-chart.ts +1915 -0
- package/src/mintwaterfall-data.ts +1100 -0
- package/src/mintwaterfall-export.ts +475 -0
- package/src/mintwaterfall-hierarchical-layouts.ts +724 -0
- package/src/mintwaterfall-layouts.ts +647 -0
- package/src/mintwaterfall-performance.ts +573 -0
- package/src/mintwaterfall-scales.ts +437 -0
- package/src/mintwaterfall-shapes.ts +385 -0
- package/src/mintwaterfall-statistics.ts +821 -0
- package/src/mintwaterfall-themes.ts +391 -0
- package/src/mintwaterfall-tooltip.ts +450 -0
- package/src/mintwaterfall-zoom.ts +399 -0
- package/src/types/js-modules.d.ts +25 -0
- package/src/utils/compatibility-layer.js +0 -0
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
// MintWaterfall Advanced Performance Optimization - TypeScript Version
|
|
2
|
+
// Provides high-performance features for handling large datasets with D3.js optimization
|
|
3
|
+
|
|
4
|
+
import * as d3 from 'd3';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// TYPE DEFINITIONS
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export interface QuadTreeNode {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
data: any;
|
|
14
|
+
index: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface VirtualScrollConfig {
|
|
18
|
+
containerHeight: number;
|
|
19
|
+
itemHeight: number;
|
|
20
|
+
overscan: number; // Number of items to render outside visible area
|
|
21
|
+
threshold: number; // Minimum items before virtualization kicks in
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PerformanceMetrics {
|
|
25
|
+
renderTime: number;
|
|
26
|
+
dataProcessingTime: number;
|
|
27
|
+
memoryUsage: number;
|
|
28
|
+
frameRate: number;
|
|
29
|
+
itemsRendered: number;
|
|
30
|
+
totalItems: number;
|
|
31
|
+
virtualizationActive: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SpatialIndex {
|
|
35
|
+
quadTree: d3.Quadtree<QuadTreeNode>;
|
|
36
|
+
search(x: number, y: number, radius?: number): QuadTreeNode[];
|
|
37
|
+
findNearest(x: number, y: number): QuadTreeNode | undefined;
|
|
38
|
+
add(node: QuadTreeNode): void;
|
|
39
|
+
remove(node: QuadTreeNode): void;
|
|
40
|
+
clear(): void;
|
|
41
|
+
size(): number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface VirtualScrollManager {
|
|
45
|
+
getVisibleRange(scrollTop: number): { start: number; end: number };
|
|
46
|
+
getVirtualizedData<T>(data: T[], scrollTop: number): {
|
|
47
|
+
visibleData: T[];
|
|
48
|
+
offsetY: number;
|
|
49
|
+
totalHeight: number;
|
|
50
|
+
metrics: PerformanceMetrics;
|
|
51
|
+
};
|
|
52
|
+
updateConfig(config: Partial<VirtualScrollConfig>): void;
|
|
53
|
+
destroy(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CanvasRenderer {
|
|
57
|
+
render(data: any[], scales: any): void;
|
|
58
|
+
clear(): void;
|
|
59
|
+
getCanvas(): HTMLCanvasElement;
|
|
60
|
+
setDimensions(width: number, height: number): void;
|
|
61
|
+
enableHighDPI(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AdvancedPerformanceSystem {
|
|
65
|
+
// Spatial indexing
|
|
66
|
+
createSpatialIndex(): SpatialIndex;
|
|
67
|
+
|
|
68
|
+
// Virtual scrolling
|
|
69
|
+
createVirtualScrollManager(config: VirtualScrollConfig): VirtualScrollManager;
|
|
70
|
+
|
|
71
|
+
// Canvas rendering
|
|
72
|
+
createCanvasRenderer(container: HTMLElement): CanvasRenderer;
|
|
73
|
+
|
|
74
|
+
// Performance monitoring
|
|
75
|
+
createPerformanceMonitor(): PerformanceMonitor;
|
|
76
|
+
|
|
77
|
+
// Data optimization
|
|
78
|
+
optimizeDataForRendering<T>(data: T[], maxItems?: number): T[];
|
|
79
|
+
createDataSampler<T>(strategy: 'uniform' | 'random' | 'importance'): (data: T[], count: number) => T[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface PerformanceMonitor {
|
|
83
|
+
startTiming(label: string): void;
|
|
84
|
+
endTiming(label: string): number;
|
|
85
|
+
getMetrics(): PerformanceMetrics;
|
|
86
|
+
getMemoryUsage(): number;
|
|
87
|
+
trackFrameRate(): void;
|
|
88
|
+
generateReport(): string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// SPATIAL INDEXING IMPLEMENTATION
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
function createSpatialIndexImpl(): SpatialIndex {
|
|
96
|
+
let quadTree = d3.quadtree<QuadTreeNode>()
|
|
97
|
+
.x(d => d.x)
|
|
98
|
+
.y(d => d.y);
|
|
99
|
+
|
|
100
|
+
function search(x: number, y: number, radius: number = 10): QuadTreeNode[] {
|
|
101
|
+
const results: QuadTreeNode[] = [];
|
|
102
|
+
|
|
103
|
+
quadTree.visit((node, x1, y1, x2, y2) => {
|
|
104
|
+
if (!node.length) {
|
|
105
|
+
// Leaf node
|
|
106
|
+
const leaf = node as any;
|
|
107
|
+
if (leaf.data) {
|
|
108
|
+
const distance = Math.sqrt(
|
|
109
|
+
Math.pow(leaf.data.x - x, 2) + Math.pow(leaf.data.y - y, 2)
|
|
110
|
+
);
|
|
111
|
+
if (distance <= radius) {
|
|
112
|
+
results.push(leaf.data);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Continue search if bounds intersect with search radius
|
|
117
|
+
return x1 > x + radius || y1 > y + radius || x2 < x - radius || y2 < y - radius;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return results;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function findNearest(x: number, y: number): QuadTreeNode | undefined {
|
|
124
|
+
return quadTree.find(x, y);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function add(node: QuadTreeNode): void {
|
|
128
|
+
quadTree.add(node);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function remove(node: QuadTreeNode): void {
|
|
132
|
+
quadTree.remove(node);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function clear(): void {
|
|
136
|
+
quadTree = d3.quadtree<QuadTreeNode>()
|
|
137
|
+
.x(d => d.x)
|
|
138
|
+
.y(d => d.y);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function size(): number {
|
|
142
|
+
let count = 0;
|
|
143
|
+
quadTree.visit(() => {
|
|
144
|
+
count++;
|
|
145
|
+
return false;
|
|
146
|
+
});
|
|
147
|
+
return count;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
quadTree,
|
|
152
|
+
search,
|
|
153
|
+
findNearest,
|
|
154
|
+
add,
|
|
155
|
+
remove,
|
|
156
|
+
clear,
|
|
157
|
+
size
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// VIRTUAL SCROLLING IMPLEMENTATION
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
function createVirtualScrollManagerImpl(config: VirtualScrollConfig): VirtualScrollManager {
|
|
166
|
+
let currentConfig = { ...config };
|
|
167
|
+
let lastRenderTime = 0;
|
|
168
|
+
let frameCount = 0;
|
|
169
|
+
let frameRate = 60;
|
|
170
|
+
|
|
171
|
+
function getVisibleRange(scrollTop: number): { start: number; end: number } {
|
|
172
|
+
const visibleStart = Math.floor(scrollTop / currentConfig.itemHeight);
|
|
173
|
+
const visibleEnd = Math.ceil(
|
|
174
|
+
(scrollTop + currentConfig.containerHeight) / currentConfig.itemHeight
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Add overscan
|
|
178
|
+
const start = Math.max(0, visibleStart - currentConfig.overscan);
|
|
179
|
+
const end = visibleEnd + currentConfig.overscan;
|
|
180
|
+
|
|
181
|
+
return { start, end };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getVirtualizedData<T>(data: T[], scrollTop: number): {
|
|
185
|
+
visibleData: T[];
|
|
186
|
+
offsetY: number;
|
|
187
|
+
totalHeight: number;
|
|
188
|
+
metrics: PerformanceMetrics;
|
|
189
|
+
} {
|
|
190
|
+
const startTime = performance.now();
|
|
191
|
+
|
|
192
|
+
// Check if virtualization should be active
|
|
193
|
+
const virtualizationActive = data.length >= currentConfig.threshold;
|
|
194
|
+
|
|
195
|
+
if (!virtualizationActive) {
|
|
196
|
+
const endTime = performance.now();
|
|
197
|
+
return {
|
|
198
|
+
visibleData: data,
|
|
199
|
+
offsetY: 0,
|
|
200
|
+
totalHeight: data.length * currentConfig.itemHeight,
|
|
201
|
+
metrics: {
|
|
202
|
+
renderTime: endTime - startTime,
|
|
203
|
+
dataProcessingTime: endTime - startTime,
|
|
204
|
+
memoryUsage: getMemoryUsage(),
|
|
205
|
+
frameRate,
|
|
206
|
+
itemsRendered: data.length,
|
|
207
|
+
totalItems: data.length,
|
|
208
|
+
virtualizationActive: false
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { start, end } = getVisibleRange(scrollTop);
|
|
214
|
+
const visibleData = data.slice(start, Math.min(end, data.length));
|
|
215
|
+
const offsetY = start * currentConfig.itemHeight;
|
|
216
|
+
const totalHeight = data.length * currentConfig.itemHeight;
|
|
217
|
+
|
|
218
|
+
const endTime = performance.now();
|
|
219
|
+
|
|
220
|
+
// Update frame rate calculation
|
|
221
|
+
frameCount++;
|
|
222
|
+
if (frameCount % 60 === 0) {
|
|
223
|
+
const now = performance.now();
|
|
224
|
+
if (lastRenderTime > 0) {
|
|
225
|
+
frameRate = 60000 / (now - lastRenderTime);
|
|
226
|
+
}
|
|
227
|
+
lastRenderTime = now;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
visibleData,
|
|
232
|
+
offsetY,
|
|
233
|
+
totalHeight,
|
|
234
|
+
metrics: {
|
|
235
|
+
renderTime: endTime - startTime,
|
|
236
|
+
dataProcessingTime: endTime - startTime,
|
|
237
|
+
memoryUsage: getMemoryUsage(),
|
|
238
|
+
frameRate,
|
|
239
|
+
itemsRendered: visibleData.length,
|
|
240
|
+
totalItems: data.length,
|
|
241
|
+
virtualizationActive: true
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function updateConfig(newConfig: Partial<VirtualScrollConfig>): void {
|
|
247
|
+
currentConfig = { ...currentConfig, ...newConfig };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function destroy(): void {
|
|
251
|
+
// Cleanup if needed
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
getVisibleRange,
|
|
256
|
+
getVirtualizedData,
|
|
257
|
+
updateConfig,
|
|
258
|
+
destroy
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// CANVAS RENDERING IMPLEMENTATION
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
function createCanvasRendererImpl(container: HTMLElement): CanvasRenderer {
|
|
267
|
+
const canvas = document.createElement('canvas');
|
|
268
|
+
const context = canvas.getContext('2d')!;
|
|
269
|
+
|
|
270
|
+
container.appendChild(canvas);
|
|
271
|
+
|
|
272
|
+
function render(data: any[], scales: any): void {
|
|
273
|
+
const { xScale, yScale } = scales;
|
|
274
|
+
|
|
275
|
+
// Clear canvas
|
|
276
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
277
|
+
|
|
278
|
+
// Set drawing properties for better performance
|
|
279
|
+
context.save();
|
|
280
|
+
|
|
281
|
+
// Render data points efficiently
|
|
282
|
+
data.forEach((item, index) => {
|
|
283
|
+
const x = xScale(item.label) || 0;
|
|
284
|
+
const y = yScale(item.value) || 0;
|
|
285
|
+
const width = xScale.bandwidth ? xScale.bandwidth() : 20;
|
|
286
|
+
const height = Math.abs(yScale(0) - y);
|
|
287
|
+
|
|
288
|
+
// Use fillRect for better performance than path operations
|
|
289
|
+
context.fillStyle = item.color || '#3498db';
|
|
290
|
+
context.fillRect(x, Math.min(y, yScale(0)), width, height);
|
|
291
|
+
|
|
292
|
+
// Add border if needed
|
|
293
|
+
context.strokeStyle = '#ffffff';
|
|
294
|
+
context.lineWidth = 1;
|
|
295
|
+
context.strokeRect(x, Math.min(y, yScale(0)), width, height);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
context.restore();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function clear(): void {
|
|
302
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getCanvas(): HTMLCanvasElement {
|
|
306
|
+
return canvas;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function setDimensions(width: number, height: number): void {
|
|
310
|
+
canvas.width = width;
|
|
311
|
+
canvas.height = height;
|
|
312
|
+
canvas.style.width = `${width}px`;
|
|
313
|
+
canvas.style.height = `${height}px`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function enableHighDPI(): void {
|
|
317
|
+
const dpr = window.devicePixelRatio || 1;
|
|
318
|
+
const rect = canvas.getBoundingClientRect();
|
|
319
|
+
|
|
320
|
+
canvas.width = rect.width * dpr;
|
|
321
|
+
canvas.height = rect.height * dpr;
|
|
322
|
+
canvas.style.width = `${rect.width}px`;
|
|
323
|
+
canvas.style.height = `${rect.height}px`;
|
|
324
|
+
|
|
325
|
+
context.scale(dpr, dpr);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
render,
|
|
330
|
+
clear,
|
|
331
|
+
getCanvas,
|
|
332
|
+
setDimensions,
|
|
333
|
+
enableHighDPI
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// PERFORMANCE MONITORING IMPLEMENTATION
|
|
339
|
+
// ============================================================================
|
|
340
|
+
|
|
341
|
+
function createPerformanceMonitorImpl(): PerformanceMonitor {
|
|
342
|
+
const timings: Map<string, number> = new Map();
|
|
343
|
+
const completed: Map<string, number[]> = new Map();
|
|
344
|
+
let frameCount = 0;
|
|
345
|
+
let frameStartTime = performance.now();
|
|
346
|
+
|
|
347
|
+
function startTiming(label: string): void {
|
|
348
|
+
timings.set(label, performance.now());
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function endTiming(label: string): number {
|
|
352
|
+
const startTime = timings.get(label);
|
|
353
|
+
if (!startTime) {
|
|
354
|
+
console.warn(`No start time found for timing label: ${label}`);
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const duration = performance.now() - startTime;
|
|
359
|
+
|
|
360
|
+
// Store completed timing
|
|
361
|
+
if (!completed.has(label)) {
|
|
362
|
+
completed.set(label, []);
|
|
363
|
+
}
|
|
364
|
+
completed.get(label)!.push(duration);
|
|
365
|
+
|
|
366
|
+
// Keep only last 100 measurements
|
|
367
|
+
const measurements = completed.get(label)!;
|
|
368
|
+
if (measurements.length > 100) {
|
|
369
|
+
measurements.shift();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
timings.delete(label);
|
|
373
|
+
return duration;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getMetrics(): PerformanceMetrics {
|
|
377
|
+
const renderTimes = completed.get('render') || [];
|
|
378
|
+
const processingTimes = completed.get('dataProcessing') || [];
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
renderTime: renderTimes.length > 0 ? d3.mean(renderTimes) || 0 : 0,
|
|
382
|
+
dataProcessingTime: processingTimes.length > 0 ? d3.mean(processingTimes) || 0 : 0,
|
|
383
|
+
memoryUsage: getMemoryUsage(),
|
|
384
|
+
frameRate: frameCount > 0 ? 1000 / ((performance.now() - frameStartTime) / frameCount) : 0,
|
|
385
|
+
itemsRendered: 0, // To be set by caller
|
|
386
|
+
totalItems: 0, // To be set by caller
|
|
387
|
+
virtualizationActive: false // To be set by caller
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function trackFrameRate(): void {
|
|
392
|
+
frameCount++;
|
|
393
|
+
if (frameCount === 1) {
|
|
394
|
+
frameStartTime = performance.now();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function generateReport(): string {
|
|
399
|
+
const metrics = getMetrics();
|
|
400
|
+
return `
|
|
401
|
+
Performance Report:
|
|
402
|
+
- Average Render Time: ${metrics.renderTime.toFixed(2)}ms
|
|
403
|
+
- Average Processing Time: ${metrics.dataProcessingTime.toFixed(2)}ms
|
|
404
|
+
- Memory Usage: ${metrics.memoryUsage.toFixed(2)}MB
|
|
405
|
+
- Frame Rate: ${metrics.frameRate.toFixed(1)}fps
|
|
406
|
+
- Items Rendered: ${metrics.itemsRendered}/${metrics.totalItems}
|
|
407
|
+
- Virtualization: ${metrics.virtualizationActive ? 'Active' : 'Inactive'}
|
|
408
|
+
`.trim();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
startTiming,
|
|
413
|
+
endTiming,
|
|
414
|
+
getMetrics,
|
|
415
|
+
getMemoryUsage,
|
|
416
|
+
trackFrameRate,
|
|
417
|
+
generateReport
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ============================================================================
|
|
422
|
+
// UTILITY FUNCTIONS
|
|
423
|
+
// ============================================================================
|
|
424
|
+
|
|
425
|
+
function getMemoryUsage(): number {
|
|
426
|
+
if ('memory' in performance) {
|
|
427
|
+
const memory = (performance as any).memory;
|
|
428
|
+
return memory.usedJSHeapSize / (1024 * 1024); // Convert to MB
|
|
429
|
+
}
|
|
430
|
+
return 0; // Memory API not available
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ============================================================================
|
|
434
|
+
// DATA OPTIMIZATION FUNCTIONS
|
|
435
|
+
// ============================================================================
|
|
436
|
+
|
|
437
|
+
function optimizeDataForRenderingImpl<T>(data: T[], maxItems: number = 1000): T[] {
|
|
438
|
+
if (data.length <= maxItems) {
|
|
439
|
+
return data;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Use uniform sampling to reduce data points while maintaining distribution
|
|
443
|
+
const step = Math.ceil(data.length / maxItems);
|
|
444
|
+
const optimized: T[] = [];
|
|
445
|
+
|
|
446
|
+
for (let i = 0; i < data.length; i += step) {
|
|
447
|
+
optimized.push(data[i]);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return optimized;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function createDataSamplerImpl<T>(strategy: 'uniform' | 'random' | 'importance'): (data: T[], count: number) => T[] {
|
|
454
|
+
switch (strategy) {
|
|
455
|
+
case 'uniform':
|
|
456
|
+
return (data: T[], count: number): T[] => {
|
|
457
|
+
if (count >= data.length) return data;
|
|
458
|
+
const step = data.length / count;
|
|
459
|
+
const result: T[] = [];
|
|
460
|
+
for (let i = 0; i < count; i++) {
|
|
461
|
+
const index = Math.floor(i * step);
|
|
462
|
+
result.push(data[index]);
|
|
463
|
+
}
|
|
464
|
+
return result;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
case 'random':
|
|
468
|
+
return (data: T[], count: number): T[] => {
|
|
469
|
+
if (count >= data.length) return data;
|
|
470
|
+
const shuffled = [...data].sort(() => 0.5 - Math.random());
|
|
471
|
+
return shuffled.slice(0, count);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
case 'importance':
|
|
475
|
+
return (data: T[], count: number): T[] => {
|
|
476
|
+
if (count >= data.length) return data;
|
|
477
|
+
// For importance sampling, we'd need a way to assess importance
|
|
478
|
+
// For now, take first and last items plus uniform sampling in between
|
|
479
|
+
const result: T[] = [data[0]]; // Always include first
|
|
480
|
+
|
|
481
|
+
if (count > 2) {
|
|
482
|
+
const middle = createDataSamplerImpl<T>('uniform')(
|
|
483
|
+
data.slice(1, -1),
|
|
484
|
+
count - 2
|
|
485
|
+
);
|
|
486
|
+
result.push(...middle);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (count > 1) {
|
|
490
|
+
result.push(data[data.length - 1]); // Always include last
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return result;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
default:
|
|
497
|
+
throw new Error(`Unknown sampling strategy: ${strategy}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ============================================================================
|
|
502
|
+
// MAIN SYSTEM IMPLEMENTATION
|
|
503
|
+
// ============================================================================
|
|
504
|
+
|
|
505
|
+
export function createAdvancedPerformanceSystem(): AdvancedPerformanceSystem {
|
|
506
|
+
return {
|
|
507
|
+
createSpatialIndex: createSpatialIndexImpl,
|
|
508
|
+
createVirtualScrollManager: createVirtualScrollManagerImpl,
|
|
509
|
+
createCanvasRenderer: createCanvasRendererImpl,
|
|
510
|
+
createPerformanceMonitor: createPerformanceMonitorImpl,
|
|
511
|
+
optimizeDataForRendering: optimizeDataForRenderingImpl,
|
|
512
|
+
createDataSampler: createDataSamplerImpl
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// WATERFALL-SPECIFIC PERFORMANCE UTILITIES
|
|
518
|
+
// ============================================================================
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Create optimized spatial index for waterfall chart interactions
|
|
522
|
+
* Enables O(log n) hover detection for large datasets
|
|
523
|
+
*/
|
|
524
|
+
export function createWaterfallSpatialIndex(
|
|
525
|
+
data: Array<{label: string, value: number}>,
|
|
526
|
+
xScale: any,
|
|
527
|
+
yScale: any
|
|
528
|
+
): SpatialIndex {
|
|
529
|
+
const spatialIndex = createSpatialIndexImpl();
|
|
530
|
+
|
|
531
|
+
data.forEach((item, index) => {
|
|
532
|
+
const x = (xScale(item.label) || 0) + (xScale.bandwidth ? xScale.bandwidth() / 2 : 0);
|
|
533
|
+
const y = yScale(item.value) || 0;
|
|
534
|
+
|
|
535
|
+
spatialIndex.add({
|
|
536
|
+
x,
|
|
537
|
+
y,
|
|
538
|
+
data: item,
|
|
539
|
+
index
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
return spatialIndex;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Create high-performance virtual waterfall renderer
|
|
548
|
+
* Handles thousands of data points with smooth scrolling
|
|
549
|
+
*/
|
|
550
|
+
export function createVirtualWaterfallRenderer(
|
|
551
|
+
container: HTMLElement,
|
|
552
|
+
config: VirtualScrollConfig
|
|
553
|
+
): {
|
|
554
|
+
virtualScrollManager: VirtualScrollManager;
|
|
555
|
+
performanceMonitor: PerformanceMonitor;
|
|
556
|
+
render: (data: any[], scrollTop: number) => void;
|
|
557
|
+
} {
|
|
558
|
+
const system = createAdvancedPerformanceSystem();
|
|
559
|
+
const virtualScrollManager = system.createVirtualScrollManager(config);
|
|
560
|
+
const performanceMonitor = system.createPerformanceMonitor();
|
|
561
|
+
|
|
562
|
+
function render(data: any[], scrollTop: number): void {
|
|
563
|
+
performanceMonitor.startTiming('render');
|
|
564
|
+
|
|
565
|
+
const virtualized = virtualScrollManager.getVirtualizedData(data, scrollTop);
|
|
566
|
+
|
|
567
|
+
// Here you would render only the visible data
|
|
568
|
+
// This is a simplified version - in practice, you'd integrate with D3.js rendering
|
|
569
|
+
|
|
570
|
+
performanceMonitor.endTiming('render');
|
|
571
|
+
performanceMonitor.trackFrameRate();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
virtualScrollManager,
|
|
576
|
+
performanceMonitor,
|
|
577
|
+
render
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Default export for convenience
|
|
582
|
+
export default createAdvancedPerformanceSystem;
|