visualifyjs 2.5.3 → 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.
Files changed (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -193
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -0
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -43
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -123
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -1
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -119
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -579
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -143
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -0
@@ -1,281 +1,236 @@
1
- import React, { useEffect, useState } from 'react';
2
- import simplefetch from '../fetch/fetch';
3
- import { useAppContext } from '../appContext';
4
-
5
- const generateNodes = (data, split_pattern) => {
6
- return data.reduce((groups, item) => {
7
- const parts = item.split(split_pattern);
8
- const main = parts[0];
9
- const sub = parts.slice(1).join(split_pattern);
10
-
11
- if (sub) {
12
- if (!groups[main]) {
13
- groups[main] = [];
14
- }
15
- groups[main].push(sub);
16
- } else {
17
- groups[main] = null;
18
- }
19
-
20
- return groups;
21
- }, {});
22
- };
23
-
24
- function Timeline({ props, style }) {
25
- const { debug } = props;
26
-
27
- const { style: _style_parsed = {} } = props;
28
- const { class: className, ...customStyle } = _style_parsed;
29
-
30
- // if debug is true, then border is red, else no border
31
- style = {
32
- ...style,
33
- border: debug ? '1px solid red' : 'none',
34
- maxHeight: '500px',
35
- overflowY: 'auto',
36
- overflowX: 'hidden',
37
- ...customStyle,
38
- position: 'relative',
39
- };
40
-
41
- const { id, val, config = {}, title } = props;
42
-
43
- const {
44
- split_pattern = '_',
45
- node_width = '50%',
46
- sort_pattern = new RegExp(/E(\d+)/),
47
- distance_pattern = undefined,
48
- basicGap = 1,
49
- } = config;
50
- // Render title if it exists
51
- const renderTitle = () => {
52
- return title && <h3>{title}</h3>;
53
- };
54
-
55
- const [nodes, setNodes] = useState([]);
56
-
57
- useEffect(() => {
58
- const { selection, urlval, rm_suffix } = props;
59
-
60
- const fetchData = async () => {
61
- try {
62
- const response = await simplefetch(selection, { key: urlval });
63
- // remove the suffix "_metadata" from the options
64
- try {
65
- const removed_suffix = response.map((item) =>
66
- item.replace(rm_suffix, ''),
67
- );
68
- //console.log('Removed suffix:', removed_suffix);
69
- const groupedNodes = generateNodes(
70
- removed_suffix,
71
- split_pattern,
72
- );
73
- setNodes(groupedNodes);
74
- } catch (error) {
75
- setNodes(response);
76
- }
77
- } catch (error) {
78
- console.error('Error fetching options:', error);
79
- }
80
- };
81
-
82
- fetchData();
83
- }, [props, split_pattern, debug]);
84
-
85
- const { setSharedData } = useAppContext();
86
-
87
- const handleNodeClick = (node) => {
88
- if (debug) console.log(`Node [${node}] clicked!`);
89
- if (val) {
90
- setSharedData((prevSharedData) => {
91
- return { ...prevSharedData, [val]: [node] };
92
- });
93
- alert(`Clicked: ${node}`);
94
- }
95
- };
96
-
97
- const _sort_pattern =
98
- typeof sort_pattern === 'string'
99
- ? new RegExp(sort_pattern)
100
- : sort_pattern;
101
-
102
- const sortedNodes = Object.keys(nodes)
103
- .sort((a, b) => {
104
- //console.log('a:', a, 'b:', b, 'sort_pattern:', _sort_pattern);
105
- const numA = parseInt(a.match(_sort_pattern)[1]);
106
- const numB = parseInt(b.match(_sort_pattern)[1]);
107
- return numA - numB;
108
- })
109
- .reduce((acc, key) => {
110
- acc[key] = nodes[key];
111
- return acc;
112
- }, {});
113
-
114
- const extractNumber = (str) => {
115
- const matchE = str.match(/E(\d+)/);
116
-
117
- if (distance_pattern && distance_pattern.regex) {
118
- const dis_regex =
119
- typeof distance_pattern?.regex === 'string'
120
- ? new RegExp(distance_pattern.regex)
121
- : distance_pattern.regex;
122
-
123
- const match = str.match(dis_regex);
124
-
125
- //console.log('str', str, 'match:', match, 'distance_pattern:', dis_regex);
126
- if (match) {
127
- for (let i = distance_pattern.pos; i >= 1; i--) {
128
- if (match[i]) {
129
- return parseInt(match[i]);
130
- }
131
- }
132
- }
133
- } else if (matchE) {
134
- return parseInt(matchE[1]);
135
- }
136
- return 0;
137
- };
138
-
139
- const calculateDistance = (main, previousMain) => {
140
- if (!previousMain) return 0;
141
-
142
- const numA = extractNumber(main);
143
- const numB = extractNumber(previousMain);
144
-
145
- //console.log('numA:', numA, 'numB:', numB, 'diff:', Math.abs(numA - numB));
146
-
147
- return Math.abs(numA - numB) * basicGap; // Distance based on the basic gap value
148
- };
149
-
150
- let previousMain = null;
151
-
152
- return (
153
- <div
154
- key={id}
155
- style={{ ...style }}
156
- className={className}>
157
- {renderTitle()}
158
- <div
159
- key={id + '.timeline'}
160
- style={{
161
- display: 'flex',
162
- flexDirection: 'column',
163
- alignItems: 'flex-start',
164
- position: 'relative',
165
- }}>
166
- <div
167
- style={{
168
- borderLeft: '10px solid black',
169
- //minHeight: '350px',
170
- height: '100%',
171
- position: 'absolute',
172
- left: '0px',
173
- top: '0px',
174
- }}
175
- />
176
- <div
177
- style={{ ...style }}
178
- className={className}>
179
- <div
180
- style={{
181
- maxHeight: '500px',
182
- overflowY: 'auto',
183
- overflowX: 'hidden',
184
- }}>
185
- {Object.keys(sortedNodes).map((main, index) => {
186
- const distance = calculateDistance(
187
- main,
188
- previousMain,
189
- );
190
- previousMain = main; // Update previousMain for the next iteration
191
- return (
192
- <div
193
- key={index}
194
- style={{
195
- display: 'flex',
196
- alignItems: 'center',
197
- marginTop: distance + 'px', // Apply distance here
198
- position: 'relative',
199
- flexDirection: 'column',
200
- }}>
201
- <div
202
- style={{
203
- position: 'absolute',
204
- left: '0%',
205
- top: '10px',
206
- width: node_width ?? '50%',
207
- height: '2px',
208
- background: 'black',
209
- }}
210
- />
211
- <div
212
- id='Timeline.Dot'
213
- style={{
214
- position: 'absolute',
215
- left: '0%',
216
- top: '15px',
217
- width: '10px',
218
- height: '10px',
219
- borderRadius: '50%',
220
- background: 'black',
221
- }}
222
- />
223
- {nodes[main] ? (
224
- <>
225
- <div style={{ marginLeft: '15%' }}>
226
- {main}
227
- </div>
228
- <div
229
- style={{
230
- marginLeft: '15%',
231
- display: 'flex',
232
- flexWrap: 'wrap',
233
- }}>
234
- {nodes[main].map(
235
- (sub, subIndex) => (
236
- <button
237
- key={subIndex}
238
- style={{
239
- padding:
240
- '5px 3px',
241
- marginLeft:
242
- '2px',
243
- border: '1px solid black',
244
- cursor: 'pointer',
245
- }}
246
- onClick={() =>
247
- handleNodeClick(
248
- `${main}_${sub}`,
249
- )
250
- }>
251
- {sub}
252
- </button>
253
- ),
254
- )}
255
- </div>
256
- </>
257
- ) : (
258
- <div
259
- style={{
260
- marginLeft: '15%',
261
- padding: '5px 3px',
262
- border: '1px solid black',
263
- cursor: 'pointer',
264
- }}
265
- onClick={() =>
266
- handleNodeClick(main)
267
- }>
268
- {main}
269
- </div>
270
- )}
271
- </div>
272
- );
273
- })}
274
- </div>
275
- </div>
276
- </div>
277
- </div>
278
- );
279
- }
280
-
281
- export default Timeline;
1
+ import React, { useEffect, useState } from 'react';
2
+ import simplefetch from '../fetch/fetch';
3
+ import { useAppContext } from '../appContext';
4
+
5
+ const generateNodes = (data, split_pattern) => {
6
+ return data.reduce((groups, item) => {
7
+ const parts = item.split(split_pattern);
8
+ const main = parts[0];
9
+ const sub = parts.slice(1).join(split_pattern);
10
+
11
+ if (sub) {
12
+ if (!groups[main]) {
13
+ groups[main] = [];
14
+ }
15
+ groups[main].push(sub);
16
+ } else {
17
+ groups[main] = null;
18
+ }
19
+
20
+ return groups;
21
+ }, {});
22
+ };
23
+
24
+ function Timeline({ props, style }) {
25
+ const { debug } = props;
26
+
27
+ const { style: _style_parsed = {} } = props;
28
+ const { class: className, ...customStyle } = _style_parsed;
29
+
30
+ // if debug is true, then border is red, else no border
31
+ style = {
32
+ ...style,
33
+ border: debug ? '1px solid red' : 'none',
34
+ maxHeight: '500px',
35
+ overflowY: 'auto',
36
+ overflowX: 'hidden',
37
+ ...customStyle,
38
+ position: 'relative',
39
+ };
40
+
41
+ const { id, val, config = {}, title } = props;
42
+
43
+ const {
44
+ split_pattern = '_',
45
+ node_width = '50%',
46
+ sort_pattern = new RegExp(/E(\d+)/),
47
+ distance_pattern = undefined,
48
+ basicGap = 1,
49
+ } = config;
50
+
51
+ const renderTitle = () => {
52
+ return title && <h3 className='v-control-title'>{title}</h3>;
53
+ };
54
+
55
+ const [nodes, setNodes] = useState([]);
56
+ const [activeNode, setActiveNode] = useState(null);
57
+
58
+ // Extract stable primitives from props for dependency tracking
59
+ const { selection, urlval, rm_suffix } = props;
60
+
61
+ useEffect(() => {
62
+ const fetchData = async () => {
63
+ try {
64
+ const response = await simplefetch(selection, { key: urlval });
65
+ try {
66
+ const removed_suffix = response.map((item) =>
67
+ item.replace(rm_suffix, ''),
68
+ );
69
+ const groupedNodes = generateNodes(
70
+ removed_suffix,
71
+ split_pattern,
72
+ );
73
+ setNodes(groupedNodes);
74
+ } catch (error) {
75
+ setNodes(response);
76
+ }
77
+ } catch (error) {
78
+ console.error('Error fetching options:', error);
79
+ }
80
+ };
81
+
82
+ fetchData();
83
+ }, [selection, urlval, rm_suffix, split_pattern, debug]);
84
+
85
+ const { setSharedData } = useAppContext();
86
+
87
+ const handleNodeClick = (node) => {
88
+ if (debug) console.log(`Node [${node}] clicked!`);
89
+ if (val) {
90
+ setSharedData((prevSharedData) => {
91
+ return { ...prevSharedData, [val]: [node] };
92
+ });
93
+ setActiveNode(node);
94
+ }
95
+ };
96
+
97
+ const _sort_pattern =
98
+ typeof sort_pattern === 'string'
99
+ ? new RegExp(sort_pattern)
100
+ : sort_pattern;
101
+
102
+ const sortedNodes = Object.keys(nodes)
103
+ .sort((a, b) => {
104
+ const numA = parseInt(a.match(_sort_pattern)[1]);
105
+ const numB = parseInt(b.match(_sort_pattern)[1]);
106
+ return numA - numB;
107
+ })
108
+ .reduce((acc, key) => {
109
+ acc[key] = nodes[key];
110
+ return acc;
111
+ }, {});
112
+
113
+ const extractNumber = (str) => {
114
+ const matchE = str.match(/E(\d+)/);
115
+
116
+ if (distance_pattern && distance_pattern.regex) {
117
+ const dis_regex =
118
+ typeof distance_pattern?.regex === 'string'
119
+ ? new RegExp(distance_pattern.regex)
120
+ : distance_pattern.regex;
121
+
122
+ const match = str.match(dis_regex);
123
+
124
+ if (match) {
125
+ for (let i = distance_pattern.pos; i >= 1; i--) {
126
+ if (match[i]) {
127
+ return parseInt(match[i]);
128
+ }
129
+ }
130
+ }
131
+ } else if (matchE) {
132
+ return parseInt(matchE[1]);
133
+ }
134
+ return 0;
135
+ };
136
+
137
+ const calculateDistance = (main, previousMain) => {
138
+ if (!previousMain) return 0;
139
+
140
+ const numA = extractNumber(main);
141
+ const numB = extractNumber(previousMain);
142
+
143
+ return Math.abs(numA - numB) * basicGap;
144
+ };
145
+
146
+ let previousMain = null;
147
+
148
+ // Check if a node group has any active sub-item
149
+ const isNodeActive = (main) => {
150
+ if (activeNode === main) return true;
151
+ if (nodes[main]) {
152
+ return nodes[main].some(
153
+ (sub) => activeNode === `${main}_${sub}`,
154
+ );
155
+ }
156
+ return false;
157
+ };
158
+
159
+ return (
160
+ <div
161
+ key={id}
162
+ style={{ ...style }}
163
+ className={className}>
164
+ {renderTitle()}
165
+ <div
166
+ key={id + '.timeline'}
167
+ className='v-timeline'
168
+ style={{
169
+ position: 'relative',
170
+ }}>
171
+ <div className='v-timeline-line' />
172
+ <div className='v-timeline-scroll'
173
+ style={{
174
+ maxHeight: '500px',
175
+ overflowY: 'auto',
176
+ overflowX: 'hidden',
177
+ }}>
178
+ {Object.keys(sortedNodes).map((main, index) => {
179
+ const distance = calculateDistance(
180
+ main,
181
+ previousMain,
182
+ );
183
+ previousMain = main;
184
+ const nodeActive = isNodeActive(main);
185
+ return (
186
+ <div
187
+ key={index}
188
+ className={`v-timeline-node${nodeActive ? ' has-active' : ''}`}
189
+ style={{
190
+ marginTop: distance + 'px',
191
+ }}>
192
+ <div className='v-timeline-branch' />
193
+ <div className='v-timeline-dot' />
194
+ <div className='v-timeline-content'>
195
+ {nodes[main] ? (
196
+ <>
197
+ <div className='v-timeline-label'>
198
+ {main}
199
+ </div>
200
+ <div className='v-timeline-btn-group'>
201
+ {nodes[main].map(
202
+ (sub, subIndex) => (
203
+ <button
204
+ key={subIndex}
205
+ className={`v-timeline-btn${activeNode === `${main}_${sub}` ? ' active' : ''}`}
206
+ onClick={() =>
207
+ handleNodeClick(
208
+ `${main}_${sub}`,
209
+ )
210
+ }>
211
+ {sub}
212
+ </button>
213
+ ),
214
+ )}
215
+ </div>
216
+ </>
217
+ ) : (
218
+ <div
219
+ className={`v-timeline-single${activeNode === main ? ' active' : ''}`}
220
+ onClick={() =>
221
+ handleNodeClick(main)
222
+ }>
223
+ {main}
224
+ </div>
225
+ )}
226
+ </div>
227
+ </div>
228
+ );
229
+ })}
230
+ </div>
231
+ </div>
232
+ </div>
233
+ );
234
+ }
235
+
236
+ export default Timeline;