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,20 @@
1
+ module.exports = {
2
+ testEnvironment: 'jsdom',
3
+ setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
4
+ transform: {
5
+ '^.+\\.(js|jsx)$': 'babel-jest',
6
+ },
7
+ testMatch: [
8
+ '<rootDir>/tests/**/*.test.js'
9
+ ],
10
+ moduleFileExtensions: ['js', 'json'],
11
+ collectCoverageFrom: [
12
+ 'src/**/*.{ts,js}',
13
+ '!src/**/*.d.ts',
14
+ '!src/types/**/*'
15
+ ],
16
+ // Allow importing the standalone ESM build in tests
17
+ transformIgnorePatterns: [
18
+ 'node_modules/(?!(d3|internmap)/)'
19
+ ]
20
+ };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "energy-visualization-sankey",
3
+ "version": "1.0.0",
4
+ "description": "A modern TypeScript library for creating interactive Sankey diagrams that visualize energy flows from sources to consumption sectors.",
5
+ "type": "module",
6
+ "main": "dist/sankey.umd.js",
7
+ "module": "dist/sankey.esm.js",
8
+ "types": "dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/types/index.d.ts",
12
+ "import": "./dist/sankey.esm.js",
13
+ "require": "./dist/sankey.umd.js"
14
+ },
15
+ "./styles": "./src/styles/sankey.css"
16
+ },
17
+ "scripts": {
18
+ "dev": "rollup -c -w",
19
+ "build": "rollup -c",
20
+ "build:dev": "rollup -c",
21
+ "type-check": "tsc --noEmit",
22
+ "clean": "rm -rf dist",
23
+ "test": "jest",
24
+ "test:watch": "jest --watch",
25
+ "test:numerical": "jest tests/numerical-validation.test.js",
26
+ "test:visual": "node scripts/visual-validation-real-data.js",
27
+ "test:performance": "node scripts/performance-validation-real.js",
28
+ "test:ci": "jest --coverage --watchAll=false --passWithNoTests",
29
+ "validate:baseline": "npm run build && npm run serve & sleep 3 && npm run test:visual && npm run test:performance && kill %1",
30
+ "validate:quick": "npm run test:numerical",
31
+ "validate:full": "npm run test:numerical && npm run build && npm run serve & sleep 3 && npm run test:visual && npm run test:performance && kill %1",
32
+ "validate": "npm run validate:full",
33
+ "prepublishOnly": "npm run clean && npm run test:ci && npm run build"
34
+ },
35
+ "keywords": [
36
+ "sankey",
37
+ "energy",
38
+ "d3js",
39
+ "typescript",
40
+ "energy-visualization",
41
+ "data-visualization"
42
+ ],
43
+ "author": "Research Computing Center (RCC) at The University of Chicago",
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "d3": "^7.8.5"
47
+ },
48
+ "devDependencies": {
49
+ "@babel/core": "^7.23.0",
50
+ "@babel/preset-env": "^7.23.0",
51
+ "@rollup/plugin-node-resolve": "^15.2.3",
52
+ "@rollup/plugin-terser": "^0.4.4",
53
+ "@rollup/plugin-typescript": "^11.1.5",
54
+ "@types/d3": "^7.4.0",
55
+ "@types/jest": "^29.5.0",
56
+ "babel-jest": "^29.7.0",
57
+ "jest": "^29.7.0",
58
+ "jest-environment-jsdom": "^29.7.0",
59
+ "jsdom": "^22.1.0",
60
+ "pixelmatch": "^5.3.0",
61
+ "playwright": "^1.40.0",
62
+ "pngjs": "^7.0.0",
63
+ "rollup": "^4.6.1",
64
+ "rollup-plugin-typescript2": "^0.36.0",
65
+ "tslib": "^2.6.0",
66
+ "typescript": "^5.3.0"
67
+ }
68
+ }
@@ -0,0 +1,131 @@
1
+ import resolve from '@rollup/plugin-node-resolve';
2
+ import typescript from 'rollup-plugin-typescript2';
3
+ import terser from '@rollup/plugin-terser';
4
+ import ts from 'typescript';
5
+
6
+ const isDev = process.env.NODE_ENV === 'development';
7
+
8
+ const baseConfig = {
9
+ input: 'src/core/Sankey.ts',
10
+ external: ['d3'], // D3 is external dependency
11
+ plugins: [
12
+ resolve({
13
+ browser: true,
14
+ preferBuiltins: false
15
+ }),
16
+ typescript({
17
+ tsconfig: './tsconfig.json',
18
+ useTsconfigDeclarationDir: true,
19
+ clean: true,
20
+ abortOnError: false,
21
+ rollupCommonJSResolveHack: false,
22
+ typescript: ts
23
+ })
24
+ ]
25
+ };
26
+
27
+ // ES Module build
28
+ const esmConfig = {
29
+ ...baseConfig,
30
+ output: {
31
+ file: 'dist/sankey.esm.js',
32
+ format: 'es',
33
+ sourcemap: true,
34
+ inlineDynamicImports: true
35
+ }
36
+ };
37
+
38
+ // UMD build
39
+ const umdConfig = {
40
+ ...baseConfig,
41
+ output: {
42
+ file: 'dist/sankey.umd.js',
43
+ format: 'umd',
44
+ name: 'energy-visualization-sankey',
45
+ globals: {
46
+ 'd3': 'd3'
47
+ },
48
+ sourcemap: true,
49
+ inlineDynamicImports: true
50
+ }
51
+ };
52
+
53
+ // UMD minified build
54
+ const umdMinConfig = {
55
+ ...baseConfig,
56
+ output: {
57
+ file: 'dist/sankey.umd.min.js',
58
+ format: 'umd',
59
+ name: 'energy-visualization-sankey',
60
+ globals: {
61
+ 'd3': 'd3'
62
+ },
63
+ sourcemap: true,
64
+ inlineDynamicImports: true
65
+ },
66
+ plugins: [
67
+ ...baseConfig.plugins,
68
+ terser()
69
+ ]
70
+ };
71
+
72
+ // Standalone builds (include D3)
73
+ const standaloneBaseConfig = {
74
+ input: 'src/core/Sankey.ts',
75
+ external: [], // Include D3 in standalone builds
76
+ plugins: [
77
+ resolve({
78
+ browser: true,
79
+ preferBuiltins: false
80
+ }),
81
+ typescript({
82
+ tsconfig: './tsconfig.json',
83
+ declaration: false, // Only generate types once
84
+ clean: true,
85
+ abortOnError: false,
86
+ rollupCommonJSResolveHack: false,
87
+ typescript: ts
88
+ })
89
+ ]
90
+ };
91
+
92
+ const standaloneEsmConfig = {
93
+ ...standaloneBaseConfig,
94
+ output: {
95
+ file: 'dist/sankey.standalone.esm.js',
96
+ format: 'es',
97
+ sourcemap: true,
98
+ inlineDynamicImports: true
99
+ }
100
+ };
101
+
102
+ const standaloneUmdConfig = {
103
+ ...standaloneBaseConfig,
104
+ output: {
105
+ file: 'dist/sankey.standalone.umd.js',
106
+ format: 'umd',
107
+ name: 'energy-visualization-sankey',
108
+ sourcemap: true,
109
+ inlineDynamicImports: true
110
+ }
111
+ };
112
+
113
+ const standaloneMinConfig = {
114
+ ...standaloneBaseConfig,
115
+ output: {
116
+ file: 'dist/sankey.standalone.min.js',
117
+ format: 'umd',
118
+ name: 'energy-visualization-sankey',
119
+ sourcemap: true,
120
+ inlineDynamicImports: true
121
+ },
122
+ plugins: [
123
+ ...standaloneBaseConfig.plugins,
124
+ terser()
125
+ ]
126
+ };
127
+
128
+ // Export appropriate configs based on environment
129
+ export default isDev
130
+ ? [esmConfig, umdConfig, standaloneEsmConfig, standaloneUmdConfig] // Include standalone versions for dev testing
131
+ : [esmConfig, umdConfig, umdMinConfig, standaloneEsmConfig, standaloneUmdConfig, standaloneMinConfig];
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Performance Validation Script for US Energy Sankey v5
3
+ * Measures performance with REAL data (1800-2021, 222 years)
4
+ * Tests initialization, animation, and memory usage
5
+ */
6
+
7
+ import {chromium} from 'playwright';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import {fileURLToPath} from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ class RealDataPerformanceValidator {
16
+ constructor() {
17
+ this.browser = null;
18
+ this.performanceDir = path.join(__dirname, '../validation/performance');
19
+ this.ensureDirectories();
20
+ }
21
+
22
+ ensureDirectories() {
23
+ if (!fs.existsSync(this.performanceDir)) {
24
+ fs.mkdirSync(this.performanceDir, {recursive: true});
25
+ console.log(`๐Ÿ“ Created performance directory: ${this.performanceDir}`);
26
+ }
27
+ }
28
+
29
+ async setup() {
30
+ console.log('โšก Setting up browser for performance validation...');
31
+ this.browser = await chromium.launch({
32
+ headless: true,
33
+ args: [
34
+ '--no-sandbox',
35
+ '--disable-dev-shm-usage',
36
+ '--enable-precise-memory-info'
37
+ ]
38
+ });
39
+ }
40
+
41
+ async measureInitializationPerformance() {
42
+ const page = await this.browser.newPage();
43
+
44
+ try {
45
+ console.log('๐Ÿ“Š Measuring initialization performance with 222 years of real data...');
46
+
47
+ // Navigate to the test page first
48
+ await page.goto('http://localhost:8080/examples/visual-test.html', {
49
+ waitUntil: 'networkidle',
50
+ timeout: 30000
51
+ });
52
+
53
+ const metrics = await page.evaluate(() => {
54
+ return new Promise((resolve) => {
55
+ const overallStart = performance.now();
56
+
57
+ // Wait for sankey to be available and initialized
58
+ const checkForSankey = () => {
59
+ if (window.sankey && window.sankey.isInitialized()) {
60
+ const overallEnd = performance.now();
61
+ const dataService = window.sankey.getDataService();
62
+
63
+ resolve({
64
+ totalTime: overallEnd - overallStart,
65
+ dataPointCount: dataService.data.length,
66
+ firstYear: dataService.firstYear,
67
+ lastYear: dataService.lastYear,
68
+ memoryUsage: performance.memory ? {
69
+ used: performance.memory.usedJSHeapSize,
70
+ total: performance.memory.totalJSHeapSize,
71
+ limit: performance.memory.jsHeapSizeLimit
72
+ } : null,
73
+ timestamp: new Date().toISOString()
74
+ });
75
+ } else {
76
+ setTimeout(checkForSankey, 50);
77
+ }
78
+ };
79
+
80
+ checkForSankey();
81
+ });
82
+ });
83
+
84
+ await page.close();
85
+ return metrics;
86
+
87
+ } catch (error) {
88
+ await page.close();
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ async measureAnimationPerformance() {
94
+ const page = await this.browser.newPage();
95
+
96
+ try {
97
+ console.log('๐ŸŽฌ Measuring animation performance across historical periods...');
98
+
99
+ // Navigate to the page
100
+ await page.goto('http://localhost:8080/examples/visual-test.html', {
101
+ waitUntil: 'networkidle',
102
+ timeout: 30000
103
+ });
104
+
105
+ // Wait for sankey to initialize
106
+ await page.waitForFunction(() => {
107
+ return window.sankey && window.sankey.isInitialized() && window.sankey.getYears().length === 222;
108
+ }, {timeout: 20000});
109
+
110
+ const animationMetrics = await page.evaluate(() => {
111
+ return new Promise((resolve) => {
112
+ const sankey = window.sankey;
113
+ if (!sankey) {
114
+ resolve({error: 'Sankey not available'});
115
+ return;
116
+ }
117
+
118
+ // Test years representing major energy transitions
119
+ const testYears = [1800, 1850, 1900, 1950, 1980, 2000, 2021];
120
+ let currentYearIndex = 0;
121
+ let frameCount = 0;
122
+ let totalFrameTime = 0;
123
+ const frameTimes = [];
124
+ const yearTransitionTimes = [];
125
+
126
+ const startTime = performance.now();
127
+
128
+ const measureFrame = () => {
129
+ const frameStart = performance.now();
130
+ frameCount++;
131
+
132
+ // Switch to next test year every 10 frames
133
+ if (frameCount % 10 === 0 && currentYearIndex < testYears.length - 1) {
134
+ currentYearIndex++;
135
+ const transitionStart = performance.now();
136
+ sankey.setYear(testYears[currentYearIndex]);
137
+ const transitionEnd = performance.now();
138
+ yearTransitionTimes.push(transitionEnd - transitionStart);
139
+ }
140
+
141
+ if (frameCount < 70) { // Test 70 frames across all years
142
+ requestAnimationFrame(() => {
143
+ const frameEnd = performance.now();
144
+ const frameTime = frameEnd - frameStart;
145
+ frameTimes.push(frameTime);
146
+ totalFrameTime += frameTime;
147
+ measureFrame();
148
+ });
149
+ } else {
150
+ const endTime = performance.now();
151
+ const totalDuration = endTime - startTime;
152
+
153
+ resolve({
154
+ totalDuration,
155
+ frameCount,
156
+ averageFrameTime: totalFrameTime / frameCount,
157
+ averageFPS: (frameCount / totalDuration) * 1000,
158
+ minFrameTime: Math.min(...frameTimes),
159
+ maxFrameTime: Math.max(...frameTimes),
160
+ yearTransitions: yearTransitionTimes.length,
161
+ averageTransitionTime: yearTransitionTimes.reduce((a, b) => a + b, 0) / yearTransitionTimes.length,
162
+ testedYears: testYears.slice(0, currentYearIndex + 1),
163
+ memoryUsage: performance.memory ? {
164
+ used: performance.memory.usedJSHeapSize,
165
+ total: performance.memory.totalJSHeapSize
166
+ } : null
167
+ });
168
+ }
169
+ };
170
+
171
+ measureFrame();
172
+ });
173
+ });
174
+
175
+ await page.close();
176
+ return animationMetrics;
177
+
178
+ } catch (error) {
179
+ await page.close();
180
+ throw error;
181
+ }
182
+ }
183
+
184
+ async measureMemoryUsage() {
185
+ const page = await this.browser.newPage();
186
+
187
+ try {
188
+ console.log('๐Ÿง  Measuring memory usage patterns...');
189
+
190
+ await page.goto('http://localhost:8080/examples/visual-test.html', {
191
+ waitUntil: 'networkidle',
192
+ timeout: 30000
193
+ });
194
+
195
+ await page.waitForFunction(() => {
196
+ return window.sankey && window.sankey.isInitialized();
197
+ }, {timeout: 20000});
198
+
199
+ const memoryMetrics = await page.evaluate(() => {
200
+ return new Promise((resolve) => {
201
+ const sankey = window.sankey;
202
+ const measurements = [];
203
+
204
+ const takeMeasurement = (label) => {
205
+ if (performance.memory) {
206
+ measurements.push({
207
+ label,
208
+ used: performance.memory.usedJSHeapSize,
209
+ total: performance.memory.totalJSHeapSize,
210
+ timestamp: performance.now()
211
+ });
212
+ }
213
+ };
214
+
215
+ takeMeasurement('initial');
216
+
217
+ // Force garbage collection if available
218
+ if (window.gc) {
219
+ window.gc();
220
+ }
221
+
222
+ takeMeasurement('after_gc');
223
+
224
+ // Trigger heavy operations
225
+ sankey.setYear(2021);
226
+ takeMeasurement('after_year_change');
227
+
228
+ sankey.play();
229
+ setTimeout(() => {
230
+ takeMeasurement('during_animation');
231
+ sankey.pause();
232
+
233
+ setTimeout(() => {
234
+ takeMeasurement('after_pause');
235
+
236
+ resolve({
237
+ measurements,
238
+ peakMemory: Math.max(...measurements.map(m => m.used)),
239
+ memoryGrowth: measurements[measurements.length - 1].used - measurements[0].used
240
+ });
241
+ }, 1000);
242
+ }, 2000);
243
+ });
244
+ });
245
+
246
+ await page.close();
247
+ return memoryMetrics;
248
+
249
+ } catch (error) {
250
+ await page.close();
251
+ throw error;
252
+ }
253
+ }
254
+
255
+ async runCompletePerformanceValidation() {
256
+ console.log('๐Ÿš€ Starting comprehensive performance validation...\n');
257
+
258
+ const results = {};
259
+
260
+ try {
261
+ // Measure initialization performance
262
+ console.log('1/3 Testing initialization performance...');
263
+ results.initialization = await this.measureInitializationPerformance();
264
+ this.reportInitializationResults(results.initialization);
265
+
266
+ // Measure animation performance
267
+ console.log('\n2/3 Testing animation performance...');
268
+ results.animation = await this.measureAnimationPerformance();
269
+ this.reportAnimationResults(results.animation);
270
+
271
+ // Measure memory usage
272
+ console.log('\n3/3 Testing memory usage...');
273
+ results.memory = await this.measureMemoryUsage();
274
+ this.reportMemoryResults(results.memory);
275
+
276
+ // Save complete results
277
+ this.savePerformanceBaseline(results);
278
+
279
+ return results;
280
+
281
+ } catch (error) {
282
+ console.error('โŒ Performance validation failed:', error);
283
+ throw error;
284
+ }
285
+ }
286
+
287
+ reportInitializationResults(metrics) {
288
+ if (metrics.error) {
289
+ console.error(`โŒ Initialization error: ${metrics.error}`);
290
+ return;
291
+ }
292
+
293
+ console.log(`๐Ÿ“Š Initialization Results:`);
294
+ console.log(` Total time: ${metrics.totalTime.toFixed(2)}ms`);
295
+ console.log(` Data points: ${metrics.dataPointCount} years (${metrics.firstYear}-${metrics.lastYear})`);
296
+
297
+ if (metrics.memoryUsage) {
298
+ console.log(` Memory usage: ${(metrics.memoryUsage.used / 1024 / 1024).toFixed(2)}MB`);
299
+ }
300
+ }
301
+
302
+ reportAnimationResults(metrics) {
303
+ if (metrics.error) {
304
+ console.error(`โŒ Animation error: ${metrics.error}`);
305
+ return;
306
+ }
307
+
308
+ console.log(`๐ŸŽฌ Animation Results:`);
309
+ console.log(` Duration: ${metrics.totalDuration.toFixed(2)}ms`);
310
+ console.log(` Frames: ${metrics.frameCount}`);
311
+ console.log(` Average FPS: ${metrics.averageFPS.toFixed(2)}`);
312
+ console.log(` Avg frame time: ${metrics.averageFrameTime.toFixed(2)}ms`);
313
+ console.log(` Frame time range: ${metrics.minFrameTime.toFixed(2)}ms - ${metrics.maxFrameTime.toFixed(2)}ms`);
314
+ console.log(` Year transitions: ${metrics.yearTransitions}`);
315
+ console.log(` Avg transition time: ${metrics.averageTransitionTime.toFixed(2)}ms`);
316
+ console.log(` Tested years: ${metrics.testedYears.join(', ')}`);
317
+ }
318
+
319
+ reportMemoryResults(metrics) {
320
+ console.log(`๐Ÿง  Memory Results:`);
321
+ console.log(` Peak memory: ${(metrics.peakMemory / 1024 / 1024).toFixed(2)}MB`);
322
+ console.log(` Memory growth: ${(metrics.memoryGrowth / 1024 / 1024).toFixed(2)}MB`);
323
+
324
+ console.log(` Memory progression:`);
325
+ metrics.measurements.forEach(m => {
326
+ console.log(` ${m.label}: ${(m.used / 1024 / 1024).toFixed(2)}MB`);
327
+ });
328
+ }
329
+
330
+ savePerformanceBaseline(results) {
331
+ const baselineFile = path.join(this.performanceDir, 'real-data-baseline.json');
332
+ const summaryFile = path.join(this.performanceDir, 'performance-summary.json');
333
+
334
+ // Save detailed results
335
+ fs.writeFileSync(baselineFile, JSON.stringify(results, null, 2));
336
+
337
+ // Save summary for easy comparison
338
+ const summary = {
339
+ timestamp: new Date().toISOString(),
340
+ dataPoints: results.initialization?.dataPointCount || 0,
341
+ initTime: results.initialization?.totalTime || 0,
342
+ avgFPS: results.animation?.averageFPS || 0,
343
+ peakMemoryMB: results.memory ? (results.memory.peakMemory / 1024 / 1024) : 0,
344
+ version: '5.0.0'
345
+ };
346
+
347
+ fs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2));
348
+
349
+ console.log(`\n๐Ÿ’พ Performance baseline saved:`);
350
+ console.log(` Details: ${baselineFile}`);
351
+ console.log(` Summary: ${summaryFile}`);
352
+ }
353
+
354
+ async teardown() {
355
+ if (this.browser) {
356
+ await this.browser.close();
357
+ console.log('๐Ÿงน Browser closed');
358
+ }
359
+ }
360
+ }
361
+
362
+ // Main execution
363
+ async function main() {
364
+ const validator = new RealDataPerformanceValidator();
365
+
366
+ try {
367
+ await validator.setup();
368
+ const results = await validator.runCompletePerformanceValidation();
369
+
370
+ console.log('\nโœ… Performance validation completed successfully!');
371
+
372
+ // Performance thresholds (adjust based on your requirements)
373
+ const thresholds = {
374
+ maxInitTime: 5000, // 5 seconds
375
+ minFPS: 30,
376
+ maxMemoryMB: 100
377
+ };
378
+
379
+ let failed = false;
380
+
381
+ if (results.initialization?.totalTime > thresholds.maxInitTime) {
382
+ console.error(`โŒ Initialization too slow: ${results.initialization.totalTime.toFixed(2)}ms > ${thresholds.maxInitTime}ms`);
383
+ failed = true;
384
+ }
385
+
386
+ if (results.animation?.averageFPS < thresholds.minFPS) {
387
+ console.error(`โŒ FPS too low: ${results.animation.averageFPS.toFixed(2)} < ${thresholds.minFPS}`);
388
+ failed = true;
389
+ }
390
+
391
+ if (results.memory?.peakMemory > thresholds.maxMemoryMB * 1024 * 1024) {
392
+ console.error(`โŒ Memory usage too high: ${(results.memory.peakMemory / 1024 / 1024).toFixed(2)}MB > ${thresholds.maxMemoryMB}MB`);
393
+ failed = true;
394
+ }
395
+
396
+ if (failed) {
397
+ process.exit(1);
398
+ }
399
+
400
+ } catch (error) {
401
+ console.error('โŒ Performance validation crashed:', error);
402
+ process.exit(1);
403
+ } finally {
404
+ await validator.teardown();
405
+ }
406
+ }
407
+
408
+ // Run if called directly
409
+ if (import.meta.url === `file://${process.argv[1]}`) {
410
+ main();
411
+ }