energy-visualization-sankey 1.0.0

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 (79) hide show
  1. package/README.md +497 -0
  2. package/babel.config.cjs +28 -0
  3. package/coverage/clover.xml +6 -0
  4. package/coverage/coverage-final.json +1 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/favicon.png +0 -0
  8. package/coverage/lcov-report/index.html +101 -0
  9. package/coverage/lcov-report/prettify.css +1 -0
  10. package/coverage/lcov-report/prettify.js +2 -0
  11. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  12. package/coverage/lcov-report/sorter.js +210 -0
  13. package/coverage/lcov.info +0 -0
  14. package/demo-caching.js +68 -0
  15. package/dist/core/Sankey.d.ts +294 -0
  16. package/dist/core/Sankey.d.ts.map +1 -0
  17. package/dist/core/events/EventBus.d.ts +195 -0
  18. package/dist/core/events/EventBus.d.ts.map +1 -0
  19. package/dist/core/types/events.d.ts +42 -0
  20. package/dist/core/types/events.d.ts.map +1 -0
  21. package/dist/index.d.ts +19 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/sankey.esm.js +5212 -0
  24. package/dist/sankey.esm.js.map +1 -0
  25. package/dist/sankey.standalone.esm.js +9111 -0
  26. package/dist/sankey.standalone.esm.js.map +1 -0
  27. package/dist/sankey.standalone.min.js +2 -0
  28. package/dist/sankey.standalone.min.js.map +1 -0
  29. package/dist/sankey.standalone.umd.js +9119 -0
  30. package/dist/sankey.standalone.umd.js.map +1 -0
  31. package/dist/sankey.umd.js +5237 -0
  32. package/dist/sankey.umd.js.map +1 -0
  33. package/dist/sankey.umd.min.js +2 -0
  34. package/dist/sankey.umd.min.js.map +1 -0
  35. package/dist/services/AnimationService.d.ts +229 -0
  36. package/dist/services/AnimationService.d.ts.map +1 -0
  37. package/dist/services/ConfigurationService.d.ts +173 -0
  38. package/dist/services/ConfigurationService.d.ts.map +1 -0
  39. package/dist/services/InteractionService.d.ts +377 -0
  40. package/dist/services/InteractionService.d.ts.map +1 -0
  41. package/dist/services/RenderingService.d.ts +152 -0
  42. package/dist/services/RenderingService.d.ts.map +1 -0
  43. package/dist/services/calculation/GraphService.d.ts +111 -0
  44. package/dist/services/calculation/GraphService.d.ts.map +1 -0
  45. package/dist/services/calculation/SummaryService.d.ts +149 -0
  46. package/dist/services/calculation/SummaryService.d.ts.map +1 -0
  47. package/dist/services/data/DataService.d.ts +167 -0
  48. package/dist/services/data/DataService.d.ts.map +1 -0
  49. package/dist/services/data/DataValidationService.d.ts +48 -0
  50. package/dist/services/data/DataValidationService.d.ts.map +1 -0
  51. package/dist/types/index.d.ts +189 -0
  52. package/dist/types/index.d.ts.map +1 -0
  53. package/dist/utils/Logger.d.ts +88 -0
  54. package/dist/utils/Logger.d.ts.map +1 -0
  55. package/jest.config.cjs +20 -0
  56. package/package.json +68 -0
  57. package/rollup.config.js +131 -0
  58. package/scripts/performance-validation-real.js +411 -0
  59. package/scripts/validate-optimization.sh +147 -0
  60. package/scripts/visual-validation-real-data.js +374 -0
  61. package/src/core/Sankey.ts +1039 -0
  62. package/src/core/events/EventBus.ts +488 -0
  63. package/src/core/types/events.ts +80 -0
  64. package/src/index.ts +35 -0
  65. package/src/services/AnimationService.ts +983 -0
  66. package/src/services/ConfigurationService.ts +497 -0
  67. package/src/services/InteractionService.ts +920 -0
  68. package/src/services/RenderingService.ts +484 -0
  69. package/src/services/calculation/GraphService.ts +616 -0
  70. package/src/services/calculation/SummaryService.ts +394 -0
  71. package/src/services/data/DataService.ts +380 -0
  72. package/src/services/data/DataValidationService.ts +155 -0
  73. package/src/styles/controls.css +184 -0
  74. package/src/styles/sankey.css +211 -0
  75. package/src/types/index.ts +220 -0
  76. package/src/utils/Logger.ts +105 -0
  77. package/tests/numerical-validation.test.js +575 -0
  78. package/tests/setup.js +53 -0
  79. package/tsconfig.json +54 -0
