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.
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 -241
  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 -298
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -44
  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 -121
  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 -9
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -124
  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 -587
  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 -147
  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
@@ -0,0 +1,468 @@
1
+ /**
2
+ * @fileoverview Property Panel Component - Dynamic form for chart configuration
3
+ * @module editor/components/PropertyPanel
4
+ *
5
+ * Provides property editing for selected charts with dynamic forms based on chart type.
6
+ */
7
+
8
+ import React, { useState, useCallback, useEffect } from 'react';
9
+ import {
10
+ Sliders,
11
+ Database,
12
+ Palette,
13
+ Code,
14
+ ChevronDown,
15
+ ChevronUp,
16
+ } from 'react-bootstrap-icons';
17
+ import { Form, Button, Accordion, Row, Col } from 'react-bootstrap';
18
+ import { useEditor } from '../context/EditorContext';
19
+
20
+ /**
21
+ * Debounce hook for delaying updates
22
+ */
23
+ function useDebounce(value, delay) {
24
+ const [debouncedValue, setDebouncedValue] = useState(value);
25
+
26
+ useEffect(() => {
27
+ const timer = setTimeout(() => {
28
+ setDebouncedValue(value);
29
+ }, delay);
30
+
31
+ return () => clearTimeout(timer);
32
+ }, [value, delay]);
33
+
34
+ return debouncedValue;
35
+ }
36
+
37
+ /**
38
+ * Data Editor Component
39
+ */
40
+ function DataEditor({ chart, onChange }) {
41
+ const [dataText, setDataText] = useState('');
42
+ const [error, setError] = useState(null);
43
+
44
+ useEffect(() => {
45
+ try {
46
+ setDataText(JSON.stringify(chart.data, null, 2));
47
+ setError(null);
48
+ } catch (e) {
49
+ setError('Failed to serialize data');
50
+ }
51
+ }, [chart.data]);
52
+
53
+ const handleChange = useCallback((e) => {
54
+ setDataText(e.target.value);
55
+ }, []);
56
+
57
+ const handleBlur = useCallback(() => {
58
+ try {
59
+ const parsed = JSON.parse(dataText);
60
+ onChange('data', parsed);
61
+ setError(null);
62
+ } catch (e) {
63
+ setError(`Invalid JSON: ${e.message}`);
64
+ }
65
+ }, [dataText, onChange]);
66
+
67
+ return (
68
+ <div className="property-group">
69
+ <div className="property-group-title">
70
+ <Database className="me-1" size={14} />
71
+ Data
72
+ </div>
73
+ <Form.Group>
74
+ <Form.Control
75
+ as="textarea"
76
+ value={dataText}
77
+ onChange={handleChange}
78
+ onBlur={handleBlur}
79
+ className="data-editor-textarea"
80
+ isInvalid={!!error}
81
+ placeholder="Enter JSON data array..."
82
+ />
83
+ {error && (
84
+ <Form.Control.Feedback type="invalid">
85
+ {error}
86
+ </Form.Control.Feedback>
87
+ )}
88
+ <Form.Text className="text-muted">
89
+ Enter data as JSON array. Changes apply on blur.
90
+ </Form.Text>
91
+ </Form.Group>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Basic Properties Component
98
+ */
99
+ function BasicProperties({ chart, onChange }) {
100
+ const [localValues, setLocalValues] = useState({
101
+ title: chart.title || '',
102
+ width: chart.width || '100%',
103
+ height: chart.height || '400px',
104
+ });
105
+
106
+ const debouncedValues = useDebounce(localValues, 100);
107
+
108
+ useEffect(() => {
109
+ setLocalValues({
110
+ title: chart.title || '',
111
+ width: chart.width || '100%',
112
+ height: chart.height || '400px',
113
+ });
114
+ }, [chart.title, chart.width, chart.height]);
115
+
116
+ useEffect(() => {
117
+ Object.entries(debouncedValues).forEach(([key, value]) => {
118
+ if (value !== chart[key]) {
119
+ onChange(key, value);
120
+ }
121
+ });
122
+ }, [debouncedValues, chart, onChange]);
123
+
124
+ const handleChange = useCallback((field, value) => {
125
+ setLocalValues((prev) => ({ ...prev, [field]: value }));
126
+ }, []);
127
+
128
+ return (
129
+ <div className="property-group">
130
+ <div className="property-group-title">
131
+ <Sliders className="me-1" size={14} />
132
+ Basic Properties
133
+ </div>
134
+
135
+ <Form.Group className="property-field">
136
+ <Form.Label>Title</Form.Label>
137
+ <Form.Control
138
+ type="text"
139
+ value={localValues.title}
140
+ onChange={(e) => handleChange('title', e.target.value)}
141
+ placeholder="Chart title"
142
+ />
143
+ </Form.Group>
144
+
145
+ <Row>
146
+ <Col>
147
+ <Form.Group className="property-field">
148
+ <Form.Label>Width</Form.Label>
149
+ <Form.Control
150
+ type="text"
151
+ value={localValues.width}
152
+ onChange={(e) => handleChange('width', e.target.value)}
153
+ placeholder="100%"
154
+ />
155
+ </Form.Group>
156
+ </Col>
157
+ <Col>
158
+ <Form.Group className="property-field">
159
+ <Form.Label>Height</Form.Label>
160
+ <Form.Control
161
+ type="text"
162
+ value={localValues.height}
163
+ onChange={(e) => handleChange('height', e.target.value)}
164
+ placeholder="400px"
165
+ />
166
+ </Form.Group>
167
+ </Col>
168
+ </Row>
169
+ </div>
170
+ );
171
+ }
172
+
173
+ /**
174
+ * Axis Properties Component
175
+ */
176
+ function AxisProperties({ chart, onChange }) {
177
+ const is3D = chart.type.includes('3d');
178
+ const [axisOptions, setAxisOptions] = useState(chart.options || {});
179
+
180
+ useEffect(() => {
181
+ setAxisOptions(chart.options || {});
182
+ }, [chart.options]);
183
+
184
+ const handleAxisChange = useCallback(
185
+ (axis, field, value) => {
186
+ const newOptions = {
187
+ ...axisOptions,
188
+ [axis]: {
189
+ ...axisOptions[axis],
190
+ [field]: value,
191
+ },
192
+ };
193
+ setAxisOptions(newOptions);
194
+ onChange('options', newOptions);
195
+ },
196
+ [axisOptions, onChange]
197
+ );
198
+
199
+ const axes = is3D
200
+ ? [
201
+ { key: 'xAxis3D', label: 'X Axis (3D)' },
202
+ { key: 'yAxis3D', label: 'Y Axis (3D)' },
203
+ { key: 'zAxis3D', label: 'Z Axis (3D)' },
204
+ ]
205
+ : [
206
+ { key: 'xAxis', label: 'X Axis' },
207
+ { key: 'yAxis', label: 'Y Axis' },
208
+ ];
209
+
210
+ return (
211
+ <div className="property-group">
212
+ <div className="property-group-title">
213
+ <Palette className="me-1" size={14} />
214
+ Axis Configuration
215
+ </div>
216
+
217
+ {axes.map(({ key, label }) => (
218
+ <div key={key} className="mb-3">
219
+ <strong className="d-block mb-2 fs-7">{label}</strong>
220
+ <Form.Group className="property-field">
221
+ <Form.Label>Name</Form.Label>
222
+ <Form.Control
223
+ type="text"
224
+ value={axisOptions[key]?.name || ''}
225
+ onChange={(e) =>
226
+ handleAxisChange(key, 'name', e.target.value)
227
+ }
228
+ placeholder={`${label} name`}
229
+ />
230
+ </Form.Group>
231
+ <Form.Group className="property-field">
232
+ <Form.Label>Type</Form.Label>
233
+ <Form.Select
234
+ value={axisOptions[key]?.type || 'value'}
235
+ onChange={(e) =>
236
+ handleAxisChange(key, 'type', e.target.value)
237
+ }
238
+ >
239
+ <option value="value">Value</option>
240
+ <option value="category">Category</option>
241
+ <option value="time">Time</option>
242
+ <option value="log">Log</option>
243
+ </Form.Select>
244
+ </Form.Group>
245
+ </div>
246
+ ))}
247
+ </div>
248
+ );
249
+ }
250
+
251
+ /**
252
+ * Series Properties Component
253
+ */
254
+ function SeriesProperties({ chart, onChange }) {
255
+ const [seriesOptions, setSeriesOptions] = useState(chart.series || {});
256
+
257
+ useEffect(() => {
258
+ setSeriesOptions(chart.series || {});
259
+ }, [chart.series]);
260
+
261
+ const handleChange = useCallback(
262
+ (field, value) => {
263
+ const newSeries = { ...seriesOptions, [field]: value };
264
+ setSeriesOptions(newSeries);
265
+ onChange('series', newSeries);
266
+ },
267
+ [seriesOptions, onChange]
268
+ );
269
+
270
+ const renderTypeSpecificOptions = () => {
271
+ switch (chart.type) {
272
+ case 'scatter':
273
+ case 'scatter3d':
274
+ return (
275
+ <>
276
+ <Form.Group className="property-field">
277
+ <Form.Label>Symbol Size</Form.Label>
278
+ <Form.Control
279
+ type="number"
280
+ value={seriesOptions.symbolSize || 10}
281
+ onChange={(e) =>
282
+ handleChange(
283
+ 'symbolSize',
284
+ parseInt(e.target.value)
285
+ )
286
+ }
287
+ />
288
+ </Form.Group>
289
+ <Form.Group className="property-field">
290
+ <Form.Label>Symbol Type</Form.Label>
291
+ <Form.Select
292
+ value={seriesOptions.symbol || 'circle'}
293
+ onChange={(e) =>
294
+ handleChange('symbol', e.target.value)
295
+ }
296
+ >
297
+ <option value="circle">Circle</option>
298
+ <option value="rect">Rectangle</option>
299
+ <option value="triangle">Triangle</option>
300
+ <option value="diamond">Diamond</option>
301
+ <option value="pin">Pin</option>
302
+ </Form.Select>
303
+ </Form.Group>
304
+ </>
305
+ );
306
+
307
+ case 'line':
308
+ case 'line3d':
309
+ return (
310
+ <>
311
+ <Form.Group className="property-field">
312
+ <Form.Check
313
+ type="checkbox"
314
+ label="Smooth Line"
315
+ checked={seriesOptions.smooth || false}
316
+ onChange={(e) =>
317
+ handleChange('smooth', e.target.checked)
318
+ }
319
+ />
320
+ </Form.Group>
321
+ <Form.Group className="property-field">
322
+ <Form.Check
323
+ type="checkbox"
324
+ label="Show Area"
325
+ checked={seriesOptions.areaStyle !== undefined}
326
+ onChange={(e) =>
327
+ handleChange(
328
+ 'areaStyle',
329
+ e.target.checked ? {} : undefined
330
+ )
331
+ }
332
+ />
333
+ </Form.Group>
334
+ </>
335
+ );
336
+
337
+ case 'bar':
338
+ case 'bar3d':
339
+ return (
340
+ <Form.Group className="property-field">
341
+ <Form.Check
342
+ type="checkbox"
343
+ label="Stack Bars"
344
+ checked={seriesOptions.stack !== undefined}
345
+ onChange={(e) =>
346
+ handleChange(
347
+ 'stack',
348
+ e.target.checked ? 'total' : undefined
349
+ )
350
+ }
351
+ />
352
+ </Form.Group>
353
+ );
354
+
355
+ case 'pie':
356
+ return (
357
+ <>
358
+ <Form.Group className="property-field">
359
+ <Form.Label>Inner Radius (%)</Form.Label>
360
+ <Form.Control
361
+ type="text"
362
+ value={seriesOptions.radius?.[0] || '0%'}
363
+ onChange={(e) =>
364
+ handleChange('radius', [
365
+ e.target.value,
366
+ seriesOptions.radius?.[1] || '70%',
367
+ ])
368
+ }
369
+ />
370
+ </Form.Group>
371
+ <Form.Group className="property-field">
372
+ <Form.Label>Outer Radius (%)</Form.Label>
373
+ <Form.Control
374
+ type="text"
375
+ value={seriesOptions.radius?.[1] || '70%'}
376
+ onChange={(e) =>
377
+ handleChange('radius', [
378
+ seriesOptions.radius?.[0] || '0%',
379
+ e.target.value,
380
+ ])
381
+ }
382
+ />
383
+ </Form.Group>
384
+ </>
385
+ );
386
+
387
+ default:
388
+ return null;
389
+ }
390
+ };
391
+
392
+ return (
393
+ <div className="property-group">
394
+ <div className="property-group-title">
395
+ <Code className="me-1" size={14} />
396
+ Series Options
397
+ </div>
398
+ {renderTypeSpecificOptions()}
399
+ </div>
400
+ );
401
+ }
402
+
403
+ /**
404
+ * Property Panel Component
405
+ */
406
+ function PropertyPanel() {
407
+ const { state, updateChart } = useEditor();
408
+ const { selectedChart } = state;
409
+
410
+ const handlePropertyChange = useCallback(
411
+ (field, value) => {
412
+ if (selectedChart) {
413
+ updateChart(selectedChart.id, { [field]: value });
414
+ }
415
+ },
416
+ [selectedChart, updateChart]
417
+ );
418
+
419
+ if (!selectedChart) {
420
+ return (
421
+ <div className="editor-property-panel">
422
+ <div className="property-panel-header">Properties</div>
423
+ <div className="property-panel-empty">
424
+ <Sliders size={48} className="mb-3 opacity-25" />
425
+ <p className="text-muted">
426
+ Select a chart to edit its properties
427
+ </p>
428
+ </div>
429
+ </div>
430
+ );
431
+ }
432
+
433
+ return (
434
+ <div className="editor-property-panel">
435
+ <div className="property-panel-header">
436
+ Properties: {selectedChart.title}
437
+ <span className="badge bg-secondary ms-2 fs-7">
438
+ {selectedChart.type}
439
+ </span>
440
+ </div>
441
+ <div className="property-panel-content">
442
+ <BasicProperties
443
+ chart={selectedChart}
444
+ onChange={handlePropertyChange}
445
+ />
446
+
447
+ <DataEditor
448
+ chart={selectedChart}
449
+ onChange={handlePropertyChange}
450
+ />
451
+
452
+ {!['pie', 'funnel', 'radar'].includes(selectedChart.type) && (
453
+ <AxisProperties
454
+ chart={selectedChart}
455
+ onChange={handlePropertyChange}
456
+ />
457
+ )}
458
+
459
+ <SeriesProperties
460
+ chart={selectedChart}
461
+ onChange={handlePropertyChange}
462
+ />
463
+ </div>
464
+ </div>
465
+ );
466
+ }
467
+
468
+ export default PropertyPanel;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @fileoverview Status Bar Component
3
+ * @module editor/components/StatusBar
4
+ *
5
+ * Displays editor status information including chart count, auto-save status, and errors.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { CheckCircle, ExclamationCircle, Save } from 'react-bootstrap-icons';
10
+
11
+ /**
12
+ * Format time ago
13
+ */
14
+ function formatTimeAgo(date) {
15
+ if (!date) return 'Never';
16
+
17
+ const now = new Date();
18
+ const diff = now - new Date(date);
19
+ const seconds = Math.floor(diff / 1000);
20
+ const minutes = Math.floor(seconds / 60);
21
+ const hours = Math.floor(minutes / 60);
22
+
23
+ if (seconds < 60) {
24
+ return 'Just now';
25
+ } else if (minutes < 60) {
26
+ return `${minutes}m ago`;
27
+ } else if (hours < 24) {
28
+ return `${hours}h ago`;
29
+ } else {
30
+ return new Date(date).toLocaleDateString();
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Status Bar Component
36
+ */
37
+ function StatusBar({ chartCount, lastSaved, selectedChart }) {
38
+ const hasChanges = !lastSaved;
39
+
40
+ return (
41
+ <div className="editor-status-bar">
42
+ <div className="status-bar-left">
43
+ <span className="status-indicator">
44
+ <span
45
+ className={`status-indicator-dot ${chartCount > 0 ? 'success' : 'warning'}`}
46
+ />
47
+ {chartCount} chart{chartCount !== 1 ? 's' : ''}
48
+ </span>
49
+
50
+ {selectedChart && (
51
+ <span className="text-muted">
52
+ Selected: <strong>{selectedChart.title}</strong> ({selectedChart.type})
53
+ </span>
54
+ )}
55
+ </div>
56
+
57
+ <div className="status-bar-right">
58
+ <span className="status-indicator">
59
+ {hasChanges ? (
60
+ <>
61
+ <ExclamationCircle className="text-warning" size={14} />
62
+ <span className="text-warning">Unsaved changes</span>
63
+ </>
64
+ ) : (
65
+ <>
66
+ <CheckCircle className="text-success" size={14} />
67
+ <span>Saved</span>
68
+ </>
69
+ )}
70
+ </span>
71
+
72
+ <span className="status-indicator">
73
+ <Save size={14} />
74
+ <span>Auto-save: {formatTimeAgo(lastSaved)}</span>
75
+ </span>
76
+
77
+ <span className="text-muted">
78
+ Press <kbd>Ctrl+S</kbd> to export
79
+ </span>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ export default StatusBar;