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
package/jest.config.cjs
ADDED
|
@@ -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
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -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
|
+
}
|