@@ -0,0 +1,488 @@
1
+ import type {EventHandler, EventSubscription, SankeyEvent, SankeyEventType} from '@/core/types/events';
2
+ import {Logger} from "@/utils/Logger";
3
+
4
+ /**
5
+ * Event bus statistics for debugging and monitoring
6
+ */
7
+ export interface EventBusStats {
8
+ readonly totalSubscriptions: number;
9
+ readonly subscriptionsByType: Record<SankeyEventType, number>;
10
+ readonly totalEventsEmitted: number;
11
+ readonly eventsByType: Record<string, number>;
12
+ readonly averageHandlerTime: number;
13
+ readonly errorCount: number;
14
+ }
15
+
16
+ /**
17
+ * Event Bus
18
+ *
19
+ * High-performance, type-safe event system enabling clean service communication.
20
+ * Provides asynchronous event dispatch, error isolation, and performance monitoring.
21
+ *
22
+ * Architecture Features:
23
+ * - Type-safe event handling with comprehensive interfaces
24
+ * - Asynchronous dispatch preventing caller blocking
25
+ * - Error isolation ensuring single handler failures don't cascade
26
+ * - Performance monitoring with execution time tracking
27
+ * - Memory leak prevention through proper subscription cleanup
28
+ * - Development-mode debugging with detailed error context
29
+ *
30
+ * Usage:
31
+ * ```typescript
32
+ * const eventBus = new EventBus();
33
+ * const subscription = eventBus.subscribe('year.changed', (event) => {
34
+ * console.log(`Year changed to ${event.data.year}`);
35
+ * });
36
+ * eventBus.emit({ type: 'year.changed', data: { year: 2021 }, ... });
37
+ * eventBus.unsubscribe(subscription);
38
+ * ```
39
+ *
40
+ */
41
+ export class EventBus {
42
+
43
+ constructor(private logger: Logger) {
44
+ }
45
+
46
+ // HANDLER STORAGE OPTIMIZATION:
47
+ // Map<EventType, Set<Handler>> provides O(1) event type lookups
48
+ // Set<Handler> provides O(1) handler deduplication and removal
49
+ // This dual-structure design optimizes both subscription and dispatch performance
50
+ private handlers = new Map<SankeyEventType, Set<EventHandler<any>>>();
51
+
52
+ // SUBSCRIPTION LIFECYCLE MANAGEMENT:
53
+ // Map<SubscriptionId, Subscription> enables fast subscription lookup for cleanup
54
+ // Each subscription gets a unique ID to prevent memory leaks from orphaned references
55
+ // Critical for proper resource management in long-running applications
56
+ private subscriptions = new Map<string, EventSubscription>();
57
+
58
+ // PERFORMANCE METRICS COLLECTION:
59
+ // Tracks comprehensive statistics for debugging and optimization
60
+ // All counters are updated atomically to prevent race conditions
61
+ // Statistics are immutable when returned via getStats() to prevent external mutation
62
+ private stats = {
63
+ totalSubscriptions: 0, // Current active subscription count
64
+ subscriptionsByType: {} as Record<SankeyEventType, number>, // Per-event-type subscription counts
65
+ totalEventsEmitted: 0, // Lifetime event emission count
66
+ eventsByType: {} as Record<string, number>, // Per-event-type emission counts
67
+ averageHandlerTime: 0, // Computed from handlerTimes array
68
+ errorCount: 0 // Total handler error count
69
+ };
70
+
71
+ // ROLLING PERFORMANCE WINDOW:
72
+ // Maintains last N handler execution times for performance analysis
73
+ // Prevents unbounded memory growth while preserving recent performance data
74
+ // Used for calculating rolling averages and identifying performance regressions
75
+ private handlerTimes: number[] = [];
76
+ private readonly MAX_HANDLER_TIMES = 100; // Optimal balance: sufficient data, bounded memory
77
+
78
+ /**
79
+ * Emit an event to all subscribers with asynchronous dispatch
80
+ *
81
+ * ASYNC DISPATCH PATTERN:
82
+ * Uses Promise.resolve().then() to break out of the current execution context,
83
+ * ensuring the emit() call returns immediately and doesn't block the caller.
84
+ * This prevents stack overflow in recursive event scenarios and maintains
85
+ * responsive UI during heavy event processing.
86
+ *
87
+ * ERROR ISOLATION STRATEGY:
88
+ * Each handler executes in its own try-catch block, preventing handler
89
+ * failures from affecting other handlers or the event system itself.
90
+ * Async handler errors are caught via promise rejection handling.
91
+ *
92
+ * PERFORMANCE MONITORING:
93
+ * Tracks individual handler execution times and aggregate dispatch metrics.
94
+ * Uses high-resolution performance.now() for microsecond accuracy.
95
+ * Maintains rolling averages to prevent unbounded memory growth.
96
+ */
97
+ emit<T>(event: SankeyEvent<T>): void {
98
+ const eventHandlers = this.handlers.get(event.type);
99
+
100
+ // Fast path: no handlers registered for this event type
101
+ if (!eventHandlers || eventHandlers.size === 0) {
102
+ return;
103
+ }
104
+
105
+ // Update emission statistics BEFORE dispatch
106
+ // This ensures accurate counting even if handlers throw errors
107
+ this.stats.totalEventsEmitted++;
108
+ this.stats.eventsByType[event.type] = (this.stats.eventsByType[event.type] || 0) + 1;
109
+
110
+ // CRITICAL: Promise.resolve().then() creates async boundary
111
+ // This ensures emit() returns immediately, preventing:
112
+ // 1. Stack overflow from recursive event chains
113
+ // 2. UI blocking during handler execution
114
+ // 3. Caller dependency on handler completion timing
115
+ Promise.resolve().then(() => {
116
+ const dispatchStartTime = performance.now();
117
+ let handlerCount = 0;
118
+ let errorCount = 0;
119
+
120
+ // Process each handler with individual error isolation
121
+ eventHandlers.forEach(handler => {
122
+ try {
123
+ const handlerStartTime = performance.now();
124
+
125
+ // Execute handler - may return void, Promise<void>, or throw
126
+ const result = handler(event);
127
+
128
+ // ASYNC HANDLER SUPPORT:
129
+ // If handler returns a Promise, attach error handling
130
+ // This catches async errors that occur after handler returns
131
+ if (result && typeof result.catch === 'function') {
132
+ (result as Promise<void>).catch(error => {
133
+ this.handleError(error, event, handler);
134
+ });
135
+ }
136
+
137
+ // Track individual handler performance
138
+ // Used for identifying slow handlers during optimization
139
+ const handlerExecutionTime = performance.now() - handlerStartTime;
140
+ this.recordHandlerTime(handlerExecutionTime);
141
+ handlerCount++;
142
+
143
+ } catch (error) {
144
+ // Synchronous error handling
145
+ // Prevents one bad handler from affecting others
146
+ this.handleError(error, event, handler);
147
+ errorCount++;
148
+ }
149
+ });
150
+
151
+ // Log dispatch completion with performance metrics
152
+ const totalDispatchTime = performance.now() - dispatchStartTime;
153
+ this.logger.log(`EventBus: ${event.type} → ${handlerCount} handlers (${errorCount} errors) in ${totalDispatchTime.toFixed(2)}ms`);
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Subscribe to specific event types with automatic deduplication
159
+ *
160
+ * SUBSCRIPTION LIFECYCLE:
161
+ * 1. Handler storage: Lazy-initialized Set prevents duplicate handlers
162
+ * 2. Subscription record: Unique ID enables precise cleanup without reference leaks
163
+ * 3. Statistics tracking: Real-time metrics for debugging and monitoring
164
+ * 4. Memory safety: All data structures designed for leak-free cleanup
165
+ *
166
+ * PERFORMANCE CHARACTERISTICS:
167
+ * - Handler lookup: O(1) via Map<EventType, Set<Handler>>
168
+ * - Duplicate prevention: O(1) via Set.add() deduplication
169
+ * - Subscription tracking: O(1) via Map<SubscriptionId, Record>
170
+ * - Memory overhead: ~200 bytes per subscription (ID + metadata)
171
+ */
172
+ subscribe<T>(eventType: SankeyEventType, handler: EventHandler<T>): EventSubscription {
173
+ // LAZY INITIALIZATION: Only create handler set when first subscriber arrives
174
+ // Prevents memory allocation for unused event types
175
+ if (!this.handlers.has(eventType)) {
176
+ this.handlers.set(eventType, new Set());
177
+ }
178
+
179
+ // AUTOMATIC DEDUPLICATION: Set.add() naturally prevents duplicate handlers
180
+ // Same handler function can only be registered once per event type
181
+ // Critical for preventing duplicate event processing in service composition
182
+ this.handlers.get(eventType)!.add(handler);
183
+
184
+ // UNIQUE SUBSCRIPTION RECORD: Each subscription gets unique ID
185
+ // Enables precise cleanup without requiring handler reference retention
186
+ // Prevents memory leaks from orphaned subscription references
187
+ const subscription: EventSubscription = {
188
+ id: this.generateSubscriptionId(), // Cryptographically unique identifier
189
+ eventType, // Event type for targeted cleanup
190
+ handler, // Handler function reference
191
+ createdAt: Date.now() // Timestamp for debugging/analysis
192
+ };
193
+
194
+ // DUAL TRACKING SYSTEM: Both by ID and by handler reference
195
+ // ID mapping enables fast subscription cleanup by consumers
196
+ // Handler mapping enables fast event dispatch by event type
197
+ this.subscriptions.set(subscription.id, subscription);
198
+
199
+ // REAL-TIME STATISTICS: Updated immediately for monitoring
200
+ // Atomic updates prevent race conditions in statistics
201
+ this.stats.totalSubscriptions++;
202
+ this.stats.subscriptionsByType[eventType] = (this.stats.subscriptionsByType[eventType] || 0) + 1;
203
+
204
+ this.logger.debug(`EventBus: Subscribed to ${eventType} (${subscription.id}) - ${this.handlers.get(eventType)!.size} total handlers`);
205
+
206
+ return subscription;
207
+ }
208
+
209
+ /**
210
+ * Unsubscribe from events with comprehensive cleanup
211
+ *
212
+ * MEMORY LEAK PREVENTION STRATEGY:
213
+ * 1. Handler removal: Deletes specific handler from event type set
214
+ * 2. Empty set cleanup: Removes entire event type mapping when no handlers remain
215
+ * 3. Subscription cleanup: Removes subscription record by unique ID
216
+ * 4. Statistics maintenance: Atomically updates counters with bounds checking
217
+ *
218
+ * CLEANUP SAFETY:
219
+ * - Idempotent: Safe to call multiple times with same subscription
220
+ * - Graceful degradation: Handles missing handlers/subscriptions without errors
221
+ * - Statistics integrity: Prevents negative counts via Math.max() bounds
222
+ * - Memory optimization: Eagerly frees unused Map entries
223
+ */
224
+ unsubscribe(subscription: EventSubscription): void {
225
+ const handlers = this.handlers.get(subscription.eventType);
226
+
227
+ if (handlers) {
228
+ // PRECISE HANDLER REMOVAL: Delete specific handler by reference
229
+ // Set.delete() is O(1) and safe to call on non-existent elements
230
+ handlers.delete(subscription.handler);
231
+
232
+ // MEMORY OPTIMIZATION: Remove empty handler sets immediately
233
+ // Prevents accumulation of empty Map entries over application lifetime
234
+ // Critical for long-running applications with dynamic subscriptions
235
+ if (handlers.size === 0) {
236
+ this.handlers.delete(subscription.eventType);
237
+ this.logger.debug(`EventBus: Removed empty handler set for ${subscription.eventType}`);
238
+ }
239
+ }
240
+
241
+ // SUBSCRIPTION RECORD CLEANUP: Remove by unique ID
242
+ // ID-based removal prevents accidental cleanup of similar subscriptions
243
+ // Safe to call on non-existent subscriptions (Map.delete returns boolean)
244
+ const wasSubscribed = this.subscriptions.delete(subscription.id);
245
+
246
+ // ATOMIC STATISTICS UPDATE: Maintain accurate counters
247
+ // Math.max() prevents negative counts from double-unsubscribe scenarios
248
+ // All updates are atomic to prevent race conditions in statistics
249
+ if (wasSubscribed) {
250
+ this.stats.totalSubscriptions = Math.max(0, this.stats.totalSubscriptions - 1);
251
+ const currentTypeCount = this.stats.subscriptionsByType[subscription.eventType] || 0;
252
+ this.stats.subscriptionsByType[subscription.eventType] = Math.max(0, currentTypeCount - 1);
253
+
254
+ const remainingHandlers = this.handlers.get(subscription.eventType)?.size || 0;
255
+ this.logger.debug(`EventBus: Unsubscribed from ${subscription.eventType} (${subscription.id}) - ${remainingHandlers} handlers remain`);
256
+ } else {
257
+ this.logger.warn(`EventBus: Attempted to unsubscribe non-existent subscription ${subscription.id}`);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Get current event bus statistics
263
+ * Useful for debugging and performance monitoring
264
+ */
265
+ getStats(): EventBusStats {
266
+ return {
267
+ ...this.stats,
268
+ averageHandlerTime: this.calculateAverageHandlerTime(),
269
+ // Create copies to prevent mutation
270
+ subscriptionsByType: {...this.stats.subscriptionsByType},
271
+ eventsByType: {...this.stats.eventsByType}
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Clear all subscriptions
277
+ * Important for cleanup to prevent memory leaks
278
+ */
279
+ clear(): void {
280
+ const subscriptionCount = this.subscriptions.size;
281
+ const handlerTypeCount = this.handlers.size;
282
+
283
+ this.handlers.clear();
284
+ this.subscriptions.clear();
285
+
286
+ // Reset statistics
287
+ this.stats = {
288
+ totalSubscriptions: 0,
289
+ subscriptionsByType: {} as Record<SankeyEventType, number>,
290
+ totalEventsEmitted: 0,
291
+ eventsByType: {},
292
+ averageHandlerTime: 0,
293
+ errorCount: 0
294
+ };
295
+
296
+ this.handlerTimes = [];
297
+
298
+ this.logger.debug(`EventBus: Cleared ${subscriptionCount} subscriptions across ${handlerTypeCount} event types`);
299
+ }
300
+
301
+ /**
302
+ * Generate unique subscription ID
303
+ */
304
+ private generateSubscriptionId(): string {
305
+ return `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
306
+ }
307
+
308
+ /**
309
+ * Handle errors in event handlers with comprehensive error isolation
310
+ *
311
+ * ERROR ISOLATION PRINCIPLES:
312
+ * 1. Statistics tracking: Increment error count for monitoring
313
+ * 2. Detailed logging: Capture error context for debugging
314
+ * 3. Stack preservation: Include stack trace for error analysis
315
+ * 4. Handler identification: Log handler source for debugging
316
+ * 5. Cascade prevention: Error never bubbles up to caller
317
+ *
318
+ * DEBUGGING INFORMATION CAPTURE:
319
+ * - Error message and type
320
+ * - Event context (type, source, timestamp)
321
+ * - Handler function preview (first 100 chars)
322
+ * - Full stack trace when available
323
+ * - Performance timing context
324
+ *
325
+ * PRODUCTION SAFETY:
326
+ * - Never throws exceptions (pure error logging)
327
+ * - Preserves application stability during handler failures
328
+ * - Maintains event system availability during error scenarios
329
+ */
330
+ private handleError(error: any, event: SankeyEvent<any>, handler: EventHandler<any>): void {
331
+ // ATOMIC ERROR COUNTING: Thread-safe increment for monitoring
332
+ this.stats.errorCount++;
333
+
334
+ // COMPREHENSIVE ERROR CONTEXT: Capture all relevant debugging information
335
+ const errorContext = {
336
+ // Error details
337
+ error: error instanceof Error ? error.message : String(error),
338
+ errorType: error instanceof Error ? error.constructor.name : typeof error,
339
+ stack: error instanceof Error ? error.stack : undefined,
340
+
341
+ // Event context
342
+ eventType: event.type,
343
+ eventSource: event.source,
344
+ eventTimestamp: event.timestamp,
345
+
346
+ // Handler context
347
+ handlerPreview: handler.toString().substring(0, 100) + '...',
348
+ handlerLength: handler.toString().length,
349
+
350
+ // System context
351
+ totalErrors: this.stats.errorCount,
352
+ totalEvents: this.stats.totalEventsEmitted,
353
+ errorRate: this.stats.totalEventsEmitted > 0
354
+ ? (this.stats.errorCount / this.stats.totalEventsEmitted * 100).toFixed(2) + '%'
355
+ : '0%'
356
+ };
357
+
358
+ // STRUCTURED ERROR LOGGING: Use console.error for visibility in production
359
+ console.error(`EventBus: Handler error in ${event.type} (error #${this.stats.errorCount}):`, errorContext);
360
+
361
+ // HANDLER IDENTIFICATION: Log handler source for debugging
362
+ // Truncated to prevent log spam from very large handler functions
363
+ this.logger.warn(`EventBus: Failing handler source preview:`, errorContext.handlerPreview);
364
+
365
+ // PERFORMANCE IMPACT LOGGING: Track if errors are affecting performance
366
+ if (this.stats.errorCount > 0 && this.stats.errorCount % 10 === 0) {
367
+ this.logger.warn(`EventBus: Error count reached ${this.stats.errorCount} - consider investigating handler stability`);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Record handler execution time using rolling window algorithm
373
+ *
374
+ * ROLLING WINDOW PERFORMANCE TRACKING:
375
+ * Maintains a bounded circular buffer of recent handler execution times
376
+ * for statistical analysis without unbounded memory growth.
377
+ *
378
+ * ALGORITHM PROPERTIES:
379
+ * - Time complexity: O(1) for insertion, O(1) amortized for window maintenance
380
+ * - Space complexity: O(MAX_HANDLER_TIMES) = O(100) = constant
381
+ * - Statistical accuracy: Based on last 100 measurements
382
+ * - Memory safety: Automatic buffer rotation prevents memory leaks
383
+ *
384
+ * PERFORMANCE INSIGHTS:
385
+ * - Captures recent performance trends vs. lifetime averages
386
+ * - Enables detection of performance regressions
387
+ * - Filters out historical performance from earlier application states
388
+ * - Provides statistically significant sample size for analysis
389
+ */
390
+ private recordHandlerTime(time: number): void {
391
+ // BOUNDED BUFFER APPEND: Add new measurement to rolling window
392
+ this.handlerTimes.push(time);
393
+
394
+ // AUTOMATIC WINDOW ROTATION: Maintain fixed buffer size
395
+ // Array.shift() removes oldest measurement when buffer exceeds limit
396
+ // This creates a FIFO (First In, First Out) circular buffer behavior
397
+ // Memory complexity remains constant regardless of application lifetime
398
+ if (this.handlerTimes.length > this.MAX_HANDLER_TIMES) {
399
+ this.handlerTimes.shift(); // O(n) operation, but bounded by MAX_HANDLER_TIMES
400
+ }
401
+
402
+ // PERFORMANCE ANOMALY DETECTION: Log unusually slow handlers
403
+ // Threshold: 10ms (significant for UI responsiveness)
404
+ if (time > 10) {
405
+ this.logger.warn(`EventBus: Slow handler detected: ${time.toFixed(2)}ms execution time`);
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Calculate rolling average handler execution time
411
+ *
412
+ * STATISTICAL CALCULATION:
413
+ * Computes arithmetic mean of recent handler execution times using
414
+ * the rolling window buffer for temporally-relevant performance metrics.
415
+ *
416
+ * MATHEMATICAL PROPERTIES:
417
+ * - Formula: μ = (Σ times) / n, where n = sample count
418
+ * - Sample size: min(total_measurements, MAX_HANDLER_TIMES)
419
+ * - Precision: Floating point precision of performance.now()
420
+ * - Accuracy: Based on high-resolution performance timer
421
+ *
422
+ * PERFORMANCE CHARACTERISTICS:
423
+ * - Time complexity: O(n) where n ≤ MAX_HANDLER_TIMES
424
+ * - Space complexity: O(1) additional memory
425
+ * - Numerical stability: Uses reduce() for precision
426
+ */
427
+ private calculateAverageHandlerTime(): number {
428
+ // EDGE CASE: Handle empty buffer gracefully
429
+ if (this.handlerTimes.length === 0) {
430
+ return 0;
431
+ }
432
+
433
+ // ARITHMETIC MEAN CALCULATION: Sum all measurements and divide by count
434
+ // Using reduce() for numerical stability and functional programming clarity
435
+ const sum = this.handlerTimes.reduce((accumulator, currentTime) => accumulator + currentTime, 0);
436
+ const average = sum / this.handlerTimes.length;
437
+
438
+ // PRECISION OPTIMIZATION: Round to 3 decimal places for performance metrics
439
+ // Balances precision with readability for monitoring dashboards
440
+ return Math.round(average * 1000) / 1000;
441
+ }
442
+
443
+ /**
444
+ * Get debug information about current subscriptions
445
+ * Useful for development and debugging
446
+ */
447
+ getDebugInfo(): {
448
+ activeSubscriptions: Array<{
449
+ id: string;
450
+ eventType: SankeyEventType;
451
+ createdAt: number;
452
+ age: number;
453
+ }>;
454
+ handlerCounts: Record<SankeyEventType, number>;
455
+ recentPerformance: {
456
+ averageHandlerTime: number;
457
+ recentHandlerTimes: number[];
458
+ totalEvents: number;
459
+ errorRate: number;
460
+ };
461
+ } {
462
+ const now = Date.now();
463
+ const activeSubscriptions = Array.from(this.subscriptions.values()).map(sub => ({
464
+ id: sub.id,
465
+ eventType: sub.eventType,
466
+ createdAt: sub.createdAt,
467
+ age: now - sub.createdAt
468
+ }));
469
+
470
+ const handlerCounts: Record<string, number> = {};
471
+ this.handlers.forEach((handlerSet, eventType) => {
472
+ handlerCounts[eventType] = handlerSet.size;
473
+ });
474
+
475
+ return {
476
+ activeSubscriptions,
477
+ handlerCounts: handlerCounts as Record<SankeyEventType, number>,
478
+ recentPerformance: {
479
+ averageHandlerTime: this.calculateAverageHandlerTime(),
480
+ recentHandlerTimes: [...this.handlerTimes],
481
+ totalEvents: this.stats.totalEventsEmitted,
482
+ errorRate: this.stats.totalEventsEmitted > 0
483
+ ? this.stats.errorCount / this.stats.totalEventsEmitted
484
+ : 0
485
+ }
486
+ };
487
+ }
488
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Core Event Type Definitions
3
+ *
4
+ * Comprehensive type-safe event system enabling clean service communication.
5
+ * Defines all event types, data structures, and handler interfaces used
6
+ * throughout the visualization system.
7
+ *
8
+ * Event System Architecture:
9
+ * - 20+ typed event channels for service coordination
10
+ * - Asynchronous event dispatch with error isolation
11
+ * - Performance monitoring and debugging support
12
+ * - Memory leak prevention with proper cleanup
13
+ *
14
+ */
15
+
16
+ /**
17
+ * All possible event types in the Sankey system
18
+ * Each event represents a specific state change or action
19
+ */
20
+ export type SankeyEventType =
21
+ // Data lifecycle events
22
+ | 'data.loaded'
23
+ | 'data.validated'
24
+
25
+ // Calculation events
26
+ | 'calculation.completed'
27
+
28
+ // Navigation events
29
+ | 'year.changing'
30
+ | 'year.changed'
31
+ | 'timeline.updated'
32
+
33
+ // Animation events
34
+ | 'animation.started'
35
+ | 'animation.stopped'
36
+ | 'speed.changed'
37
+
38
+ // Rendering events
39
+ | 'rendering.started'
40
+ | 'rendering.completed'
41
+
42
+ // Interaction events
43
+ | 'interaction.hover'
44
+ | 'interaction.click'
45
+ | 'interaction.keypress'
46
+ | 'interaction.slider'
47
+ | 'interaction.button'
48
+
49
+ // System events
50
+ | 'system.initialized'
51
+ | 'system.ready'
52
+ | 'system.error'
53
+
54
+ // Dimension events
55
+ | 'dimensions.changed';
56
+
57
+ /**
58
+ * Base event interface - all events extend this
59
+ */
60
+ export interface SankeyEvent<T = any> {
61
+ readonly type: SankeyEventType;
62
+ readonly timestamp: number;
63
+ readonly source: string;
64
+ readonly data: T;
65
+ }
66
+
67
+ /**
68
+ * Event handler type definition
69
+ */
70
+ export type EventHandler<T = any> = (event: SankeyEvent<T>) => void | Promise<void>;
71
+
72
+ /**
73
+ * Event subscription interface
74
+ */
75
+ export interface EventSubscription {
76
+ readonly id: string;
77
+ readonly eventType: SankeyEventType;
78
+ readonly handler: EventHandler<any>;
79
+ readonly createdAt: number;
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Energy Sankey
3
+ *
4
+ * Modern TypeScript energy visualization library with event-driven architecture.
5
+ * Provides interactive Sankey diagrams for energy consumption.
6
+ *
7
+ * Key Features:
8
+ * - Event-driven service communication with type safety
9
+ * - High-performance caching (4-layer optimization)
10
+ * - Complete mathematical accuracy preservation
11
+ * - Responsive design with mobile optimization
12
+ * - Comprehensive accessibility support
13
+ *
14
+ * @author Research Computing Center (RCC), University of Chicago
15
+ */
16
+
17
+ // Export the main visualization class
18
+ export {default} from '@/core/Sankey';
19
+
20
+ // Export public type definitions for TypeScript consumers
21
+ export type {
22
+ SankeyOptions,
23
+ EnergyDataPoint,
24
+ SummaryData,
25
+ GraphData,
26
+ YearTotals,
27
+ BoxMaxes,
28
+ BoxTops
29
+ } from '@/types/index';
30
+
31
+ // Export error handling types
32
+ export {
33
+ SankeyError,
34
+ DataValidationError
35
+ } from '@/types/index';