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.
- package/README.md +497 -0
- package/babel.config.cjs +28 -0
- package/coverage/clover.xml +6 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +101 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +0 -0
- package/demo-caching.js +68 -0
- package/dist/core/Sankey.d.ts +294 -0
- package/dist/core/Sankey.d.ts.map +1 -0
- package/dist/core/events/EventBus.d.ts +195 -0
- package/dist/core/events/EventBus.d.ts.map +1 -0
- package/dist/core/types/events.d.ts +42 -0
- package/dist/core/types/events.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/sankey.esm.js +5212 -0
- package/dist/sankey.esm.js.map +1 -0
- package/dist/sankey.standalone.esm.js +9111 -0
- package/dist/sankey.standalone.esm.js.map +1 -0
- package/dist/sankey.standalone.min.js +2 -0
- package/dist/sankey.standalone.min.js.map +1 -0
- package/dist/sankey.standalone.umd.js +9119 -0
- package/dist/sankey.standalone.umd.js.map +1 -0
- package/dist/sankey.umd.js +5237 -0
- package/dist/sankey.umd.js.map +1 -0
- package/dist/sankey.umd.min.js +2 -0
- package/dist/sankey.umd.min.js.map +1 -0
- package/dist/services/AnimationService.d.ts +229 -0
- package/dist/services/AnimationService.d.ts.map +1 -0
- package/dist/services/ConfigurationService.d.ts +173 -0
- package/dist/services/ConfigurationService.d.ts.map +1 -0
- package/dist/services/InteractionService.d.ts +377 -0
- package/dist/services/InteractionService.d.ts.map +1 -0
- package/dist/services/RenderingService.d.ts +152 -0
- package/dist/services/RenderingService.d.ts.map +1 -0
- package/dist/services/calculation/GraphService.d.ts +111 -0
- package/dist/services/calculation/GraphService.d.ts.map +1 -0
- package/dist/services/calculation/SummaryService.d.ts +149 -0
- package/dist/services/calculation/SummaryService.d.ts.map +1 -0
- package/dist/services/data/DataService.d.ts +167 -0
- package/dist/services/data/DataService.d.ts.map +1 -0
- package/dist/services/data/DataValidationService.d.ts +48 -0
- package/dist/services/data/DataValidationService.d.ts.map +1 -0
- package/dist/types/index.d.ts +189 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/Logger.d.ts +88 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/jest.config.cjs +20 -0
- package/package.json +68 -0
- package/rollup.config.js +131 -0
- package/scripts/performance-validation-real.js +411 -0
- package/scripts/validate-optimization.sh +147 -0
- package/scripts/visual-validation-real-data.js +374 -0
- package/src/core/Sankey.ts +1039 -0
- package/src/core/events/EventBus.ts +488 -0
- package/src/core/types/events.ts +80 -0
- package/src/index.ts +35 -0
- package/src/services/AnimationService.ts +983 -0
- package/src/services/ConfigurationService.ts +497 -0
- package/src/services/InteractionService.ts +920 -0
- package/src/services/RenderingService.ts +484 -0
- package/src/services/calculation/GraphService.ts +616 -0
- package/src/services/calculation/SummaryService.ts +394 -0
- package/src/services/data/DataService.ts +380 -0
- package/src/services/data/DataValidationService.ts +155 -0
- package/src/styles/controls.css +184 -0
- package/src/styles/sankey.css +211 -0
- package/src/types/index.ts +220 -0
- package/src/utils/Logger.ts +105 -0
- package/tests/numerical-validation.test.js +575 -0
- package/tests/setup.js +53 -0
- 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';
|