visualifyjs 2.5.3-2.dev → 3.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/.claude/mem/TIMELINE.md +36 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
- package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
- package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
- package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
- package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
- package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
- package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
- package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
- package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
- package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
- package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
- package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
- package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
- package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
- package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
- package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
- package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
- package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
- package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
- package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
- package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
- package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
- package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
- package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
- package/.claude/settings.local.json +21 -0
- package/.github/workflows/static.yml.bak +51 -51
- package/.sisyphus/boulder.json +65 -0
- package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
- package/LICENSE +674 -674
- package/README.md +94 -59
- package/config-overrides.js +31 -31
- package/dist/stats.html +4949 -0
- package/dist/visualify-3d.esm.js +1 -0
- package/dist/visualify-3d.js +1 -0
- package/dist/visualify-core.esm.js +1 -0
- package/dist/visualify-core.js +1 -0
- package/dist/visualify-docs.esm.js +1 -0
- package/dist/visualify-docs.js +1 -0
- package/dist/visualify-loader.js +1 -0
- package/dist/visualify-pages.esm.js +1 -0
- package/dist/visualify-pages.js +1 -0
- package/dist/visualify-portal.esm.js +1 -0
- package/dist/visualify-portal.js +1 -0
- package/dist/visualify-shared.js +26571 -0
- package/dist/visualify.js +1 -188
- package/docs/CHANGELOG.md +148 -0
- package/docs/cli/commands.md +513 -0
- package/docs/configuration/visualify-json.md +474 -0
- package/docs/docs/3d-visualization.md +374 -0
- package/docs/docs/CLI.md +303 -34
- package/docs/docs/README.md +65 -65
- package/docs/docs/Rechart/bar.md +190 -190
- package/docs/docs/Rechart/funnel.md +241 -241
- package/docs/docs/Rechart/line.md +355 -355
- package/docs/docs/Rechart/pie.md +225 -225
- package/docs/docs/Rechart/radar.md +253 -253
- package/docs/docs/Rechart/scatter.md +298 -298
- package/docs/docs/_404.md +51 -51
- package/docs/docs/_coverpage.md +11 -11
- package/docs/docs/_sidebar.md +54 -44
- package/docs/docs/components/dotBio.md +87 -34
- package/docs/docs/components/echart.md +171 -82
- package/docs/docs/components/html.md +61 -34
- package/docs/docs/components/macaron.md +156 -145
- package/docs/docs/components/markdown.md +42 -0
- package/docs/docs/components/more.md +183 -142
- package/docs/docs/components/plotly.md +132 -62
- package/docs/docs/components/scatterL.md +171 -70
- package/docs/docs/components/visium.md +112 -57
- package/docs/docs/configuration.md +121 -121
- package/docs/docs/deploy.md +31 -31
- package/docs/docs/docsify-plugin.md +655 -0
- package/docs/docs/hmr.md +165 -0
- package/docs/docs/i18n.md +332 -0
- package/docs/docs/log.md +30 -9
- package/docs/docs/more-pages.md +23 -23
- package/docs/docs/quickstart.md +148 -124
- package/docs/docs/rechart-attributes.md +74 -74
- package/docs/docs/rechart-basic-usage.md +160 -162
- package/docs/docs/theme.md +5 -5
- package/docs/docs/typescript.md +306 -0
- package/docs/docs/visual-editor.md +359 -0
- package/docs/index.html +85 -71
- package/docs/manifest.json +23 -23
- package/docs/migration/v3-migration.md +392 -0
- package/docs/static/css/fluff-stuff.css +169 -169
- package/docs/static/css/font-awesome.min.css +4 -4
- package/docs/static/css/visualify.css +6 -25
- package/docs/static/js/3d-viz-examples.js +181 -0
- package/docs/static/js/configuration.js +630 -448
- package/docs/static/js/visualify.js +1 -188
- package/package.json +106 -84
- package/rollup.config.mjs +766 -76
- package/src/_css/404.css +115 -115
- package/src/_css/App.css +37 -37
- package/src/_css/autoSuggestion.css +26 -26
- package/src/_css/circular-progress.css +32 -32
- package/src/_css/index.css +36 -36
- package/src/_css/modern.css +350 -25
- package/src/_media/corner.svg +8 -8
- package/src/_media/download.svg +3 -3
- package/src/_media/logo.svg +14 -14
- package/src/_test/App.test.js +15 -15
- package/src/_utils/reportWebVitals.js +13 -13
- package/src/a11y/README.md +177 -0
- package/src/a11y/aria-labels.js +339 -0
- package/src/a11y/color-contrast.js +535 -0
- package/src/a11y/index.js +197 -0
- package/src/a11y/keyboard-nav.js +523 -0
- package/src/a11y/styles.css +165 -0
- package/src/cli/commands/dev.js +214 -0
- package/src/cli/commands/docs.js +521 -0
- package/src/cli/commands/edit.js +379 -0
- package/src/cli/commands/init.js +213 -0
- package/src/cli/commands/portal.js +236 -0
- package/src/cli/dev-server.js +530 -0
- package/src/cli/hmr.js +456 -0
- package/src/cli/index.js +180 -0
- package/src/cli/utils/config.js +207 -0
- package/src/cli/utils/logger.js +241 -0
- package/src/config/defaults.ts +122 -0
- package/src/config/index.ts +72 -0
- package/src/config/loader.ts +478 -0
- package/src/config/schema.ts +227 -0
- package/src/config/validator.ts +337 -0
- package/src/core/appContext.js +34 -27
- package/src/core/components/Bar.js +383 -0
- package/src/core/components/Bar3D.js +473 -0
- package/src/core/components/LargeDatasetChart.js +296 -0
- package/src/core/components/Line3D.js +310 -0
- package/src/core/components/Scatter.js +392 -188
- package/src/core/components/Scatter3D.js +455 -0
- package/src/core/components/ScatterBio.js +601 -572
- package/src/core/components/Surface3D.js +326 -0
- package/src/core/components/ThreeCustom.js +648 -0
- package/src/core/components/ThreeScene.js +459 -0
- package/src/core/components/VisiumPlot.js +191 -165
- package/src/core/components/browser.js +42 -42
- package/src/core/components/dotplot.js +413 -413
- package/src/core/components/html.js +29 -29
- package/src/core/components/list.js +178 -178
- package/src/core/components/macaron.js +206 -201
- package/src/core/components/markdown.js +56 -56
- package/src/core/components/parser.scatterBio.js +582 -587
- package/src/core/components/ratio.js +82 -80
- package/src/core/components/scatterL.js +206 -173
- package/src/core/components/searchbar.js +156 -131
- package/src/core/components/selection.js +310 -193
- package/src/core/components/timeline.js +236 -281
- package/src/core/components/visium.js +114 -97
- package/src/core/data-processor.js +413 -0
- package/src/core/fetch/condfetch.js +82 -82
- package/src/core/fetch/fetch.js +92 -92
- package/src/core/fetch/json.js +29 -29
- package/src/core/fetch/vfetch.js +42 -42
- package/src/core/hmr-client.js +724 -0
- package/src/core/liveEditor.js +44 -44
- package/src/core/modules/codeEditorWithPreview.js +104 -104
- package/src/core/modules/echarts/common.js +20 -20
- package/src/core/modules/echarts/gl.js +228 -0
- package/src/core/modules/echarts/presetHandler.js +41 -41
- package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
- package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
- package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
- package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
- package/src/core/modules/echarts.js +70 -71
- package/src/core/modules/echartsUtils.js +43 -43
- package/src/core/modules/echartswitcher.js +227 -152
- package/src/core/modules/replotly/presetHandler.js +24 -24
- package/src/core/modules/replotly/presets/minimum.js +18 -18
- package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
- package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
- package/src/core/modules/replotly.js +74 -71
- package/src/core/modules/threejs/Camera.js +373 -0
- package/src/core/modules/threejs/Lighting.js +459 -0
- package/src/core/modules/threejs/Renderer.js +364 -0
- package/src/core/modules/threejs/Scene.js +266 -0
- package/src/core/modules/threejs/index.js +155 -0
- package/src/core/pages/404.js +50 -50
- package/src/core/pages/error.js +27 -27
- package/src/core/pages/jsonPage.js +62 -62
- package/src/core/pages/loading.js +44 -44
- package/src/core/parser/echart.data.js +204 -183
- package/src/core/parser/echart.features.js +125 -125
- package/src/core/parser/echart.general.js +147 -147
- package/src/core/parser/echart.hilbert.js +57 -57
- package/src/core/parser/echart.parser.js +210 -210
- package/src/core/parser/echart.series.js +67 -67
- package/src/core/parser/echart.types.js +76 -76
- package/src/core/parser/plotly.config.js +10 -10
- package/src/core/parser/plotly.data.js +132 -132
- package/src/core/parser/plotly.layout.js +9 -9
- package/src/core/parser/plotly.violin.js +18 -18
- package/src/core/recharts.js +361 -62
- package/src/core/router/alias.js +49 -49
- package/src/core/router/jsonRouter.js +31 -31
- package/src/core/themes/modern.js +32 -32
- package/src/core/themes/themeSelector.js +33 -33
- package/src/core/visualify.js +213 -47
- package/src/core/widgets/circularProgress.js +23 -23
- package/src/core/widgets/controller.js +116 -83
- package/src/core/widgets/errorBoundary.js +36 -36
- package/src/core/widgets/footer.js +185 -177
- package/src/core/widgets/header.js +238 -234
- package/src/core/widgets/layout/Grid.js +31 -31
- package/src/core/widgets/layout.js +36 -36
- package/src/core/widgets/mapping.js +56 -42
- package/src/core/workers/data-worker.js +349 -0
- package/src/core/workers/worker-pool.js +396 -0
- package/src/docsify/bundle.js +215 -0
- package/src/docsify/markdown.js +271 -0
- package/src/docsify/plugin.js +268 -0
- package/src/editor/README.md +172 -0
- package/src/editor/components/ChartBuilder.jsx +341 -0
- package/src/editor/components/ChartTypeSidebar.jsx +91 -0
- package/src/editor/components/Editor.jsx +367 -0
- package/src/editor/components/Preview.jsx +446 -0
- package/src/editor/components/PropertyPanel.jsx +468 -0
- package/src/editor/components/StatusBar.jsx +85 -0
- package/src/editor/context/EditorContext.js +248 -0
- package/src/editor/hooks/useDebounce.js +32 -0
- package/src/editor/index.js +315 -0
- package/src/editor/styles/editor.css +637 -0
- package/src/editor/utils/chartValidator.js +263 -0
- package/src/entries/charts3d.js +70 -0
- package/src/entries/core.js +78 -0
- package/src/entries/docs.js +154 -0
- package/src/entries/pages.js +93 -0
- package/src/entries/portal.js +204 -0
- package/src/entries/shared.js +50 -0
- package/src/i18n/formatters.js +455 -0
- package/src/i18n/index.js +169 -0
- package/src/i18n/locales/ar.json +137 -0
- package/src/i18n/locales/de.json +137 -0
- package/src/i18n/locales/en.json +137 -0
- package/src/i18n/locales/es.json +137 -0
- package/src/i18n/locales/he.json +137 -0
- package/src/i18n/locales/zh.json +137 -0
- package/src/i18n/rtl.css +183 -0
- package/src/index.js +82 -62
- package/src/loader.js +103 -0
- package/src/setupTests.js +5 -5
- package/tsconfig.json +51 -0
- package/types/charts.d.ts +569 -0
- package/types/components.d.ts +441 -0
- package/types/config.d.ts +199 -0
- package/types/index.d.ts +353 -0
|
@@ -1,97 +1,114 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* @Author : Lihao leolihao@arizona.edu
|
|
3
|
-
* @Date : 2024-01-08 16:34:20
|
|
4
|
-
* @FilePath : /visualifyjs/src/core/components/Visium.js
|
|
5
|
-
* @Description :
|
|
6
|
-
* Copyright (c) 2024 by Lihao (leolihao@arizona.edu), All Rights Reserved.
|
|
7
|
-
*/
|
|
8
|
-
import React, { useEffect, useState } from 'react';
|
|
9
|
-
import { useAppContext } from '../appContext';
|
|
10
|
-
import conditionalFetch from '../fetch/condfetch';
|
|
11
|
-
import EChartSwitcher from '../modules/echartswitcher';
|
|
12
|
-
import Loading from '../pages/loading';
|
|
13
|
-
import { isEmpty } from 'lodash';
|
|
14
|
-
|
|
15
|
-
const Visium = ({ props, style }) => {
|
|
16
|
-
const [loading, setLoading] = useState({
|
|
17
|
-
active: true,
|
|
18
|
-
message: 'Please Select the Section',
|
|
19
|
-
});
|
|
20
|
-
const { sharedData } = useAppContext();
|
|
21
|
-
const [Options, setOptions] = useState(props);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
/*
|
|
2
|
+
* @Author : Lihao leolihao@arizona.edu
|
|
3
|
+
* @Date : 2024-01-08 16:34:20
|
|
4
|
+
* @FilePath : /visualifyjs/src/core/components/Visium.js
|
|
5
|
+
* @Description :
|
|
6
|
+
* Copyright (c) 2024 by Lihao (leolihao@arizona.edu), All Rights Reserved.
|
|
7
|
+
*/
|
|
8
|
+
import React, { useEffect, useState, useRef } from 'react';
|
|
9
|
+
import { useAppContext } from '../appContext';
|
|
10
|
+
import conditionalFetch from '../fetch/condfetch';
|
|
11
|
+
import EChartSwitcher from '../modules/echartswitcher';
|
|
12
|
+
import Loading from '../pages/loading';
|
|
13
|
+
import { isEmpty } from 'lodash';
|
|
14
|
+
|
|
15
|
+
const Visium = ({ props, style }) => {
|
|
16
|
+
const [loading, setLoading] = useState({
|
|
17
|
+
active: true,
|
|
18
|
+
message: 'Please Select the Section',
|
|
19
|
+
});
|
|
20
|
+
const { sharedData } = useAppContext();
|
|
21
|
+
const [Options, setOptions] = useState(props);
|
|
22
|
+
|
|
23
|
+
// Use ref for sharedData to access latest without depending on it
|
|
24
|
+
const sharedDataRef = useRef(sharedData);
|
|
25
|
+
sharedDataRef.current = sharedData;
|
|
26
|
+
|
|
27
|
+
// Extract stable trigger keys from props.settings
|
|
28
|
+
const { cellfrac, metadata, gene, image } = props.settings || {};
|
|
29
|
+
const imageTrigger = image?.trigger;
|
|
30
|
+
// Build a snapshot of only the sharedData keys we depend on
|
|
31
|
+
const relevantKeys = [imageTrigger, cellfrac].filter(Boolean);
|
|
32
|
+
const sharedDataSnapshot = JSON.stringify(
|
|
33
|
+
relevantKeys.reduce((acc, key) => {
|
|
34
|
+
acc[key] = sharedData[key];
|
|
35
|
+
return acc;
|
|
36
|
+
}, {}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!metadata || !gene || !image)
|
|
41
|
+
throw new Error('missing metadata, gene, or image');
|
|
42
|
+
const currentSharedData = sharedDataRef.current;
|
|
43
|
+
let trigger = {
|
|
44
|
+
metadata: metadata.trigger,
|
|
45
|
+
gene: gene.trigger,
|
|
46
|
+
image: image.trigger,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const updatePlot = async () => {
|
|
50
|
+
try {
|
|
51
|
+
const cellval = isEmpty(currentSharedData[cellfrac])
|
|
52
|
+
? 'All'
|
|
53
|
+
: currentSharedData[cellfrac][0];
|
|
54
|
+
|
|
55
|
+
if (currentSharedData?.[trigger.image]) {
|
|
56
|
+
console.log('image trigger', currentSharedData?.[trigger.image]);
|
|
57
|
+
const imageBuffer = await conditionalFetch(
|
|
58
|
+
image,
|
|
59
|
+
currentSharedData,
|
|
60
|
+
{},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const blob = new Blob([new Uint8Array(imageBuffer.data)], {
|
|
64
|
+
type: 'image/png',
|
|
65
|
+
});
|
|
66
|
+
const dataURL = URL.createObjectURL(blob);
|
|
67
|
+
|
|
68
|
+
console.log('image', dataURL);
|
|
69
|
+
setOptions((prev) => ({
|
|
70
|
+
...prev,
|
|
71
|
+
config: {
|
|
72
|
+
...prev.config,
|
|
73
|
+
overrides: {
|
|
74
|
+
...prev.config.overrides,
|
|
75
|
+
backgroundColor: {
|
|
76
|
+
image: dataURL,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log('update plot', cellval, trigger);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
setLoading({ active: true, message: e.message });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
updatePlot();
|
|
90
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
91
|
+
}, [sharedDataSnapshot, cellfrac, imageTrigger]);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
id={props.id}
|
|
96
|
+
style={{ ...style, position: 'relative' }}>
|
|
97
|
+
{loading.active && (
|
|
98
|
+
<Loading
|
|
99
|
+
message={loading.message}
|
|
100
|
+
style={loading.style}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
<EChartSwitcher
|
|
104
|
+
props={Options}
|
|
105
|
+
style={{
|
|
106
|
+
opacity: loading.active ? 0 : 1,
|
|
107
|
+
...style,
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export default Visium;
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Data Processor - Main Thread Interface
|
|
3
|
+
* @module core/data-processor
|
|
4
|
+
*
|
|
5
|
+
* Provides a high-level API for offloading data processing to Web Workers.
|
|
6
|
+
* Handles worker pool management, task distribution, and result aggregation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getWorkerPool, terminateWorkerPool } from './workers/worker-pool';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration for data processor
|
|
13
|
+
* @readonly
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
workerScript: new URL('./workers/data-worker.js', import.meta.url).href,
|
|
17
|
+
minWorkers: 2,
|
|
18
|
+
maxWorkers: 4,
|
|
19
|
+
chunkSize: 10000,
|
|
20
|
+
enableChunking: true,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Data Processor class
|
|
25
|
+
* Manages data processing operations using Web Workers
|
|
26
|
+
*/
|
|
27
|
+
class DataProcessor {
|
|
28
|
+
/**
|
|
29
|
+
* Create a new data processor
|
|
30
|
+
* @param {Object} config - Processor configuration
|
|
31
|
+
*/
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
34
|
+
this.pool = getWorkerPool({
|
|
35
|
+
workerScript: this.config.workerScript,
|
|
36
|
+
minWorkers: this.config.minWorkers,
|
|
37
|
+
maxWorkers: this.config.maxWorkers,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if data should be chunked for processing
|
|
43
|
+
* @private
|
|
44
|
+
* @param {Array} data - Data array
|
|
45
|
+
* @returns {boolean} True if data should be chunked
|
|
46
|
+
*/
|
|
47
|
+
_shouldChunk(data) {
|
|
48
|
+
return this.config.enableChunking && data.length > this.config.chunkSize;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Split data into chunks for parallel processing
|
|
53
|
+
* @private
|
|
54
|
+
* @param {Array} data - Data array
|
|
55
|
+
* @param {number} chunkSize - Size of each chunk
|
|
56
|
+
* @returns {Array<Array>} Array of chunks
|
|
57
|
+
*/
|
|
58
|
+
_chunkData(data, chunkSize) {
|
|
59
|
+
const chunks = [];
|
|
60
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
61
|
+
chunks.push(data.slice(i, i + chunkSize));
|
|
62
|
+
}
|
|
63
|
+
return chunks;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Aggregate data using specified method
|
|
68
|
+
* @param {Array} data - Input data
|
|
69
|
+
* @param {Object} config - Aggregation config
|
|
70
|
+
* @returns {Promise<number>} Aggregated value
|
|
71
|
+
*/
|
|
72
|
+
async aggregate(data, config) {
|
|
73
|
+
if (this._shouldChunk(data)) {
|
|
74
|
+
// Process in chunks and combine results
|
|
75
|
+
const chunks = this._chunkData(data, this.config.chunkSize);
|
|
76
|
+
const chunkResults = await this.pool.executeAll(
|
|
77
|
+
chunks.map((chunk) => ({
|
|
78
|
+
operation: 'aggregate',
|
|
79
|
+
data: chunk,
|
|
80
|
+
config: { ...config, method: 'sum' },
|
|
81
|
+
}))
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Combine chunk results
|
|
85
|
+
const total = chunkResults.reduce((a, b) => a + b, 0);
|
|
86
|
+
|
|
87
|
+
if (config.method === 'avg' || config.method === 'mean') {
|
|
88
|
+
return total / data.length;
|
|
89
|
+
}
|
|
90
|
+
if (config.method === 'count') {
|
|
91
|
+
return data.length;
|
|
92
|
+
}
|
|
93
|
+
return total;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this.pool.execute({
|
|
97
|
+
operation: 'aggregate',
|
|
98
|
+
data,
|
|
99
|
+
config,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Filter data based on criteria
|
|
105
|
+
* @param {Array} data - Input data
|
|
106
|
+
* @param {Object} criteria - Filter criteria
|
|
107
|
+
* @returns {Promise<Array>} Filtered data
|
|
108
|
+
*/
|
|
109
|
+
async filter(data, criteria) {
|
|
110
|
+
if (this._shouldChunk(data)) {
|
|
111
|
+
const chunks = this._chunkData(data, this.config.chunkSize);
|
|
112
|
+
const results = await this.pool.executeAll(
|
|
113
|
+
chunks.map((chunk) => ({
|
|
114
|
+
operation: 'filter',
|
|
115
|
+
data: chunk,
|
|
116
|
+
config: criteria,
|
|
117
|
+
}))
|
|
118
|
+
);
|
|
119
|
+
return results.flat();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return this.pool.execute({
|
|
123
|
+
operation: 'filter',
|
|
124
|
+
data,
|
|
125
|
+
config: criteria,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sort data by fields
|
|
131
|
+
* @param {Array} data - Input data
|
|
132
|
+
* @param {Array|Object} sortConfig - Sort configuration
|
|
133
|
+
* @returns {Promise<Array>} Sorted data
|
|
134
|
+
*/
|
|
135
|
+
async sort(data, sortConfig) {
|
|
136
|
+
// Sorting is typically done on full dataset
|
|
137
|
+
return this.pool.execute({
|
|
138
|
+
operation: 'sort',
|
|
139
|
+
data,
|
|
140
|
+
config: sortConfig,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Group data by field(s)
|
|
146
|
+
* @param {Array} data - Input data
|
|
147
|
+
* @param {string|Array} fields - Field(s) to group by
|
|
148
|
+
* @returns {Promise<Object>} Grouped data
|
|
149
|
+
*/
|
|
150
|
+
async groupBy(data, fields) {
|
|
151
|
+
return this.pool.execute({
|
|
152
|
+
operation: 'groupBy',
|
|
153
|
+
data,
|
|
154
|
+
config: fields,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sample data for preview
|
|
160
|
+
* @param {Array} data - Input data
|
|
161
|
+
* @param {number} sampleSize - Sample size
|
|
162
|
+
* @returns {Promise<Array>} Sampled data
|
|
163
|
+
*/
|
|
164
|
+
async sample(data, sampleSize) {
|
|
165
|
+
return this.pool.execute({
|
|
166
|
+
operation: 'sample',
|
|
167
|
+
data,
|
|
168
|
+
config: { size: sampleSize },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Calculate statistics for a field
|
|
174
|
+
* @param {Array} data - Input data
|
|
175
|
+
* @param {string} field - Field name
|
|
176
|
+
* @returns {Promise<Object>} Statistics
|
|
177
|
+
*/
|
|
178
|
+
async statistics(data, field) {
|
|
179
|
+
if (this._shouldChunk(data)) {
|
|
180
|
+
// Process statistics in chunks and combine
|
|
181
|
+
const chunks = this._chunkData(data, this.config.chunkSize);
|
|
182
|
+
const chunkStats = await this.pool.executeAll(
|
|
183
|
+
chunks.map((chunk) => ({
|
|
184
|
+
operation: 'statistics',
|
|
185
|
+
data: chunk,
|
|
186
|
+
config: { field },
|
|
187
|
+
}))
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Combine statistics from chunks
|
|
191
|
+
return this._combineStatistics(chunkStats);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return this.pool.execute({
|
|
195
|
+
operation: 'statistics',
|
|
196
|
+
data,
|
|
197
|
+
config: { field },
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Combine statistics from multiple chunks
|
|
203
|
+
* @private
|
|
204
|
+
* @param {Array<Object>} statsArray - Array of statistics
|
|
205
|
+
* @returns {Object} Combined statistics
|
|
206
|
+
*/
|
|
207
|
+
_combineStatistics(statsArray) {
|
|
208
|
+
const validStats = statsArray.filter((s) => s !== null);
|
|
209
|
+
if (validStats.length === 0) return null;
|
|
210
|
+
|
|
211
|
+
const totalCount = validStats.reduce((sum, s) => sum + s.count, 0);
|
|
212
|
+
const totalSum = validStats.reduce((sum, s) => sum + s.sum, 0);
|
|
213
|
+
const mean = totalSum / totalCount;
|
|
214
|
+
|
|
215
|
+
// Combine variance (using parallel algorithm)
|
|
216
|
+
let totalVariance = 0;
|
|
217
|
+
for (const s of validStats) {
|
|
218
|
+
totalVariance += s.variance * s.count + Math.pow(s.mean - mean, 2) * s.count;
|
|
219
|
+
}
|
|
220
|
+
totalVariance /= totalCount;
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
count: totalCount,
|
|
224
|
+
sum: totalSum,
|
|
225
|
+
mean,
|
|
226
|
+
min: Math.min(...validStats.map((s) => s.min)),
|
|
227
|
+
max: Math.max(...validStats.map((s) => s.max)),
|
|
228
|
+
variance: totalVariance,
|
|
229
|
+
std: Math.sqrt(totalVariance),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Parse CSV data
|
|
235
|
+
* @param {string} csv - CSV content
|
|
236
|
+
* @param {Object} options - Parse options
|
|
237
|
+
* @returns {Promise<Array>} Parsed data
|
|
238
|
+
*/
|
|
239
|
+
async parseCSV(csv, options = {}) {
|
|
240
|
+
return this.pool.execute({
|
|
241
|
+
operation: 'parseCSV',
|
|
242
|
+
data: csv,
|
|
243
|
+
config: options,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Execute a pipeline of operations
|
|
249
|
+
* @param {Array} data - Input data
|
|
250
|
+
* @param {Array} operations - Array of operations
|
|
251
|
+
* @returns {Promise<Array>} Result data
|
|
252
|
+
*/
|
|
253
|
+
async pipeline(data, operations) {
|
|
254
|
+
return this.pool.execute({
|
|
255
|
+
operation: 'pipeline',
|
|
256
|
+
data,
|
|
257
|
+
config: operations,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Process large dataset with progress callbacks
|
|
263
|
+
* @param {Array} data - Input data
|
|
264
|
+
* @param {Object} config - Processing config
|
|
265
|
+
* @param {Function} onProgress - Progress callback
|
|
266
|
+
* @returns {Promise<Object>} Processing result
|
|
267
|
+
*/
|
|
268
|
+
async processLargeDataset(data, config, onProgress) {
|
|
269
|
+
const { operation, operationConfig } = config;
|
|
270
|
+
const chunks = this._chunkData(data, this.config.chunkSize);
|
|
271
|
+
const totalChunks = chunks.length;
|
|
272
|
+
const results = [];
|
|
273
|
+
|
|
274
|
+
// Process chunks in batches to avoid overwhelming the worker pool
|
|
275
|
+
const batchSize = this.config.maxWorkers;
|
|
276
|
+
for (let i = 0; i < chunks.length; i += batchSize) {
|
|
277
|
+
const batch = chunks.slice(i, i + batchSize);
|
|
278
|
+
const batchResults = await this.pool.executeAll(
|
|
279
|
+
batch.map((chunk) => ({
|
|
280
|
+
operation,
|
|
281
|
+
data: chunk,
|
|
282
|
+
config: operationConfig,
|
|
283
|
+
}))
|
|
284
|
+
);
|
|
285
|
+
results.push(...batchResults);
|
|
286
|
+
|
|
287
|
+
if (onProgress) {
|
|
288
|
+
onProgress({
|
|
289
|
+
processed: Math.min(i + batchSize, totalChunks),
|
|
290
|
+
total: totalChunks,
|
|
291
|
+
percent: Math.round((Math.min(i + batchSize, totalChunks) / totalChunks) * 100),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this._combineResults(results, operation);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Combine results from chunked processing
|
|
301
|
+
* @private
|
|
302
|
+
* @param {Array} results - Chunk results
|
|
303
|
+
* @param {string} operation - Operation type
|
|
304
|
+
* @returns {any} Combined result
|
|
305
|
+
*/
|
|
306
|
+
_combineResults(results, operation) {
|
|
307
|
+
switch (operation) {
|
|
308
|
+
case 'filter':
|
|
309
|
+
return results.flat();
|
|
310
|
+
case 'aggregate':
|
|
311
|
+
return results.reduce((a, b) => a + b, 0);
|
|
312
|
+
case 'statistics':
|
|
313
|
+
return this._combineStatistics(results);
|
|
314
|
+
default:
|
|
315
|
+
return results;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get processor statistics
|
|
321
|
+
* @returns {Object} Statistics
|
|
322
|
+
*/
|
|
323
|
+
getStats() {
|
|
324
|
+
return this.pool.getStats();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Terminate the processor and clean up resources
|
|
329
|
+
*/
|
|
330
|
+
terminate() {
|
|
331
|
+
terminateWorkerPool();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Singleton instance
|
|
336
|
+
let globalProcessor = null;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get or create global data processor
|
|
340
|
+
* @param {Object} config - Processor configuration
|
|
341
|
+
* @returns {DataProcessor} Global processor instance
|
|
342
|
+
*/
|
|
343
|
+
export function getDataProcessor(config = {}) {
|
|
344
|
+
if (!globalProcessor) {
|
|
345
|
+
globalProcessor = new DataProcessor(config);
|
|
346
|
+
}
|
|
347
|
+
return globalProcessor;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Terminate global data processor
|
|
352
|
+
*/
|
|
353
|
+
export function terminateDataProcessor() {
|
|
354
|
+
if (globalProcessor) {
|
|
355
|
+
globalProcessor.terminate();
|
|
356
|
+
globalProcessor = null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Convenience functions for common operations
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Aggregate data using workers
|
|
364
|
+
* @param {Array} data - Input data
|
|
365
|
+
* @param {Object} config - Aggregation config
|
|
366
|
+
* @returns {Promise<number>} Aggregated value
|
|
367
|
+
*/
|
|
368
|
+
export function aggregateData(data, config) {
|
|
369
|
+
return getDataProcessor().aggregate(data, config);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Filter data using workers
|
|
374
|
+
* @param {Array} data - Input data
|
|
375
|
+
* @param {Object} criteria - Filter criteria
|
|
376
|
+
* @returns {Promise<Array>} Filtered data
|
|
377
|
+
*/
|
|
378
|
+
export function filterData(data, criteria) {
|
|
379
|
+
return getDataProcessor().filter(data, criteria);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Sort data using workers
|
|
384
|
+
* @param {Array} data - Input data
|
|
385
|
+
* @param {Object} sortConfig - Sort config
|
|
386
|
+
* @returns {Promise<Array>} Sorted data
|
|
387
|
+
*/
|
|
388
|
+
export function sortData(data, sortConfig) {
|
|
389
|
+
return getDataProcessor().sort(data, sortConfig);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Sample data for preview
|
|
394
|
+
* @param {Array} data - Input data
|
|
395
|
+
* @param {number} sampleSize - Sample size
|
|
396
|
+
* @returns {Promise<Array>} Sampled data
|
|
397
|
+
*/
|
|
398
|
+
export function sampleData(data, sampleSize) {
|
|
399
|
+
return getDataProcessor().sample(data, sampleSize);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Calculate statistics
|
|
404
|
+
* @param {Array} data - Input data
|
|
405
|
+
* @param {string} field - Field name
|
|
406
|
+
* @returns {Promise<Object>} Statistics
|
|
407
|
+
*/
|
|
408
|
+
export function calculateStatistics(data, field) {
|
|
409
|
+
return getDataProcessor().statistics(data, field);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export { DataProcessor };
|
|
413
|
+
export default DataProcessor;
|