scitex 2.7.0__py3-none-any.whl → 2.7.3__py3-none-any.whl
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.
- scitex/__init__.py +6 -2
- scitex/__version__.py +1 -1
- scitex/audio/README.md +52 -0
- scitex/audio/__init__.py +384 -0
- scitex/audio/__main__.py +129 -0
- scitex/audio/_tts.py +334 -0
- scitex/audio/engines/__init__.py +44 -0
- scitex/audio/engines/base.py +275 -0
- scitex/audio/engines/elevenlabs_engine.py +143 -0
- scitex/audio/engines/gtts_engine.py +162 -0
- scitex/audio/engines/pyttsx3_engine.py +131 -0
- scitex/audio/mcp_server.py +757 -0
- scitex/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_vis.py +1 -1
- scitex/dev/plt/__init__.py +272 -0
- scitex/dev/plt/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/plot_mpl_bar.py +29 -0
- scitex/dev/plt/plot_mpl_barh.py +29 -0
- scitex/dev/plt/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/plot_mpl_contour.py +31 -0
- scitex/dev/plt/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/plot_mpl_fill.py +30 -0
- scitex/dev/plt/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/plot_mpl_hist.py +28 -0
- scitex/dev/plt/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/plot_mpl_pie.py +29 -0
- scitex/dev/plt/plot_mpl_plot.py +29 -0
- scitex/dev/plt/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/plot_mpl_stem.py +29 -0
- scitex/dev/plt/plot_mpl_step.py +29 -0
- scitex/dev/plt/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/plot_sns_barplot.py +29 -0
- scitex/dev/plt/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/plot_sns_histplot.py +29 -0
- scitex/dev/plt/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/plot_stx_bar.py +29 -0
- scitex/dev/plt/plot_stx_barh.py +29 -0
- scitex/dev/plt/plot_stx_box.py +28 -0
- scitex/dev/plt/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/plot_stx_contour.py +31 -0
- scitex/dev/plt/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/plot_stx_fillv.py +28 -0
- scitex/dev/plt/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/plot_stx_image.py +28 -0
- scitex/dev/plt/plot_stx_imshow.py +28 -0
- scitex/dev/plt/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/plot_stx_kde.py +28 -0
- scitex/dev/plt/plot_stx_line.py +28 -0
- scitex/dev/plt/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/plot_stx_raster.py +28 -0
- scitex/dev/plt/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/plot_stx_scatter.py +29 -0
- scitex/dev/plt/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/plot_stx_violin.py +28 -0
- scitex/dev/plt/plot_stx_violinplot.py +28 -0
- scitex/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/_parser.py +1 -1
- scitex/{vis → fig}/canvas.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/fig/editor/_edit.py +751 -0
- scitex/{vis → fig}/editor/_qt_editor.py +181 -1
- scitex/fig/editor/flask_editor/_bbox.py +1276 -0
- scitex/fig/editor/flask_editor/_core.py +624 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +739 -0
- scitex/{vis → fig}/editor/flask_editor/templates/__init__.py +1 -1
- scitex/fig/editor/flask_editor/templates/_html.py +834 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +3136 -0
- scitex/{vis → fig}/editor/flask_editor/templates/_styles.py +625 -18
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +973 -0
- scitex/{vis → fig}/io/_canvas.py +1 -1
- scitex/{vis → fig}/io/_data.py +1 -1
- scitex/{vis → fig}/io/_export.py +1 -1
- scitex/{vis → fig}/io/_load.py +1 -1
- scitex/{vis → fig}/io/_panel.py +1 -1
- scitex/{vis → fig}/io/_save.py +1 -1
- scitex/{vis → fig}/model/__init__.py +1 -1
- scitex/{vis → fig}/model/_annotations.py +1 -1
- scitex/{vis → fig}/model/_axes.py +1 -1
- scitex/{vis → fig}/model/_figure.py +1 -1
- scitex/{vis → fig}/model/_guides.py +1 -1
- scitex/{vis → fig}/model/_plot.py +1 -1
- scitex/{vis → fig}/model/_styles.py +1 -1
- scitex/{vis → fig}/utils/__init__.py +1 -1
- scitex/io/__init__.py +10 -26
- scitex/io/_bundle.py +434 -0
- scitex/io/_flush.py +5 -2
- scitex/io/_load.py +98 -0
- scitex/io/_load_modules/_H5Explorer.py +5 -2
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_image.py +3 -4
- scitex/io/_load_modules/_txt.py +4 -2
- scitex/io/_metadata.py +34 -324
- scitex/io/_metadata_modules/__init__.py +46 -0
- scitex/io/_metadata_modules/_embed.py +70 -0
- scitex/io/_metadata_modules/_read.py +64 -0
- scitex/io/_metadata_modules/_utils.py +79 -0
- scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
- scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
- scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
- scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
- scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
- scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
- scitex/io/_metadata_modules/read_metadata_png.py +39 -0
- scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
- scitex/io/_qr_utils.py +5 -3
- scitex/io/_save.py +548 -30
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_image.py +5 -9
- scitex/io/_save_modules/_tex.py +7 -4
- scitex/io/utils/h5_to_zarr.py +11 -9
- scitex/msword/__init__.py +255 -0
- scitex/msword/profiles.py +357 -0
- scitex/msword/reader.py +753 -0
- scitex/msword/utils.py +289 -0
- scitex/msword/writer.py +362 -0
- scitex/plt/__init__.py +5 -2
- scitex/plt/_subplots/_AxesWrapper.py +6 -6
- scitex/plt/_subplots/_AxisWrapper.py +15 -9
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
- scitex/plt/_subplots/_FigWrapper.py +6 -6
- scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
- scitex/plt/_subplots/_export_as_csv.py +35 -5
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
- scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
- scitex/plt/gallery/_generate.py +421 -14
- scitex/plt/io/__init__.py +53 -0
- scitex/plt/io/_bundle.py +490 -0
- scitex/plt/io/_layered_bundle.py +1343 -0
- scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
- scitex/plt/styles/__init__.py +14 -0
- scitex/plt/styles/presets.py +78 -0
- scitex/plt/utils/__init__.py +13 -1
- scitex/plt/utils/_collect_figure_metadata.py +10 -14
- scitex/plt/utils/_configure_mpl.py +6 -18
- scitex/plt/utils/_crop.py +32 -14
- scitex/plt/utils/_csv_column_naming.py +54 -0
- scitex/plt/utils/_figure_mm.py +116 -1
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/metadata/__init__.py +25 -0
- scitex/plt/utils/metadata/_core.py +9 -10
- scitex/plt/utils/metadata/_dimensions.py +6 -3
- scitex/plt/utils/metadata/_editable_export.py +405 -0
- scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
- scitex/schema/__init__.py +109 -16
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +117 -0
- scitex/stats/io/__init__.py +29 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/tex/__init__.py +4 -0
- scitex/tex/_export.py +890 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/RECORD +238 -170
- scitex/io/memo.md +0 -2827
- scitex/plt/REQUESTS.md +0 -191
- scitex/plt/_subplots/TODO.md +0 -53
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
- scitex/vis/__init__.py +0 -177
- scitex/vis/editor/_edit.py +0 -390
- scitex/vis/editor/flask_editor/_bbox.py +0 -529
- scitex/vis/editor/flask_editor/_core.py +0 -168
- scitex/vis/editor/flask_editor/_renderer.py +0 -393
- scitex/vis/editor/flask_editor/templates/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- /scitex/{vis → fig}/README.md +0 -0
- /scitex/{vis → fig}/backend/__init__.py +0 -0
- /scitex/{vis → fig}/backend/_export.py +0 -0
- /scitex/{vis → fig}/backend/_render.py +0 -0
- /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{vis → fig}/editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/_dearpygui_editor.py +0 -0
- /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
- /scitex/{vis → fig}/editor/_mpl_editor.py +0 -0
- /scitex/{vis → fig}/editor/_tkinter_editor.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
- /scitex/{vis → fig}/io/_directory.py +0 -0
- /scitex/{vis → fig}/model/_plot_types.py +0 -0
- /scitex/{vis → fig}/utils/_defaults.py +0 -0
- /scitex/{vis → fig}/utils/_validate.py +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1261 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
# File: ./src/scitex/vis/editor/flask_editor/templates/scripts.py
|
|
4
|
-
"""JavaScript for the Flask editor UI."""
|
|
5
|
-
|
|
6
|
-
JS_SCRIPTS = """
|
|
7
|
-
let overrides = {{ overrides|safe }};
|
|
8
|
-
let traces = overrides.traces || [];
|
|
9
|
-
let elementBboxes = {};
|
|
10
|
-
let imgSize = {width: 0, height: 0};
|
|
11
|
-
let hoveredElement = null;
|
|
12
|
-
let selectedElement = null;
|
|
13
|
-
|
|
14
|
-
// Cycle selection state for overlapping elements
|
|
15
|
-
let elementsAtCursor = []; // All elements at current cursor position
|
|
16
|
-
let currentCycleIndex = 0; // Current index in cycle
|
|
17
|
-
|
|
18
|
-
// Unit system state (default: mm)
|
|
19
|
-
let dimensionUnit = 'mm';
|
|
20
|
-
const MM_TO_INCH = 1 / 25.4;
|
|
21
|
-
const INCH_TO_MM = 25.4;
|
|
22
|
-
|
|
23
|
-
// Hover system - client-side hit testing
|
|
24
|
-
function initHoverSystem() {
|
|
25
|
-
const container = document.getElementById('preview-container');
|
|
26
|
-
const img = document.getElementById('preview-img');
|
|
27
|
-
|
|
28
|
-
img.addEventListener('mousemove', (e) => {
|
|
29
|
-
if (imgSize.width === 0 || imgSize.height === 0) return;
|
|
30
|
-
|
|
31
|
-
const rect = img.getBoundingClientRect();
|
|
32
|
-
const x = e.clientX - rect.left;
|
|
33
|
-
const y = e.clientY - rect.top;
|
|
34
|
-
|
|
35
|
-
const scaleX = imgSize.width / rect.width;
|
|
36
|
-
const scaleY = imgSize.height / rect.height;
|
|
37
|
-
const imgX = x * scaleX;
|
|
38
|
-
const imgY = y * scaleY;
|
|
39
|
-
|
|
40
|
-
const element = findElementAt(imgX, imgY);
|
|
41
|
-
if (element !== hoveredElement) {
|
|
42
|
-
hoveredElement = element;
|
|
43
|
-
updateOverlay();
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
img.addEventListener('mouseleave', () => {
|
|
48
|
-
hoveredElement = null;
|
|
49
|
-
updateOverlay();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
img.addEventListener('click', (e) => {
|
|
53
|
-
const rect = img.getBoundingClientRect();
|
|
54
|
-
const x = e.clientX - rect.left;
|
|
55
|
-
const y = e.clientY - rect.top;
|
|
56
|
-
const scaleX = imgSize.width / rect.width;
|
|
57
|
-
const scaleY = imgSize.height / rect.height;
|
|
58
|
-
const imgX = x * scaleX;
|
|
59
|
-
const imgY = y * scaleY;
|
|
60
|
-
|
|
61
|
-
// Alt+click or find all overlapping elements
|
|
62
|
-
if (e.altKey) {
|
|
63
|
-
// Cycle through overlapping elements
|
|
64
|
-
const allElements = findAllElementsAt(imgX, imgY);
|
|
65
|
-
if (allElements.length > 0) {
|
|
66
|
-
// If cursor moved to different location, reset cycle
|
|
67
|
-
if (JSON.stringify(allElements) !== JSON.stringify(elementsAtCursor)) {
|
|
68
|
-
elementsAtCursor = allElements;
|
|
69
|
-
currentCycleIndex = 0;
|
|
70
|
-
} else {
|
|
71
|
-
// Cycle to next element
|
|
72
|
-
currentCycleIndex = (currentCycleIndex + 1) % elementsAtCursor.length;
|
|
73
|
-
}
|
|
74
|
-
selectedElement = elementsAtCursor[currentCycleIndex];
|
|
75
|
-
updateOverlay();
|
|
76
|
-
scrollToSection(selectedElement);
|
|
77
|
-
|
|
78
|
-
// Show cycle indicator in status
|
|
79
|
-
const total = elementsAtCursor.length;
|
|
80
|
-
const current = currentCycleIndex + 1;
|
|
81
|
-
console.log(`Cycle selection: ${current}/${total} - ${selectedElement}`);
|
|
82
|
-
}
|
|
83
|
-
} else if (hoveredElement) {
|
|
84
|
-
// Normal click - select hovered element
|
|
85
|
-
selectedElement = hoveredElement;
|
|
86
|
-
elementsAtCursor = []; // Reset cycle
|
|
87
|
-
currentCycleIndex = 0;
|
|
88
|
-
updateOverlay();
|
|
89
|
-
scrollToSection(selectedElement);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Right-click for cycle selection menu
|
|
94
|
-
img.addEventListener('contextmenu', (e) => {
|
|
95
|
-
e.preventDefault();
|
|
96
|
-
|
|
97
|
-
const rect = img.getBoundingClientRect();
|
|
98
|
-
const x = e.clientX - rect.left;
|
|
99
|
-
const y = e.clientY - rect.top;
|
|
100
|
-
const scaleX = imgSize.width / rect.width;
|
|
101
|
-
const scaleY = imgSize.height / rect.height;
|
|
102
|
-
const imgX = x * scaleX;
|
|
103
|
-
const imgY = y * scaleY;
|
|
104
|
-
|
|
105
|
-
const allElements = findAllElementsAt(imgX, imgY);
|
|
106
|
-
if (allElements.length > 1) {
|
|
107
|
-
// Cycle to next element
|
|
108
|
-
if (JSON.stringify(allElements) !== JSON.stringify(elementsAtCursor)) {
|
|
109
|
-
elementsAtCursor = allElements;
|
|
110
|
-
currentCycleIndex = 0;
|
|
111
|
-
} else {
|
|
112
|
-
currentCycleIndex = (currentCycleIndex + 1) % elementsAtCursor.length;
|
|
113
|
-
}
|
|
114
|
-
selectedElement = elementsAtCursor[currentCycleIndex];
|
|
115
|
-
updateOverlay();
|
|
116
|
-
scrollToSection(selectedElement);
|
|
117
|
-
|
|
118
|
-
const total = elementsAtCursor.length;
|
|
119
|
-
const current = currentCycleIndex + 1;
|
|
120
|
-
console.log(`Right-click cycle: ${current}/${total} - ${selectedElement}`);
|
|
121
|
-
} else if (allElements.length === 1) {
|
|
122
|
-
selectedElement = allElements[0];
|
|
123
|
-
updateOverlay();
|
|
124
|
-
scrollToSection(selectedElement);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
img.addEventListener('load', () => {
|
|
129
|
-
updateOverlay();
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function findElementAt(x, y) {
|
|
134
|
-
// Multi-panel aware hit detection with specificity hierarchy:
|
|
135
|
-
// 1. Data elements with points (lines, scatter) - proximity detection
|
|
136
|
-
// 2. Small elements (labels, ticks, legends, bars, fills)
|
|
137
|
-
// 3. Panel bboxes - lowest priority (fallback)
|
|
138
|
-
|
|
139
|
-
const PROXIMITY_THRESHOLD = 15;
|
|
140
|
-
const SCATTER_THRESHOLD = 20; // Larger threshold for scatter points
|
|
141
|
-
|
|
142
|
-
// First: Check for data elements with points (lines, scatter)
|
|
143
|
-
let closestDataElement = null;
|
|
144
|
-
let minDistance = Infinity;
|
|
145
|
-
|
|
146
|
-
for (const [name, bbox] of Object.entries(elementBboxes)) {
|
|
147
|
-
if (bbox.points && bbox.points.length > 0) {
|
|
148
|
-
// Check if cursor is within general bbox area first
|
|
149
|
-
if (x >= bbox.x0 - SCATTER_THRESHOLD && x <= bbox.x1 + SCATTER_THRESHOLD &&
|
|
150
|
-
y >= bbox.y0 - SCATTER_THRESHOLD && y <= bbox.y1 + SCATTER_THRESHOLD) {
|
|
151
|
-
|
|
152
|
-
const elementType = bbox.element_type || 'line';
|
|
153
|
-
let dist;
|
|
154
|
-
|
|
155
|
-
if (elementType === 'scatter') {
|
|
156
|
-
// For scatter, find distance to nearest point
|
|
157
|
-
dist = distanceToNearestPoint(x, y, bbox.points);
|
|
158
|
-
} else {
|
|
159
|
-
// For lines, find distance to line segments
|
|
160
|
-
dist = distanceToLine(x, y, bbox.points);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (dist < minDistance) {
|
|
164
|
-
minDistance = dist;
|
|
165
|
-
closestDataElement = name;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Use appropriate threshold based on element type
|
|
172
|
-
if (closestDataElement) {
|
|
173
|
-
const bbox = elementBboxes[closestDataElement];
|
|
174
|
-
const threshold = (bbox.element_type === 'scatter') ? SCATTER_THRESHOLD : PROXIMITY_THRESHOLD;
|
|
175
|
-
if (minDistance <= threshold) {
|
|
176
|
-
return closestDataElement;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Second: Collect all bbox matches, excluding panels and data elements with points
|
|
181
|
-
const elementMatches = [];
|
|
182
|
-
const panelMatches = [];
|
|
183
|
-
|
|
184
|
-
for (const [name, bbox] of Object.entries(elementBboxes)) {
|
|
185
|
-
if (x >= bbox.x0 && x <= bbox.x1 && y >= bbox.y0 && y <= bbox.y1) {
|
|
186
|
-
const area = (bbox.x1 - bbox.x0) * (bbox.y1 - bbox.y0);
|
|
187
|
-
const isPanel = bbox.is_panel || name.endsWith('_panel');
|
|
188
|
-
const hasPoints = bbox.points && bbox.points.length > 0;
|
|
189
|
-
|
|
190
|
-
if (hasPoints) {
|
|
191
|
-
// Already handled above with proximity
|
|
192
|
-
continue;
|
|
193
|
-
} else if (isPanel) {
|
|
194
|
-
panelMatches.push({name, area, bbox});
|
|
195
|
-
} else {
|
|
196
|
-
elementMatches.push({name, area, bbox});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Return smallest non-panel element if any
|
|
202
|
-
if (elementMatches.length > 0) {
|
|
203
|
-
elementMatches.sort((a, b) => a.area - b.area);
|
|
204
|
-
return elementMatches[0].name;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Fallback to panel selection (useful for multi-panel figures)
|
|
208
|
-
if (panelMatches.length > 0) {
|
|
209
|
-
panelMatches.sort((a, b) => a.area - b.area);
|
|
210
|
-
return panelMatches[0].name;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function distanceToNearestPoint(px, py, points) {
|
|
217
|
-
// Find distance to nearest point in scatter
|
|
218
|
-
let minDist = Infinity;
|
|
219
|
-
for (const [x, y] of points) {
|
|
220
|
-
const dist = Math.sqrt((px - x) ** 2 + (py - y) ** 2);
|
|
221
|
-
if (dist < minDist) minDist = dist;
|
|
222
|
-
}
|
|
223
|
-
return minDist;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function distanceToLine(px, py, points) {
|
|
227
|
-
let minDist = Infinity;
|
|
228
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
229
|
-
const [x1, y1] = points[i];
|
|
230
|
-
const [x2, y2] = points[i + 1];
|
|
231
|
-
const dist = distanceToSegment(px, py, x1, y1, x2, y2);
|
|
232
|
-
if (dist < minDist) minDist = dist;
|
|
233
|
-
}
|
|
234
|
-
return minDist;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function distanceToSegment(px, py, x1, y1, x2, y2) {
|
|
238
|
-
const dx = x2 - x1;
|
|
239
|
-
const dy = y2 - y1;
|
|
240
|
-
const lenSq = dx * dx + dy * dy;
|
|
241
|
-
|
|
242
|
-
if (lenSq === 0) {
|
|
243
|
-
return Math.sqrt((px - x1) ** 2 + (py - y1) ** 2);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
let t = ((px - x1) * dx + (py - y1) * dy) / lenSq;
|
|
247
|
-
t = Math.max(0, Math.min(1, t));
|
|
248
|
-
|
|
249
|
-
const projX = x1 + t * dx;
|
|
250
|
-
const projY = y1 + t * dy;
|
|
251
|
-
|
|
252
|
-
return Math.sqrt((px - projX) ** 2 + (py - projY) ** 2);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function findAllElementsAt(x, y) {
|
|
256
|
-
// Find all elements at cursor position (for cycle selection)
|
|
257
|
-
// Returns array sorted by specificity (most specific first)
|
|
258
|
-
const PROXIMITY_THRESHOLD = 15;
|
|
259
|
-
const SCATTER_THRESHOLD = 20;
|
|
260
|
-
|
|
261
|
-
const results = [];
|
|
262
|
-
|
|
263
|
-
for (const [name, bbox] of Object.entries(elementBboxes)) {
|
|
264
|
-
let match = false;
|
|
265
|
-
let distance = Infinity;
|
|
266
|
-
let priority = 0; // Lower = more specific
|
|
267
|
-
|
|
268
|
-
const hasPoints = bbox.points && bbox.points.length > 0;
|
|
269
|
-
const elementType = bbox.element_type || '';
|
|
270
|
-
const isPanel = bbox.is_panel || name.endsWith('_panel');
|
|
271
|
-
|
|
272
|
-
// Check data elements with points (lines, scatter)
|
|
273
|
-
if (hasPoints) {
|
|
274
|
-
if (x >= bbox.x0 - SCATTER_THRESHOLD && x <= bbox.x1 + SCATTER_THRESHOLD &&
|
|
275
|
-
y >= bbox.y0 - SCATTER_THRESHOLD && y <= bbox.y1 + SCATTER_THRESHOLD) {
|
|
276
|
-
|
|
277
|
-
if (elementType === 'scatter') {
|
|
278
|
-
distance = distanceToNearestPoint(x, y, bbox.points);
|
|
279
|
-
if (distance <= SCATTER_THRESHOLD) {
|
|
280
|
-
match = true;
|
|
281
|
-
priority = 1; // Scatter points = high priority
|
|
282
|
-
}
|
|
283
|
-
} else {
|
|
284
|
-
distance = distanceToLine(x, y, bbox.points);
|
|
285
|
-
if (distance <= PROXIMITY_THRESHOLD) {
|
|
286
|
-
match = true;
|
|
287
|
-
priority = 2; // Lines = high priority
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Check bbox containment
|
|
294
|
-
if (x >= bbox.x0 && x <= bbox.x1 && y >= bbox.y0 && y <= bbox.y1) {
|
|
295
|
-
const area = (bbox.x1 - bbox.x0) * (bbox.y1 - bbox.y0);
|
|
296
|
-
|
|
297
|
-
if (!match) {
|
|
298
|
-
match = true;
|
|
299
|
-
distance = 0;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (isPanel) {
|
|
303
|
-
priority = 100; // Panels = lowest priority
|
|
304
|
-
} else if (!hasPoints) {
|
|
305
|
-
// Small elements like labels, ticks - use area for priority
|
|
306
|
-
priority = 10 + Math.min(area / 10000, 50);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (match) {
|
|
311
|
-
results.push({ name, distance, priority, bbox });
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Sort by priority (lower first), then by distance
|
|
316
|
-
results.sort((a, b) => {
|
|
317
|
-
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
318
|
-
return a.distance - b.distance;
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
return results.map(r => r.name);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function drawTracePath(bbox, scaleX, scaleY, type) {
|
|
325
|
-
if (!bbox.points || bbox.points.length < 2) return '';
|
|
326
|
-
|
|
327
|
-
const points = bbox.points;
|
|
328
|
-
let pathD = `M ${points[0][0] * scaleX} ${points[0][1] * scaleY}`;
|
|
329
|
-
for (let i = 1; i < points.length; i++) {
|
|
330
|
-
pathD += ` L ${points[i][0] * scaleX} ${points[i][1] * scaleY}`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const className = type === 'hover' ? 'hover-path' : 'selected-path';
|
|
334
|
-
const labelX = points[0][0] * scaleX;
|
|
335
|
-
const labelY = points[0][1] * scaleY - 8;
|
|
336
|
-
const labelClass = type === 'hover' ? 'hover-label' : 'selected-label';
|
|
337
|
-
|
|
338
|
-
return `<path class="${className}" d="${pathD}"/>` +
|
|
339
|
-
`<text class="${labelClass}" x="${labelX}" y="${labelY}">${bbox.label}</text>`;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function drawScatterPoints(bbox, scaleX, scaleY, type) {
|
|
343
|
-
// Draw scatter points as circles
|
|
344
|
-
if (!bbox.points || bbox.points.length === 0) return '';
|
|
345
|
-
|
|
346
|
-
const className = type === 'hover' ? 'hover-scatter' : 'selected-scatter';
|
|
347
|
-
const labelClass = type === 'hover' ? 'hover-label' : 'selected-label';
|
|
348
|
-
const radius = 4;
|
|
349
|
-
|
|
350
|
-
let svg = '';
|
|
351
|
-
for (const [x, y] of bbox.points) {
|
|
352
|
-
svg += `<circle class="${className}" cx="${x * scaleX}" cy="${y * scaleY}" r="${radius}"/>`;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Add label near first point
|
|
356
|
-
if (bbox.points.length > 0) {
|
|
357
|
-
const labelX = bbox.points[0][0] * scaleX;
|
|
358
|
-
const labelY = bbox.points[0][1] * scaleY - 10;
|
|
359
|
-
svg += `<text class="${labelClass}" x="${labelX}" y="${labelY}">${bbox.label}</text>`;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return svg;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function updateOverlay() {
|
|
366
|
-
const overlay = document.getElementById('hover-overlay');
|
|
367
|
-
const img = document.getElementById('preview-img');
|
|
368
|
-
const rect = img.getBoundingClientRect();
|
|
369
|
-
|
|
370
|
-
overlay.setAttribute('width', rect.width);
|
|
371
|
-
overlay.setAttribute('height', rect.height);
|
|
372
|
-
|
|
373
|
-
const scaleX = rect.width / imgSize.width;
|
|
374
|
-
const scaleY = rect.height / imgSize.height;
|
|
375
|
-
|
|
376
|
-
let svg = '';
|
|
377
|
-
|
|
378
|
-
function drawElement(elementName, type) {
|
|
379
|
-
const bbox = elementBboxes[elementName];
|
|
380
|
-
if (!bbox) return '';
|
|
381
|
-
|
|
382
|
-
const elementType = bbox.element_type || '';
|
|
383
|
-
const hasPoints = bbox.points && bbox.points.length > 0;
|
|
384
|
-
|
|
385
|
-
// Lines - draw as path
|
|
386
|
-
if ((elementType === 'line' || elementName.includes('trace_')) && hasPoints) {
|
|
387
|
-
return drawTracePath(bbox, scaleX, scaleY, type);
|
|
388
|
-
}
|
|
389
|
-
// Scatter - draw as circles
|
|
390
|
-
else if (elementType === 'scatter' && hasPoints) {
|
|
391
|
-
return drawScatterPoints(bbox, scaleX, scaleY, type);
|
|
392
|
-
}
|
|
393
|
-
// Default - draw bbox rectangle
|
|
394
|
-
else {
|
|
395
|
-
const rectClass = type === 'hover' ? 'hover-rect' : 'selected-rect';
|
|
396
|
-
const labelClass = type === 'hover' ? 'hover-label' : 'selected-label';
|
|
397
|
-
const x = bbox.x0 * scaleX - 2;
|
|
398
|
-
const y = bbox.y0 * scaleY - 2;
|
|
399
|
-
const w = (bbox.x1 - bbox.x0) * scaleX + 4;
|
|
400
|
-
const h = (bbox.y1 - bbox.y0) * scaleY + 4;
|
|
401
|
-
return `<rect class="${rectClass}" x="${x}" y="${y}" width="${w}" height="${h}" rx="2"/>` +
|
|
402
|
-
`<text class="${labelClass}" x="${x}" y="${y - 4}">${bbox.label}</text>`;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (hoveredElement && hoveredElement !== selectedElement) {
|
|
407
|
-
svg += drawElement(hoveredElement, 'hover');
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (selectedElement) {
|
|
411
|
-
svg += drawElement(selectedElement, 'selected');
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
overlay.innerHTML = svg;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function expandSection(sectionId) {
|
|
418
|
-
document.querySelectorAll('.section').forEach(section => {
|
|
419
|
-
const header = section.querySelector('.section-header');
|
|
420
|
-
const content = section.querySelector('.section-content');
|
|
421
|
-
if (section.id === sectionId) {
|
|
422
|
-
header?.classList.remove('collapsed');
|
|
423
|
-
content?.classList.remove('collapsed');
|
|
424
|
-
} else if (header?.classList.contains('section-toggle')) {
|
|
425
|
-
header?.classList.add('collapsed');
|
|
426
|
-
content?.classList.add('collapsed');
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function scrollToSection(elementName) {
|
|
432
|
-
const elementToSection = {
|
|
433
|
-
'title': 'section-labels',
|
|
434
|
-
'xlabel': 'section-labels',
|
|
435
|
-
'ylabel': 'section-labels',
|
|
436
|
-
'xaxis_ticks': 'section-ticks',
|
|
437
|
-
'yaxis_ticks': 'section-ticks',
|
|
438
|
-
'legend': 'section-legend'
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
const fieldMap = {
|
|
442
|
-
'title': 'title',
|
|
443
|
-
'xlabel': 'xlabel',
|
|
444
|
-
'ylabel': 'ylabel',
|
|
445
|
-
'xaxis_ticks': 'x_n_ticks',
|
|
446
|
-
'yaxis_ticks': 'y_n_ticks',
|
|
447
|
-
'legend': 'legend_visible'
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
if (elementName.startsWith('trace_')) {
|
|
451
|
-
expandSection('section-traces');
|
|
452
|
-
const traceIdx = elementBboxes[elementName]?.trace_idx;
|
|
453
|
-
if (traceIdx !== undefined) {
|
|
454
|
-
const traceColors = document.querySelectorAll('.trace-color');
|
|
455
|
-
if (traceColors[traceIdx]) {
|
|
456
|
-
setTimeout(() => {
|
|
457
|
-
traceColors[traceIdx].scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
458
|
-
traceColors[traceIdx].click();
|
|
459
|
-
}, 100);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const sectionId = elementToSection[elementName];
|
|
466
|
-
if (sectionId) {
|
|
467
|
-
expandSection(sectionId);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const fieldId = fieldMap[elementName];
|
|
471
|
-
if (fieldId) {
|
|
472
|
-
const field = document.getElementById(fieldId);
|
|
473
|
-
if (field) {
|
|
474
|
-
setTimeout(() => {
|
|
475
|
-
field.scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
476
|
-
field.focus();
|
|
477
|
-
}, 100);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Always show selected element panel
|
|
482
|
-
showSelectedElementPanel(elementName);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Selected element panel management
|
|
486
|
-
function showSelectedElementPanel(elementName) {
|
|
487
|
-
const section = document.getElementById('section-selected');
|
|
488
|
-
const titleEl = document.getElementById('selected-element-title');
|
|
489
|
-
const typeBadge = document.getElementById('element-type-badge');
|
|
490
|
-
const axisInfo = document.getElementById('element-axis-info');
|
|
491
|
-
|
|
492
|
-
// Hide all property sections first
|
|
493
|
-
document.querySelectorAll('.element-props').forEach(el => el.style.display = 'none');
|
|
494
|
-
|
|
495
|
-
if (!elementName) {
|
|
496
|
-
section.style.display = 'none';
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
section.style.display = 'block';
|
|
501
|
-
|
|
502
|
-
// Parse element name to extract type and info
|
|
503
|
-
const elementInfo = parseElementName(elementName);
|
|
504
|
-
const bbox = elementBboxes[elementName] || {};
|
|
505
|
-
|
|
506
|
-
// Update title
|
|
507
|
-
titleEl.textContent = `Selected: ${elementInfo.displayName}`;
|
|
508
|
-
|
|
509
|
-
// Update type badge
|
|
510
|
-
typeBadge.className = `element-type-badge ${elementInfo.type}`;
|
|
511
|
-
typeBadge.textContent = elementInfo.type;
|
|
512
|
-
|
|
513
|
-
// Update axis info
|
|
514
|
-
if (elementInfo.axisId) {
|
|
515
|
-
const row = elementInfo.axisId.match(/ax_(\\d)(\\d)/);
|
|
516
|
-
if (row) {
|
|
517
|
-
axisInfo.textContent = `Panel: Row ${parseInt(row[1])+1}, Col ${parseInt(row[2])+1}`;
|
|
518
|
-
} else {
|
|
519
|
-
axisInfo.textContent = `Axis: ${elementInfo.axisId}`;
|
|
520
|
-
}
|
|
521
|
-
} else {
|
|
522
|
-
axisInfo.textContent = '';
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Show appropriate property panel and populate with current values
|
|
526
|
-
showPropertiesForElement(elementInfo, bbox);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function parseElementName(name) {
|
|
530
|
-
// Parse names like: ax_00_scatter_0, ax_11_trace_1, ax_01_xlabel, trace_0, xlabel, etc.
|
|
531
|
-
const result = {
|
|
532
|
-
original: name,
|
|
533
|
-
type: 'unknown',
|
|
534
|
-
displayName: name,
|
|
535
|
-
axisId: null,
|
|
536
|
-
index: null
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
// Check for axis prefix (ax_XX_)
|
|
540
|
-
const axisMatch = name.match(/^(ax_\\d+)_(.+)$/);
|
|
541
|
-
if (axisMatch) {
|
|
542
|
-
result.axisId = axisMatch[1];
|
|
543
|
-
name = axisMatch[2]; // Rest of the name
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Determine element type
|
|
547
|
-
if (name.includes('scatter')) {
|
|
548
|
-
result.type = 'scatter';
|
|
549
|
-
const idx = name.match(/scatter_(\\d+)/);
|
|
550
|
-
result.index = idx ? parseInt(idx[1]) : 0;
|
|
551
|
-
result.displayName = `Scatter ${result.index + 1}`;
|
|
552
|
-
} else if (name.includes('trace')) {
|
|
553
|
-
result.type = 'trace';
|
|
554
|
-
const idx = name.match(/trace_(\\d+)/);
|
|
555
|
-
result.index = idx ? parseInt(idx[1]) : 0;
|
|
556
|
-
result.displayName = `Line ${result.index + 1}`;
|
|
557
|
-
} else if (name.includes('fill')) {
|
|
558
|
-
result.type = 'fill';
|
|
559
|
-
const idx = name.match(/fill_(\\d+)/);
|
|
560
|
-
result.index = idx ? parseInt(idx[1]) : 0;
|
|
561
|
-
result.displayName = `Fill Area ${result.index + 1}`;
|
|
562
|
-
} else if (name.includes('bar')) {
|
|
563
|
-
result.type = 'bar';
|
|
564
|
-
const idx = name.match(/bar_(\\d+)/);
|
|
565
|
-
result.index = idx ? parseInt(idx[1]) : 0;
|
|
566
|
-
result.displayName = `Bar ${result.index + 1}`;
|
|
567
|
-
} else if (name === 'xlabel' || name === 'ylabel' || name === 'title') {
|
|
568
|
-
result.type = 'label';
|
|
569
|
-
result.displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
570
|
-
} else if (name === 'legend') {
|
|
571
|
-
result.type = 'legend';
|
|
572
|
-
result.displayName = 'Legend';
|
|
573
|
-
} else if (name.includes('panel')) {
|
|
574
|
-
result.type = 'panel';
|
|
575
|
-
result.displayName = 'Panel';
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return result;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function showPropertiesForElement(elementInfo, bbox) {
|
|
582
|
-
const type = elementInfo.type;
|
|
583
|
-
|
|
584
|
-
if (type === 'trace') {
|
|
585
|
-
const props = document.getElementById('selected-trace-props');
|
|
586
|
-
props.style.display = 'block';
|
|
587
|
-
|
|
588
|
-
// Try to get current values from overrides
|
|
589
|
-
const traceOverrides = getTraceOverrides(elementInfo);
|
|
590
|
-
if (traceOverrides) {
|
|
591
|
-
document.getElementById('sel-trace-label').value = traceOverrides.label || '';
|
|
592
|
-
document.getElementById('sel-trace-color').value = traceOverrides.color || '#1f77b4';
|
|
593
|
-
document.getElementById('sel-trace-color-text').value = traceOverrides.color || '#1f77b4';
|
|
594
|
-
document.getElementById('sel-trace-linewidth').value = traceOverrides.linewidth || 1.0;
|
|
595
|
-
document.getElementById('sel-trace-linestyle').value = traceOverrides.linestyle || '-';
|
|
596
|
-
document.getElementById('sel-trace-marker').value = traceOverrides.marker || '';
|
|
597
|
-
document.getElementById('sel-trace-markersize').value = traceOverrides.markersize || 4;
|
|
598
|
-
document.getElementById('sel-trace-alpha').value = traceOverrides.alpha || 1;
|
|
599
|
-
}
|
|
600
|
-
} else if (type === 'scatter') {
|
|
601
|
-
const props = document.getElementById('selected-scatter-props');
|
|
602
|
-
props.style.display = 'block';
|
|
603
|
-
|
|
604
|
-
const scatterOverrides = getScatterOverrides(elementInfo);
|
|
605
|
-
if (scatterOverrides) {
|
|
606
|
-
document.getElementById('sel-scatter-color').value = scatterOverrides.color || '#1f77b4';
|
|
607
|
-
document.getElementById('sel-scatter-color-text').value = scatterOverrides.color || '#1f77b4';
|
|
608
|
-
document.getElementById('sel-scatter-size').value = scatterOverrides.size || 20;
|
|
609
|
-
document.getElementById('sel-scatter-marker').value = scatterOverrides.marker || 'o';
|
|
610
|
-
document.getElementById('sel-scatter-alpha').value = scatterOverrides.alpha || 0.7;
|
|
611
|
-
document.getElementById('sel-scatter-edgecolor').value = scatterOverrides.edgecolor || '#000000';
|
|
612
|
-
document.getElementById('sel-scatter-edgecolor-text').value = scatterOverrides.edgecolor || '#000000';
|
|
613
|
-
}
|
|
614
|
-
} else if (type === 'fill') {
|
|
615
|
-
const props = document.getElementById('selected-fill-props');
|
|
616
|
-
props.style.display = 'block';
|
|
617
|
-
|
|
618
|
-
const fillOverrides = getFillOverrides(elementInfo);
|
|
619
|
-
if (fillOverrides) {
|
|
620
|
-
document.getElementById('sel-fill-color').value = fillOverrides.color || '#1f77b4';
|
|
621
|
-
document.getElementById('sel-fill-color-text').value = fillOverrides.color || '#1f77b4';
|
|
622
|
-
document.getElementById('sel-fill-alpha').value = fillOverrides.alpha || 0.3;
|
|
623
|
-
}
|
|
624
|
-
} else if (type === 'bar') {
|
|
625
|
-
const props = document.getElementById('selected-bar-props');
|
|
626
|
-
props.style.display = 'block';
|
|
627
|
-
} else if (type === 'label') {
|
|
628
|
-
const props = document.getElementById('selected-label-props');
|
|
629
|
-
props.style.display = 'block';
|
|
630
|
-
|
|
631
|
-
// Get label text from global overrides
|
|
632
|
-
const labelName = elementInfo.displayName.toLowerCase();
|
|
633
|
-
document.getElementById('sel-label-text').value = overrides[labelName] || '';
|
|
634
|
-
document.getElementById('sel-label-fontsize').value = overrides.axis_fontsize || 7;
|
|
635
|
-
} else if (type === 'panel') {
|
|
636
|
-
const props = document.getElementById('selected-panel-props');
|
|
637
|
-
props.style.display = 'block';
|
|
638
|
-
|
|
639
|
-
// Load existing panel overrides
|
|
640
|
-
const panelOverrides = getPanelOverrides(elementInfo);
|
|
641
|
-
document.getElementById('sel-panel-title').value = panelOverrides.title || '';
|
|
642
|
-
document.getElementById('sel-panel-xlabel').value = panelOverrides.xlabel || '';
|
|
643
|
-
document.getElementById('sel-panel-ylabel').value = panelOverrides.ylabel || '';
|
|
644
|
-
} else if (type === 'legend') {
|
|
645
|
-
// For legend, expand the legend section instead
|
|
646
|
-
expandSection('section-legend');
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
function getTraceOverrides(elementInfo) {
|
|
651
|
-
// Initialize element overrides storage if not exists
|
|
652
|
-
if (!overrides.element_overrides) {
|
|
653
|
-
overrides.element_overrides = {};
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const key = elementInfo.original;
|
|
657
|
-
if (!overrides.element_overrides[key]) {
|
|
658
|
-
// Try to get from traces array
|
|
659
|
-
if (traces[elementInfo.index]) {
|
|
660
|
-
overrides.element_overrides[key] = { ...traces[elementInfo.index] };
|
|
661
|
-
} else {
|
|
662
|
-
overrides.element_overrides[key] = {};
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return overrides.element_overrides[key];
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
function getScatterOverrides(elementInfo) {
|
|
669
|
-
if (!overrides.element_overrides) {
|
|
670
|
-
overrides.element_overrides = {};
|
|
671
|
-
}
|
|
672
|
-
const key = elementInfo.original;
|
|
673
|
-
if (!overrides.element_overrides[key]) {
|
|
674
|
-
overrides.element_overrides[key] = {};
|
|
675
|
-
}
|
|
676
|
-
return overrides.element_overrides[key];
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
function getFillOverrides(elementInfo) {
|
|
680
|
-
if (!overrides.element_overrides) {
|
|
681
|
-
overrides.element_overrides = {};
|
|
682
|
-
}
|
|
683
|
-
const key = elementInfo.original;
|
|
684
|
-
if (!overrides.element_overrides[key]) {
|
|
685
|
-
overrides.element_overrides[key] = {};
|
|
686
|
-
}
|
|
687
|
-
return overrides.element_overrides[key];
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function getPanelOverrides(elementInfo) {
|
|
691
|
-
if (!overrides.element_overrides) {
|
|
692
|
-
overrides.element_overrides = {};
|
|
693
|
-
}
|
|
694
|
-
const key = elementInfo.original;
|
|
695
|
-
if (!overrides.element_overrides[key]) {
|
|
696
|
-
overrides.element_overrides[key] = {};
|
|
697
|
-
}
|
|
698
|
-
return overrides.element_overrides[key];
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function applySelectedElementChanges() {
|
|
702
|
-
if (!selectedElement) return;
|
|
703
|
-
|
|
704
|
-
const elementInfo = parseElementName(selectedElement);
|
|
705
|
-
const type = elementInfo.type;
|
|
706
|
-
|
|
707
|
-
if (!overrides.element_overrides) {
|
|
708
|
-
overrides.element_overrides = {};
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
if (type === 'trace') {
|
|
712
|
-
overrides.element_overrides[selectedElement] = {
|
|
713
|
-
label: document.getElementById('sel-trace-label').value,
|
|
714
|
-
color: document.getElementById('sel-trace-color').value,
|
|
715
|
-
linewidth: parseFloat(document.getElementById('sel-trace-linewidth').value),
|
|
716
|
-
linestyle: document.getElementById('sel-trace-linestyle').value,
|
|
717
|
-
marker: document.getElementById('sel-trace-marker').value,
|
|
718
|
-
markersize: parseFloat(document.getElementById('sel-trace-markersize').value),
|
|
719
|
-
alpha: parseFloat(document.getElementById('sel-trace-alpha').value)
|
|
720
|
-
};
|
|
721
|
-
} else if (type === 'scatter') {
|
|
722
|
-
overrides.element_overrides[selectedElement] = {
|
|
723
|
-
color: document.getElementById('sel-scatter-color').value,
|
|
724
|
-
size: parseFloat(document.getElementById('sel-scatter-size').value),
|
|
725
|
-
marker: document.getElementById('sel-scatter-marker').value,
|
|
726
|
-
alpha: parseFloat(document.getElementById('sel-scatter-alpha').value),
|
|
727
|
-
edgecolor: document.getElementById('sel-scatter-edgecolor').value
|
|
728
|
-
};
|
|
729
|
-
} else if (type === 'fill') {
|
|
730
|
-
overrides.element_overrides[selectedElement] = {
|
|
731
|
-
color: document.getElementById('sel-fill-color').value,
|
|
732
|
-
alpha: parseFloat(document.getElementById('sel-fill-alpha').value)
|
|
733
|
-
};
|
|
734
|
-
} else if (type === 'label') {
|
|
735
|
-
const labelName = elementInfo.displayName.toLowerCase();
|
|
736
|
-
overrides[labelName] = document.getElementById('sel-label-text').value;
|
|
737
|
-
overrides.axis_fontsize = parseFloat(document.getElementById('sel-label-fontsize').value);
|
|
738
|
-
} else if (type === 'bar') {
|
|
739
|
-
overrides.element_overrides[selectedElement] = {
|
|
740
|
-
facecolor: document.getElementById('sel-bar-facecolor').value,
|
|
741
|
-
edgecolor: document.getElementById('sel-bar-edgecolor').value,
|
|
742
|
-
alpha: parseFloat(document.getElementById('sel-bar-alpha').value)
|
|
743
|
-
};
|
|
744
|
-
} else if (type === 'panel') {
|
|
745
|
-
// Panel-specific overrides (per-axis) including title, xlabel, ylabel
|
|
746
|
-
overrides.element_overrides[selectedElement] = {
|
|
747
|
-
title: document.getElementById('sel-panel-title').value,
|
|
748
|
-
xlabel: document.getElementById('sel-panel-xlabel').value,
|
|
749
|
-
ylabel: document.getElementById('sel-panel-ylabel').value,
|
|
750
|
-
facecolor: document.getElementById('sel-panel-facecolor').value,
|
|
751
|
-
transparent: document.getElementById('sel-panel-transparent').checked,
|
|
752
|
-
grid: document.getElementById('sel-panel-grid').checked
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// Trigger update
|
|
757
|
-
updatePreview();
|
|
758
|
-
document.getElementById('status').textContent = `Applied changes to ${elementInfo.displayName}`;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// Sync color inputs
|
|
762
|
-
function setupColorSync(colorId, textId) {
|
|
763
|
-
const colorInput = document.getElementById(colorId);
|
|
764
|
-
const textInput = document.getElementById(textId);
|
|
765
|
-
if (colorInput && textInput) {
|
|
766
|
-
colorInput.addEventListener('input', () => {
|
|
767
|
-
textInput.value = colorInput.value;
|
|
768
|
-
});
|
|
769
|
-
textInput.addEventListener('input', () => {
|
|
770
|
-
if (/^#[0-9A-Fa-f]{6}$/.test(textInput.value)) {
|
|
771
|
-
colorInput.value = textInput.value;
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// Theme management
|
|
778
|
-
function toggleTheme() {
|
|
779
|
-
const html = document.documentElement;
|
|
780
|
-
const current = html.getAttribute('data-theme');
|
|
781
|
-
const next = current === 'dark' ? 'light' : 'dark';
|
|
782
|
-
html.setAttribute('data-theme', next);
|
|
783
|
-
document.getElementById('theme-icon').innerHTML = next === 'dark' ? '☾' : '☼';
|
|
784
|
-
localStorage.setItem('scitex-editor-theme', next);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Collapsible sections
|
|
788
|
-
function toggleSection(header) {
|
|
789
|
-
header.classList.toggle('collapsed');
|
|
790
|
-
const content = header.nextElementSibling;
|
|
791
|
-
content.classList.toggle('collapsed');
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function toggleCustomLegendPosition() {
|
|
795
|
-
const legendLoc = document.getElementById('legend_loc').value;
|
|
796
|
-
const customCoordsDiv = document.getElementById('custom-legend-coords');
|
|
797
|
-
customCoordsDiv.style.display = legendLoc === 'custom' ? 'flex' : 'none';
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
// Dimension unit toggle
|
|
801
|
-
function setDimensionUnit(unit) {
|
|
802
|
-
if (unit === dimensionUnit) return;
|
|
803
|
-
|
|
804
|
-
const widthInput = document.getElementById('fig_width');
|
|
805
|
-
const heightInput = document.getElementById('fig_height');
|
|
806
|
-
const widthLabel = document.getElementById('fig_width_label');
|
|
807
|
-
const heightLabel = document.getElementById('fig_height_label');
|
|
808
|
-
const mmBtn = document.getElementById('unit-mm');
|
|
809
|
-
const inchBtn = document.getElementById('unit-inch');
|
|
810
|
-
|
|
811
|
-
// Get current values
|
|
812
|
-
let width = parseFloat(widthInput.value) || 0;
|
|
813
|
-
let height = parseFloat(heightInput.value) || 0;
|
|
814
|
-
|
|
815
|
-
// Convert values
|
|
816
|
-
if (unit === 'mm' && dimensionUnit === 'inch') {
|
|
817
|
-
// inch to mm
|
|
818
|
-
width = Math.round(width * INCH_TO_MM * 10) / 10;
|
|
819
|
-
height = Math.round(height * INCH_TO_MM * 10) / 10;
|
|
820
|
-
widthInput.min = 10;
|
|
821
|
-
widthInput.max = 300;
|
|
822
|
-
widthInput.step = 1;
|
|
823
|
-
heightInput.min = 10;
|
|
824
|
-
heightInput.max = 300;
|
|
825
|
-
heightInput.step = 1;
|
|
826
|
-
} else if (unit === 'inch' && dimensionUnit === 'mm') {
|
|
827
|
-
// mm to inch
|
|
828
|
-
width = Math.round(width * MM_TO_INCH * 100) / 100;
|
|
829
|
-
height = Math.round(height * MM_TO_INCH * 100) / 100;
|
|
830
|
-
widthInput.min = 0.5;
|
|
831
|
-
widthInput.max = 12;
|
|
832
|
-
widthInput.step = 0.05;
|
|
833
|
-
heightInput.min = 0.5;
|
|
834
|
-
heightInput.max = 12;
|
|
835
|
-
heightInput.step = 0.05;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Update values and labels
|
|
839
|
-
widthInput.value = width;
|
|
840
|
-
heightInput.value = height;
|
|
841
|
-
widthLabel.textContent = `Width (${unit})`;
|
|
842
|
-
heightLabel.textContent = `Height (${unit})`;
|
|
843
|
-
|
|
844
|
-
// Update button states
|
|
845
|
-
if (unit === 'mm') {
|
|
846
|
-
mmBtn.classList.add('active');
|
|
847
|
-
inchBtn.classList.remove('active');
|
|
848
|
-
} else {
|
|
849
|
-
mmBtn.classList.remove('active');
|
|
850
|
-
inchBtn.classList.add('active');
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
dimensionUnit = unit;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Background type management
|
|
857
|
-
let backgroundType = 'transparent';
|
|
858
|
-
let initializingBackground = true; // Flag to prevent updates during init
|
|
859
|
-
|
|
860
|
-
function setBackgroundType(type) {
|
|
861
|
-
backgroundType = type;
|
|
862
|
-
|
|
863
|
-
// Update hidden inputs for collectOverrides
|
|
864
|
-
const facecolorInput = document.getElementById('facecolor');
|
|
865
|
-
const transparentInput = document.getElementById('transparent');
|
|
866
|
-
|
|
867
|
-
if (type === 'white') {
|
|
868
|
-
facecolorInput.value = '#ffffff';
|
|
869
|
-
transparentInput.value = 'false';
|
|
870
|
-
} else if (type === 'black') {
|
|
871
|
-
facecolorInput.value = '#000000';
|
|
872
|
-
transparentInput.value = 'false';
|
|
873
|
-
} else {
|
|
874
|
-
// transparent
|
|
875
|
-
facecolorInput.value = '#ffffff';
|
|
876
|
-
transparentInput.value = 'true';
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// Update button states
|
|
880
|
-
document.querySelectorAll('.bg-btn').forEach(btn => btn.classList.remove('active'));
|
|
881
|
-
document.getElementById(`bg-${type}`).classList.add('active');
|
|
882
|
-
|
|
883
|
-
// Trigger update only after initialization
|
|
884
|
-
if (!initializingBackground) {
|
|
885
|
-
scheduleUpdate();
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Get figure dimensions in inches (for matplotlib)
|
|
890
|
-
function getFigSizeInches() {
|
|
891
|
-
let width = parseFloat(document.getElementById('fig_width').value) || 80;
|
|
892
|
-
let height = parseFloat(document.getElementById('fig_height').value) || 68;
|
|
893
|
-
|
|
894
|
-
if (dimensionUnit === 'mm') {
|
|
895
|
-
width = width * MM_TO_INCH;
|
|
896
|
-
height = height * MM_TO_INCH;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
return [Math.round(width * 100) / 100, Math.round(height * 100) / 100];
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// Initialize fields
|
|
903
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
904
|
-
// Load saved theme
|
|
905
|
-
const savedTheme = localStorage.getItem('scitex-editor-theme');
|
|
906
|
-
if (savedTheme) {
|
|
907
|
-
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
908
|
-
document.getElementById('theme-icon').innerHTML = savedTheme === 'dark' ? '☾' : '☼';
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// Labels
|
|
912
|
-
if (overrides.title) document.getElementById('title').value = overrides.title;
|
|
913
|
-
if (overrides.xlabel) document.getElementById('xlabel').value = overrides.xlabel;
|
|
914
|
-
if (overrides.ylabel) document.getElementById('ylabel').value = overrides.ylabel;
|
|
915
|
-
|
|
916
|
-
// Axis limits
|
|
917
|
-
if (overrides.xlim) {
|
|
918
|
-
document.getElementById('xmin').value = overrides.xlim[0];
|
|
919
|
-
document.getElementById('xmax').value = overrides.xlim[1];
|
|
920
|
-
}
|
|
921
|
-
if (overrides.ylim) {
|
|
922
|
-
document.getElementById('ymin').value = overrides.ylim[0];
|
|
923
|
-
document.getElementById('ymax').value = overrides.ylim[1];
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// Traces
|
|
927
|
-
document.getElementById('linewidth').value = overrides.linewidth || 1.0;
|
|
928
|
-
updateTracesList();
|
|
929
|
-
|
|
930
|
-
// Legend
|
|
931
|
-
document.getElementById('legend_visible').checked = overrides.legend_visible !== false;
|
|
932
|
-
document.getElementById('legend_loc').value = overrides.legend_loc || 'best';
|
|
933
|
-
document.getElementById('legend_frameon').checked = overrides.legend_frameon || false;
|
|
934
|
-
document.getElementById('legend_fontsize').value = overrides.legend_fontsize || 6;
|
|
935
|
-
document.getElementById('legend_x').value = overrides.legend_x !== undefined ? overrides.legend_x : 0.5;
|
|
936
|
-
document.getElementById('legend_y').value = overrides.legend_y !== undefined ? overrides.legend_y : 0.5;
|
|
937
|
-
toggleCustomLegendPosition();
|
|
938
|
-
|
|
939
|
-
// Ticks
|
|
940
|
-
document.getElementById('x_n_ticks').value = overrides.x_n_ticks || overrides.n_ticks || 4;
|
|
941
|
-
document.getElementById('y_n_ticks').value = overrides.y_n_ticks || overrides.n_ticks || 4;
|
|
942
|
-
document.getElementById('hide_x_ticks').checked = overrides.hide_x_ticks || false;
|
|
943
|
-
document.getElementById('hide_y_ticks').checked = overrides.hide_y_ticks || false;
|
|
944
|
-
document.getElementById('tick_fontsize').value = overrides.tick_fontsize || 7;
|
|
945
|
-
document.getElementById('tick_length').value = overrides.tick_length || 0.8;
|
|
946
|
-
document.getElementById('tick_width').value = overrides.tick_width || 0.2;
|
|
947
|
-
document.getElementById('tick_direction').value = overrides.tick_direction || 'out';
|
|
948
|
-
|
|
949
|
-
// Style
|
|
950
|
-
document.getElementById('grid').checked = overrides.grid || false;
|
|
951
|
-
document.getElementById('hide_top_spine').checked = overrides.hide_top_spine !== false;
|
|
952
|
-
document.getElementById('hide_right_spine').checked = overrides.hide_right_spine !== false;
|
|
953
|
-
document.getElementById('axis_width').value = overrides.axis_width || 0.2;
|
|
954
|
-
document.getElementById('axis_fontsize').value = overrides.axis_fontsize || 7;
|
|
955
|
-
// Initialize background type from overrides
|
|
956
|
-
const isTransparent = overrides.transparent !== false;
|
|
957
|
-
const facecolor = overrides.facecolor || '#ffffff';
|
|
958
|
-
document.getElementById('facecolor').value = facecolor;
|
|
959
|
-
|
|
960
|
-
if (isTransparent) {
|
|
961
|
-
setBackgroundType('transparent');
|
|
962
|
-
} else if (facecolor === '#000000') {
|
|
963
|
-
setBackgroundType('black');
|
|
964
|
-
} else {
|
|
965
|
-
setBackgroundType('white');
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// Dimensions (convert from inches in metadata to mm by default)
|
|
969
|
-
if (overrides.fig_size) {
|
|
970
|
-
// fig_size is in inches in the JSON - convert to mm for default display
|
|
971
|
-
const widthMm = Math.round(overrides.fig_size[0] * INCH_TO_MM);
|
|
972
|
-
const heightMm = Math.round(overrides.fig_size[1] * INCH_TO_MM);
|
|
973
|
-
document.getElementById('fig_width').value = widthMm;
|
|
974
|
-
document.getElementById('fig_height').value = heightMm;
|
|
975
|
-
}
|
|
976
|
-
document.getElementById('dpi').value = overrides.dpi || 300;
|
|
977
|
-
// Default unit is mm, which is already set in HTML and JS state
|
|
978
|
-
|
|
979
|
-
// Note: facecolor is now managed by background toggle buttons (white/transparent/black)
|
|
980
|
-
// No text input sync needed
|
|
981
|
-
|
|
982
|
-
updateAnnotationsList();
|
|
983
|
-
updatePreview();
|
|
984
|
-
initHoverSystem();
|
|
985
|
-
setAutoUpdateInterval();
|
|
986
|
-
|
|
987
|
-
// Setup color sync for selected element property inputs
|
|
988
|
-
setupColorSync('sel-trace-color', 'sel-trace-color-text');
|
|
989
|
-
setupColorSync('sel-scatter-color', 'sel-scatter-color-text');
|
|
990
|
-
setupColorSync('sel-scatter-edgecolor', 'sel-scatter-edgecolor-text');
|
|
991
|
-
setupColorSync('sel-fill-color', 'sel-fill-color-text');
|
|
992
|
-
setupColorSync('sel-bar-facecolor', 'sel-bar-facecolor-text');
|
|
993
|
-
setupColorSync('sel-bar-edgecolor', 'sel-bar-edgecolor-text');
|
|
994
|
-
setupColorSync('sel-panel-facecolor', 'sel-panel-facecolor-text');
|
|
995
|
-
|
|
996
|
-
// Mark initialization complete - now background changes will trigger updates
|
|
997
|
-
initializingBackground = false;
|
|
998
|
-
});
|
|
999
|
-
|
|
1000
|
-
// Traces list management
|
|
1001
|
-
function updateTracesList() {
|
|
1002
|
-
const list = document.getElementById('traces-list');
|
|
1003
|
-
if (!traces || traces.length === 0) {
|
|
1004
|
-
list.innerHTML = '<div style="padding: 10px; color: var(--text-muted); font-size: 0.85em;">No traces found in metadata</div>';
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
list.innerHTML = traces.map((t, i) => `
|
|
1009
|
-
<div class="trace-item">
|
|
1010
|
-
<input type="color" class="trace-color" value="${t.color || '#1f77b4'}"
|
|
1011
|
-
onchange="updateTraceColor(${i}, this.value)">
|
|
1012
|
-
<span class="trace-label">${t.label || t.id || 'Trace ' + (i+1)}</span>
|
|
1013
|
-
<div class="trace-style">
|
|
1014
|
-
<select onchange="updateTraceStyle(${i}, this.value)">
|
|
1015
|
-
<option value="-" ${t.linestyle === '-' ? 'selected' : ''}>Solid</option>
|
|
1016
|
-
<option value="--" ${t.linestyle === '--' ? 'selected' : ''}>Dashed</option>
|
|
1017
|
-
<option value=":" ${t.linestyle === ':' ? 'selected' : ''}>Dotted</option>
|
|
1018
|
-
<option value="-." ${t.linestyle === '-.' ? 'selected' : ''}>Dash-dot</option>
|
|
1019
|
-
</select>
|
|
1020
|
-
</div>
|
|
1021
|
-
</div>
|
|
1022
|
-
`).join('');
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
function updateTraceColor(idx, color) {
|
|
1026
|
-
if (traces[idx]) {
|
|
1027
|
-
traces[idx].color = color;
|
|
1028
|
-
scheduleUpdate();
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
function updateTraceStyle(idx, style) {
|
|
1033
|
-
if (traces[idx]) {
|
|
1034
|
-
traces[idx].linestyle = style;
|
|
1035
|
-
scheduleUpdate();
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
function collectOverrides() {
|
|
1040
|
-
const o = {};
|
|
1041
|
-
|
|
1042
|
-
// Labels
|
|
1043
|
-
const title = document.getElementById('title').value;
|
|
1044
|
-
const xlabel = document.getElementById('xlabel').value;
|
|
1045
|
-
const ylabel = document.getElementById('ylabel').value;
|
|
1046
|
-
if (title) o.title = title;
|
|
1047
|
-
if (xlabel) o.xlabel = xlabel;
|
|
1048
|
-
if (ylabel) o.ylabel = ylabel;
|
|
1049
|
-
|
|
1050
|
-
// Axis limits
|
|
1051
|
-
const xmin = document.getElementById('xmin').value;
|
|
1052
|
-
const xmax = document.getElementById('xmax').value;
|
|
1053
|
-
if (xmin !== '' && xmax !== '') o.xlim = [parseFloat(xmin), parseFloat(xmax)];
|
|
1054
|
-
|
|
1055
|
-
const ymin = document.getElementById('ymin').value;
|
|
1056
|
-
const ymax = document.getElementById('ymax').value;
|
|
1057
|
-
if (ymin !== '' && ymax !== '') o.ylim = [parseFloat(ymin), parseFloat(ymax)];
|
|
1058
|
-
|
|
1059
|
-
// Traces
|
|
1060
|
-
o.linewidth = parseFloat(document.getElementById('linewidth').value) || 1.0;
|
|
1061
|
-
o.traces = traces;
|
|
1062
|
-
|
|
1063
|
-
// Legend
|
|
1064
|
-
o.legend_visible = document.getElementById('legend_visible').checked;
|
|
1065
|
-
o.legend_loc = document.getElementById('legend_loc').value;
|
|
1066
|
-
o.legend_frameon = document.getElementById('legend_frameon').checked;
|
|
1067
|
-
o.legend_fontsize = parseInt(document.getElementById('legend_fontsize').value) || 6;
|
|
1068
|
-
o.legend_x = parseFloat(document.getElementById('legend_x').value) || 0.5;
|
|
1069
|
-
o.legend_y = parseFloat(document.getElementById('legend_y').value) || 0.5;
|
|
1070
|
-
|
|
1071
|
-
// Ticks
|
|
1072
|
-
o.x_n_ticks = parseInt(document.getElementById('x_n_ticks').value) || 4;
|
|
1073
|
-
o.y_n_ticks = parseInt(document.getElementById('y_n_ticks').value) || 4;
|
|
1074
|
-
o.hide_x_ticks = document.getElementById('hide_x_ticks').checked;
|
|
1075
|
-
o.hide_y_ticks = document.getElementById('hide_y_ticks').checked;
|
|
1076
|
-
o.tick_fontsize = parseInt(document.getElementById('tick_fontsize').value) || 7;
|
|
1077
|
-
o.tick_length = parseFloat(document.getElementById('tick_length').value) || 0.8;
|
|
1078
|
-
o.tick_width = parseFloat(document.getElementById('tick_width').value) || 0.2;
|
|
1079
|
-
o.tick_direction = document.getElementById('tick_direction').value;
|
|
1080
|
-
|
|
1081
|
-
// Style
|
|
1082
|
-
o.grid = document.getElementById('grid').checked;
|
|
1083
|
-
o.hide_top_spine = document.getElementById('hide_top_spine').checked;
|
|
1084
|
-
o.hide_right_spine = document.getElementById('hide_right_spine').checked;
|
|
1085
|
-
o.axis_width = parseFloat(document.getElementById('axis_width').value) || 0.2;
|
|
1086
|
-
o.axis_fontsize = parseInt(document.getElementById('axis_fontsize').value) || 7;
|
|
1087
|
-
o.facecolor = document.getElementById('facecolor').value;
|
|
1088
|
-
o.transparent = document.getElementById('transparent').value === 'true';
|
|
1089
|
-
|
|
1090
|
-
// Dimensions (always in inches for matplotlib)
|
|
1091
|
-
o.fig_size = getFigSizeInches();
|
|
1092
|
-
o.dpi = parseInt(document.getElementById('dpi').value) || 300;
|
|
1093
|
-
|
|
1094
|
-
// Annotations
|
|
1095
|
-
o.annotations = overrides.annotations || [];
|
|
1096
|
-
|
|
1097
|
-
// Element-specific overrides (per-element styles)
|
|
1098
|
-
if (overrides.element_overrides) {
|
|
1099
|
-
o.element_overrides = overrides.element_overrides;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
return o;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
async function updatePreview() {
|
|
1106
|
-
setStatus('Updating...', false);
|
|
1107
|
-
overrides = collectOverrides();
|
|
1108
|
-
try {
|
|
1109
|
-
const resp = await fetch('/update', {
|
|
1110
|
-
method: 'POST',
|
|
1111
|
-
headers: {'Content-Type': 'application/json'},
|
|
1112
|
-
body: JSON.stringify({overrides})
|
|
1113
|
-
});
|
|
1114
|
-
const data = await resp.json();
|
|
1115
|
-
document.getElementById('preview-img').src = 'data:image/png;base64,' + data.image;
|
|
1116
|
-
|
|
1117
|
-
if (data.bboxes) {
|
|
1118
|
-
elementBboxes = data.bboxes;
|
|
1119
|
-
}
|
|
1120
|
-
if (data.img_size) {
|
|
1121
|
-
imgSize = data.img_size;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
selectedElement = null;
|
|
1125
|
-
hoveredElement = null;
|
|
1126
|
-
updateOverlay();
|
|
1127
|
-
|
|
1128
|
-
setStatus('Preview updated', false);
|
|
1129
|
-
} catch (e) {
|
|
1130
|
-
setStatus('Error: ' + e.message, true);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
async function saveManual() {
|
|
1135
|
-
setStatus('Saving...', false);
|
|
1136
|
-
try {
|
|
1137
|
-
const resp = await fetch('/save', {
|
|
1138
|
-
method: 'POST',
|
|
1139
|
-
headers: {'Content-Type': 'application/json'}
|
|
1140
|
-
});
|
|
1141
|
-
const data = await resp.json();
|
|
1142
|
-
if (data.status === 'saved') {
|
|
1143
|
-
setStatus('Saved: ' + data.path.split('/').pop(), false);
|
|
1144
|
-
} else {
|
|
1145
|
-
setStatus('Error: ' + data.message, true);
|
|
1146
|
-
}
|
|
1147
|
-
} catch (e) {
|
|
1148
|
-
setStatus('Error: ' + e.message, true);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
function resetOverrides() {
|
|
1153
|
-
if (confirm('Reset all changes to original values?')) {
|
|
1154
|
-
location.reload();
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
function addAnnotation() {
|
|
1159
|
-
const text = document.getElementById('annot-text').value;
|
|
1160
|
-
if (!text) return;
|
|
1161
|
-
const x = parseFloat(document.getElementById('annot-x').value) || 0.5;
|
|
1162
|
-
const y = parseFloat(document.getElementById('annot-y').value) || 0.5;
|
|
1163
|
-
const size = parseInt(document.getElementById('annot-size').value) || 8;
|
|
1164
|
-
if (!overrides.annotations) overrides.annotations = [];
|
|
1165
|
-
overrides.annotations.push({type: 'text', text, x, y, fontsize: size});
|
|
1166
|
-
document.getElementById('annot-text').value = '';
|
|
1167
|
-
updateAnnotationsList();
|
|
1168
|
-
updatePreview();
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
function removeAnnotation(idx) {
|
|
1172
|
-
overrides.annotations.splice(idx, 1);
|
|
1173
|
-
updateAnnotationsList();
|
|
1174
|
-
updatePreview();
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
function updateAnnotationsList() {
|
|
1178
|
-
const list = document.getElementById('annotations-list');
|
|
1179
|
-
const annotations = overrides.annotations || [];
|
|
1180
|
-
if (annotations.length === 0) {
|
|
1181
|
-
list.innerHTML = '';
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
list.innerHTML = annotations.map((a, i) =>
|
|
1185
|
-
`<div class="annotation-item">
|
|
1186
|
-
<span>${a.text.substring(0, 25)}${a.text.length > 25 ? '...' : ''} (${a.x.toFixed(2)}, ${a.y.toFixed(2)})</span>
|
|
1187
|
-
<button onclick="removeAnnotation(${i})">Remove</button>
|
|
1188
|
-
</div>`
|
|
1189
|
-
).join('');
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
function setStatus(msg, isError = false) {
|
|
1193
|
-
const el = document.getElementById('status');
|
|
1194
|
-
el.textContent = msg;
|
|
1195
|
-
el.classList.toggle('error', isError);
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// Debounced auto-update
|
|
1199
|
-
let updateTimer = null;
|
|
1200
|
-
const DEBOUNCE_DELAY = 500;
|
|
1201
|
-
|
|
1202
|
-
function scheduleUpdate() {
|
|
1203
|
-
if (updateTimer) clearTimeout(updateTimer);
|
|
1204
|
-
updateTimer = setTimeout(() => {
|
|
1205
|
-
updatePreview();
|
|
1206
|
-
}, DEBOUNCE_DELAY);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
// Auto-update on input changes
|
|
1210
|
-
document.querySelectorAll('input[type="text"], input[type="number"]').forEach(el => {
|
|
1211
|
-
el.addEventListener('input', scheduleUpdate);
|
|
1212
|
-
el.addEventListener('keypress', (e) => {
|
|
1213
|
-
if (e.key === 'Enter') {
|
|
1214
|
-
if (updateTimer) clearTimeout(updateTimer);
|
|
1215
|
-
updatePreview();
|
|
1216
|
-
}
|
|
1217
|
-
});
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
document.querySelectorAll('input[type="checkbox"], select').forEach(el => {
|
|
1221
|
-
el.addEventListener('change', () => {
|
|
1222
|
-
if (updateTimer) clearTimeout(updateTimer);
|
|
1223
|
-
updatePreview();
|
|
1224
|
-
});
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
document.querySelectorAll('input[type="color"]').forEach(el => {
|
|
1228
|
-
el.addEventListener('change', () => {
|
|
1229
|
-
if (updateTimer) clearTimeout(updateTimer);
|
|
1230
|
-
updatePreview();
|
|
1231
|
-
});
|
|
1232
|
-
});
|
|
1233
|
-
|
|
1234
|
-
// Ctrl+S keyboard shortcut to save
|
|
1235
|
-
document.addEventListener('keydown', (e) => {
|
|
1236
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
1237
|
-
e.preventDefault();
|
|
1238
|
-
saveManual();
|
|
1239
|
-
}
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
// Auto-update interval system
|
|
1243
|
-
let autoUpdateIntervalId = null;
|
|
1244
|
-
|
|
1245
|
-
function setAutoUpdateInterval() {
|
|
1246
|
-
if (autoUpdateIntervalId) {
|
|
1247
|
-
clearInterval(autoUpdateIntervalId);
|
|
1248
|
-
autoUpdateIntervalId = null;
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
const intervalMs = parseInt(document.getElementById('auto_update_interval').value);
|
|
1252
|
-
if (intervalMs > 0) {
|
|
1253
|
-
autoUpdateIntervalId = setInterval(() => {
|
|
1254
|
-
updatePreview();
|
|
1255
|
-
}, intervalMs);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
"""
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
# EOF
|