scitex 2.7.0__py3-none-any.whl → 2.8.1__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/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -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/__init__.py +5 -2
- scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
- scitex/{vis → fig}/editor/_qt_editor.py +182 -2
- scitex/{vis → fig}/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +1299 -0
- scitex/fig/editor/flask_editor/_core.py +1429 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +813 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +123 -0
- scitex/fig/editor/flask_editor/templates/_html.py +852 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
- scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +1058 -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 +22 -26
- scitex/io/_bundle.py +493 -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/_zip_bundle.py +439 -0
- 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.8.1.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -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/__init__.py +0 -33
- scitex/vis/editor/flask_editor/templates/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
- /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/_flask_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.8.1.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
scitex/schema/_plot.py
ADDED
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-13 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/schema/_plot.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Plot Schema - Canonical source of truth for plot specifications.
|
|
8
|
+
|
|
9
|
+
This module defines the layered schema architecture for plots:
|
|
10
|
+
|
|
11
|
+
1. PlotSpec (spec.json) - Semantic definition: WHAT to plot
|
|
12
|
+
- Traces with type and column mappings
|
|
13
|
+
- Axes configuration (labels, limits)
|
|
14
|
+
- Data source reference
|
|
15
|
+
|
|
16
|
+
2. PlotStyle (style.json) - Appearance: HOW it looks
|
|
17
|
+
- Theme and colors
|
|
18
|
+
- Line widths, marker sizes
|
|
19
|
+
- Font settings
|
|
20
|
+
|
|
21
|
+
3. PlotGeometry (cache/geometry_px.json) - Rendered positions: WHERE (cache)
|
|
22
|
+
- Pixel coordinates
|
|
23
|
+
- Path data for hit testing
|
|
24
|
+
- Bounding boxes in px
|
|
25
|
+
|
|
26
|
+
4. RenderManifest (cache/render_manifest.json) - Render metadata
|
|
27
|
+
- DPI, figure size
|
|
28
|
+
- Source hash for cache invalidation
|
|
29
|
+
|
|
30
|
+
Design Principles:
|
|
31
|
+
- Canonical data uses ratio (0-1) for axes bbox, mm for panel size
|
|
32
|
+
- px data is ALWAYS derived/cached, never source of truth
|
|
33
|
+
- Traces are semantic (boxplot, heatmap) not decomposed (line segments)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from dataclasses import dataclass, field, asdict
|
|
37
|
+
from typing import Dict, Any, List, Optional, Literal, Union
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
import json
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Schema versions
|
|
43
|
+
PLOT_SPEC_VERSION = "1.0.0"
|
|
44
|
+
PLOT_STYLE_VERSION = "1.0.0"
|
|
45
|
+
PLOT_GEOMETRY_VERSION = "1.0.0"
|
|
46
|
+
|
|
47
|
+
# DPI fallback for legacy data without explicit DPI
|
|
48
|
+
# Note: For dynamic DPI resolution, use scitex.plt.styles.get_default_dpi()
|
|
49
|
+
# This constant is only used as a fallback when parsing data without DPI info
|
|
50
|
+
DPI_FALLBACK = 300
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# =============================================================================
|
|
54
|
+
# Type Aliases
|
|
55
|
+
# =============================================================================
|
|
56
|
+
|
|
57
|
+
TraceType = Literal[
|
|
58
|
+
# Line-based
|
|
59
|
+
"line", "step", "stem",
|
|
60
|
+
# Scatter-based
|
|
61
|
+
"scatter", "hexbin",
|
|
62
|
+
# Distribution
|
|
63
|
+
"histogram", "kde", "ecdf", "boxplot", "violinplot", "joyplot",
|
|
64
|
+
# Categorical
|
|
65
|
+
"bar", "barh",
|
|
66
|
+
# 2D/Grid
|
|
67
|
+
"heatmap", "imshow", "contour", "contourf", "pcolormesh",
|
|
68
|
+
# Statistical
|
|
69
|
+
"errorbar", "fill_between", "mean_std", "mean_ci", "median_iqr",
|
|
70
|
+
# Vector
|
|
71
|
+
"quiver", "streamplot",
|
|
72
|
+
# Special
|
|
73
|
+
"pie", "raster", "rectangle",
|
|
74
|
+
# Generic fallback
|
|
75
|
+
"unknown",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
CoordinateSpace = Literal["panel", "figure", "data"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# =============================================================================
|
|
82
|
+
# Bounding Box Specs
|
|
83
|
+
# =============================================================================
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class BboxRatio:
|
|
88
|
+
"""
|
|
89
|
+
Bounding box in normalized coordinates (0-1).
|
|
90
|
+
|
|
91
|
+
This is the CANONICAL representation for axes position within a panel.
|
|
92
|
+
"""
|
|
93
|
+
x0: float
|
|
94
|
+
y0: float
|
|
95
|
+
width: float
|
|
96
|
+
height: float
|
|
97
|
+
space: CoordinateSpace = "panel"
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def x1(self) -> float:
|
|
101
|
+
return self.x0 + self.width
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def y1(self) -> float:
|
|
105
|
+
return self.y0 + self.height
|
|
106
|
+
|
|
107
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
108
|
+
return {
|
|
109
|
+
"x0": self.x0,
|
|
110
|
+
"y0": self.y0,
|
|
111
|
+
"width": self.width,
|
|
112
|
+
"height": self.height,
|
|
113
|
+
"space": self.space,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BboxRatio":
|
|
118
|
+
# Handle both width/height and x1/y1 formats
|
|
119
|
+
if "width" not in data and "x1" in data:
|
|
120
|
+
data = data.copy()
|
|
121
|
+
data["width"] = data["x1"] - data["x0"]
|
|
122
|
+
data["height"] = data["y1"] - data["y0"]
|
|
123
|
+
data.pop("x1", None)
|
|
124
|
+
data.pop("y1", None)
|
|
125
|
+
return cls(**{k: v for k, v in data.items() if k in ["x0", "y0", "width", "height", "space"]})
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class BboxPx:
|
|
130
|
+
"""
|
|
131
|
+
Bounding box in pixel coordinates.
|
|
132
|
+
|
|
133
|
+
This is DERIVED/CACHED, not canonical.
|
|
134
|
+
"""
|
|
135
|
+
x0: float
|
|
136
|
+
y0: float
|
|
137
|
+
width: float
|
|
138
|
+
height: float
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def x1(self) -> float:
|
|
142
|
+
return self.x0 + self.width
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def y1(self) -> float:
|
|
146
|
+
return self.y0 + self.height
|
|
147
|
+
|
|
148
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
149
|
+
return {
|
|
150
|
+
"x0": self.x0,
|
|
151
|
+
"y0": self.y0,
|
|
152
|
+
"x1": self.x1,
|
|
153
|
+
"y1": self.y1,
|
|
154
|
+
"width": self.width,
|
|
155
|
+
"height": self.height,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BboxPx":
|
|
160
|
+
if "width" not in data and "x1" in data:
|
|
161
|
+
data = data.copy()
|
|
162
|
+
data["width"] = data["x1"] - data["x0"]
|
|
163
|
+
data["height"] = data["y1"] - data["y0"]
|
|
164
|
+
return cls(
|
|
165
|
+
x0=data["x0"],
|
|
166
|
+
y0=data["y0"],
|
|
167
|
+
width=data.get("width", 0),
|
|
168
|
+
height=data.get("height", 0),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# =============================================================================
|
|
173
|
+
# Trace Specification (Semantic)
|
|
174
|
+
# =============================================================================
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class TraceSpec:
|
|
179
|
+
"""
|
|
180
|
+
Semantic specification for a single trace/artist.
|
|
181
|
+
|
|
182
|
+
This captures WHAT the user intended to plot, not how it was rendered.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
id : str
|
|
187
|
+
Unique identifier for this trace
|
|
188
|
+
type : TraceType
|
|
189
|
+
Semantic type (boxplot, heatmap, line, etc.)
|
|
190
|
+
x_col : str, optional
|
|
191
|
+
Column name for x data (line, scatter, bar, etc.)
|
|
192
|
+
y_col : str, optional
|
|
193
|
+
Column name for y data (line, scatter)
|
|
194
|
+
data_cols : list, optional
|
|
195
|
+
Column names for multi-column data (boxplot, violinplot)
|
|
196
|
+
value_col : str, optional
|
|
197
|
+
Column name for values (heatmap, contour)
|
|
198
|
+
u_col, v_col : str, optional
|
|
199
|
+
Column names for vector components (quiver, streamplot)
|
|
200
|
+
label : str, optional
|
|
201
|
+
Legend label
|
|
202
|
+
group : str, optional
|
|
203
|
+
Grouping identifier for related traces
|
|
204
|
+
|
|
205
|
+
Examples
|
|
206
|
+
--------
|
|
207
|
+
>>> # Line plot
|
|
208
|
+
>>> TraceSpec(id="line-0", type="line", x_col="time", y_col="signal", label="EEG")
|
|
209
|
+
|
|
210
|
+
>>> # Boxplot with 4 groups
|
|
211
|
+
>>> TraceSpec(id="box-0", type="boxplot", data_cols=["A", "B", "C", "D"])
|
|
212
|
+
|
|
213
|
+
>>> # Heatmap
|
|
214
|
+
>>> TraceSpec(id="hmap-0", type="heatmap", x_col="x", y_col="y", value_col="z")
|
|
215
|
+
|
|
216
|
+
>>> # Quiver (vector field)
|
|
217
|
+
>>> TraceSpec(id="quiv-0", type="quiver", x_col="x", y_col="y", u_col="u", v_col="v")
|
|
218
|
+
"""
|
|
219
|
+
id: str
|
|
220
|
+
type: TraceType
|
|
221
|
+
|
|
222
|
+
# Column mappings (usage depends on trace type)
|
|
223
|
+
x_col: Optional[str] = None
|
|
224
|
+
y_col: Optional[str] = None
|
|
225
|
+
data_cols: Optional[List[str]] = None # For boxplot, violin, etc.
|
|
226
|
+
value_col: Optional[str] = None # For heatmap, contour
|
|
227
|
+
u_col: Optional[str] = None # For quiver
|
|
228
|
+
v_col: Optional[str] = None # For quiver
|
|
229
|
+
|
|
230
|
+
# Metadata
|
|
231
|
+
label: Optional[str] = None
|
|
232
|
+
group: Optional[str] = None
|
|
233
|
+
axes_index: int = 0 # Which axes this trace belongs to
|
|
234
|
+
|
|
235
|
+
# Additional type-specific parameters
|
|
236
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
237
|
+
|
|
238
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
239
|
+
result = {
|
|
240
|
+
"id": self.id,
|
|
241
|
+
"type": self.type,
|
|
242
|
+
"axes_index": self.axes_index,
|
|
243
|
+
}
|
|
244
|
+
# Only include non-None fields
|
|
245
|
+
if self.x_col:
|
|
246
|
+
result["x_col"] = self.x_col
|
|
247
|
+
if self.y_col:
|
|
248
|
+
result["y_col"] = self.y_col
|
|
249
|
+
if self.data_cols:
|
|
250
|
+
result["data_cols"] = self.data_cols
|
|
251
|
+
if self.value_col:
|
|
252
|
+
result["value_col"] = self.value_col
|
|
253
|
+
if self.u_col:
|
|
254
|
+
result["u_col"] = self.u_col
|
|
255
|
+
if self.v_col:
|
|
256
|
+
result["v_col"] = self.v_col
|
|
257
|
+
if self.label:
|
|
258
|
+
result["label"] = self.label
|
|
259
|
+
if self.group:
|
|
260
|
+
result["group"] = self.group
|
|
261
|
+
if self.extra:
|
|
262
|
+
result["extra"] = self.extra
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TraceSpec":
|
|
267
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# =============================================================================
|
|
271
|
+
# Axes Specification (Semantic)
|
|
272
|
+
# =============================================================================
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass
|
|
276
|
+
class AxesLimits:
|
|
277
|
+
"""Axis limits specification."""
|
|
278
|
+
x: Optional[List[float]] = None # [xmin, xmax]
|
|
279
|
+
y: Optional[List[float]] = None # [ymin, ymax]
|
|
280
|
+
|
|
281
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
282
|
+
result = {}
|
|
283
|
+
if self.x:
|
|
284
|
+
result["x"] = self.x
|
|
285
|
+
if self.y:
|
|
286
|
+
result["y"] = self.y
|
|
287
|
+
return result
|
|
288
|
+
|
|
289
|
+
@classmethod
|
|
290
|
+
def from_dict(cls, data: Dict[str, Any]) -> "AxesLimits":
|
|
291
|
+
return cls(x=data.get("x"), y=data.get("y"))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass
|
|
295
|
+
class AxesLabels:
|
|
296
|
+
"""Axes labels specification."""
|
|
297
|
+
xlabel: Optional[str] = None
|
|
298
|
+
ylabel: Optional[str] = None
|
|
299
|
+
title: Optional[str] = None
|
|
300
|
+
|
|
301
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
302
|
+
result = {}
|
|
303
|
+
if self.xlabel:
|
|
304
|
+
result["xlabel"] = self.xlabel
|
|
305
|
+
if self.ylabel:
|
|
306
|
+
result["ylabel"] = self.ylabel
|
|
307
|
+
if self.title:
|
|
308
|
+
result["title"] = self.title
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
@classmethod
|
|
312
|
+
def from_dict(cls, data: Dict[str, Any]) -> "AxesLabels":
|
|
313
|
+
return cls(**{k: v for k, v in data.items() if k in ["xlabel", "ylabel", "title"]})
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@dataclass
|
|
317
|
+
class AxesSpecItem:
|
|
318
|
+
"""
|
|
319
|
+
Specification for a single axes within a plot.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
id : str
|
|
324
|
+
Unique identifier (e.g., "ax0", "colorbar")
|
|
325
|
+
bbox : BboxRatio
|
|
326
|
+
Position in normalized coordinates (0-1) within the panel
|
|
327
|
+
labels : AxesLabels
|
|
328
|
+
Axis labels and title
|
|
329
|
+
limits : AxesLimits, optional
|
|
330
|
+
Axis limits (auto if not specified)
|
|
331
|
+
role : str
|
|
332
|
+
Role of this axes ("main", "colorbar", "inset", etc.)
|
|
333
|
+
linked_to : str, optional
|
|
334
|
+
ID of axes this is linked to (e.g., colorbar linked to heatmap axes)
|
|
335
|
+
"""
|
|
336
|
+
id: str
|
|
337
|
+
bbox: BboxRatio
|
|
338
|
+
labels: AxesLabels = field(default_factory=AxesLabels)
|
|
339
|
+
limits: Optional[AxesLimits] = None
|
|
340
|
+
role: str = "main" # "main", "colorbar", "inset", "twinx", "twiny"
|
|
341
|
+
linked_to: Optional[str] = None
|
|
342
|
+
|
|
343
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
344
|
+
result = {
|
|
345
|
+
"id": self.id,
|
|
346
|
+
"bbox": self.bbox.to_dict(),
|
|
347
|
+
"labels": self.labels.to_dict(),
|
|
348
|
+
"role": self.role,
|
|
349
|
+
}
|
|
350
|
+
if self.limits:
|
|
351
|
+
result["limits"] = self.limits.to_dict()
|
|
352
|
+
if self.linked_to:
|
|
353
|
+
result["linked_to"] = self.linked_to
|
|
354
|
+
return result
|
|
355
|
+
|
|
356
|
+
@classmethod
|
|
357
|
+
def from_dict(cls, data: Dict[str, Any]) -> "AxesSpecItem":
|
|
358
|
+
data_copy = data.copy()
|
|
359
|
+
if "bbox" in data_copy:
|
|
360
|
+
data_copy["bbox"] = BboxRatio.from_dict(data_copy["bbox"])
|
|
361
|
+
else:
|
|
362
|
+
# Default bbox
|
|
363
|
+
data_copy["bbox"] = BboxRatio(x0=0.15, y0=0.15, width=0.7, height=0.7)
|
|
364
|
+
if "labels" in data_copy:
|
|
365
|
+
data_copy["labels"] = AxesLabels.from_dict(data_copy["labels"])
|
|
366
|
+
if "limits" in data_copy and data_copy["limits"]:
|
|
367
|
+
data_copy["limits"] = AxesLimits.from_dict(data_copy["limits"])
|
|
368
|
+
return cls(**{k: v for k, v in data_copy.items() if k in cls.__dataclass_fields__})
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# =============================================================================
|
|
372
|
+
# Data Source Specification
|
|
373
|
+
# =============================================================================
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@dataclass
|
|
377
|
+
class DataSourceSpec:
|
|
378
|
+
"""
|
|
379
|
+
Specification for the data source.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
csv : str
|
|
384
|
+
Relative path to CSV file
|
|
385
|
+
format : str
|
|
386
|
+
Data format ("wide" or "long")
|
|
387
|
+
hash : str, optional
|
|
388
|
+
Content hash for integrity verification
|
|
389
|
+
"""
|
|
390
|
+
csv: str
|
|
391
|
+
format: str = "wide"
|
|
392
|
+
hash: Optional[str] = None
|
|
393
|
+
|
|
394
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
395
|
+
result = {"csv": self.csv, "format": self.format}
|
|
396
|
+
if self.hash:
|
|
397
|
+
result["hash"] = self.hash
|
|
398
|
+
return result
|
|
399
|
+
|
|
400
|
+
@classmethod
|
|
401
|
+
def from_dict(cls, data: Dict[str, Any]) -> "DataSourceSpec":
|
|
402
|
+
# Handle legacy "source" or "path" keys
|
|
403
|
+
csv = data.get("csv") or data.get("source") or data.get("path", "")
|
|
404
|
+
return cls(
|
|
405
|
+
csv=csv,
|
|
406
|
+
format=data.get("format", "wide"),
|
|
407
|
+
hash=data.get("hash"),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# =============================================================================
|
|
412
|
+
# PlotSpec - Main Semantic Specification
|
|
413
|
+
# =============================================================================
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@dataclass
|
|
417
|
+
class PlotSpec:
|
|
418
|
+
"""
|
|
419
|
+
Complete semantic specification for a plot.
|
|
420
|
+
|
|
421
|
+
This is the SOURCE OF TRUTH stored in spec.json.
|
|
422
|
+
Contains only semantic information about WHAT to plot.
|
|
423
|
+
|
|
424
|
+
Parameters
|
|
425
|
+
----------
|
|
426
|
+
plot_id : str
|
|
427
|
+
Unique identifier for this plot
|
|
428
|
+
data : DataSourceSpec
|
|
429
|
+
Data source specification
|
|
430
|
+
axes : list of AxesSpecItem
|
|
431
|
+
Axes configurations
|
|
432
|
+
traces : list of TraceSpec
|
|
433
|
+
Trace/artist specifications
|
|
434
|
+
|
|
435
|
+
Examples
|
|
436
|
+
--------
|
|
437
|
+
>>> spec = PlotSpec(
|
|
438
|
+
... plot_id="panel_A",
|
|
439
|
+
... data=DataSourceSpec(csv="data.csv"),
|
|
440
|
+
... axes=[AxesSpecItem(id="ax0", bbox=BboxRatio(0.15, 0.15, 0.7, 0.7))],
|
|
441
|
+
... traces=[TraceSpec(id="line-0", type="line", x_col="x", y_col="y")]
|
|
442
|
+
... )
|
|
443
|
+
>>> spec.to_json()
|
|
444
|
+
"""
|
|
445
|
+
plot_id: str
|
|
446
|
+
data: DataSourceSpec
|
|
447
|
+
axes: List[AxesSpecItem] = field(default_factory=list)
|
|
448
|
+
traces: List[TraceSpec] = field(default_factory=list)
|
|
449
|
+
|
|
450
|
+
# Schema metadata
|
|
451
|
+
scitex_schema: str = "scitex.plt.spec"
|
|
452
|
+
scitex_schema_version: str = PLOT_SPEC_VERSION
|
|
453
|
+
|
|
454
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
455
|
+
return {
|
|
456
|
+
"schema": {
|
|
457
|
+
"name": self.scitex_schema,
|
|
458
|
+
"version": self.scitex_schema_version,
|
|
459
|
+
},
|
|
460
|
+
"plot_id": self.plot_id,
|
|
461
|
+
"data": self.data.to_dict(),
|
|
462
|
+
"axes": [ax.to_dict() for ax in self.axes],
|
|
463
|
+
"traces": [tr.to_dict() for tr in self.traces],
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
def to_json(self, indent: int = 2) -> str:
|
|
467
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
468
|
+
|
|
469
|
+
@classmethod
|
|
470
|
+
def from_dict(cls, data: Dict[str, Any]) -> "PlotSpec":
|
|
471
|
+
schema_info = data.get("schema", {})
|
|
472
|
+
return cls(
|
|
473
|
+
plot_id=data.get("plot_id", ""),
|
|
474
|
+
data=DataSourceSpec.from_dict(data.get("data", {})),
|
|
475
|
+
axes=[AxesSpecItem.from_dict(ax) for ax in data.get("axes", [])],
|
|
476
|
+
traces=[TraceSpec.from_dict(tr) for tr in data.get("traces", [])],
|
|
477
|
+
scitex_schema=schema_info.get("name", "scitex.plt.spec"),
|
|
478
|
+
scitex_schema_version=schema_info.get("version", PLOT_SPEC_VERSION),
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
@classmethod
|
|
482
|
+
def from_json(cls, json_str: str) -> "PlotSpec":
|
|
483
|
+
return cls.from_dict(json.loads(json_str))
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
# =============================================================================
|
|
487
|
+
# PlotStyle - Appearance Specification
|
|
488
|
+
# =============================================================================
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@dataclass
|
|
492
|
+
class TraceStyleSpec:
|
|
493
|
+
"""Style overrides for a specific trace."""
|
|
494
|
+
trace_id: str
|
|
495
|
+
color: Optional[str] = None
|
|
496
|
+
linewidth: Optional[float] = None
|
|
497
|
+
linestyle: Optional[str] = None
|
|
498
|
+
marker: Optional[str] = None
|
|
499
|
+
markersize: Optional[float] = None
|
|
500
|
+
alpha: Optional[float] = None
|
|
501
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
502
|
+
|
|
503
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
504
|
+
result = {"trace_id": self.trace_id}
|
|
505
|
+
for field_name in ["color", "linewidth", "linestyle", "marker", "markersize", "alpha"]:
|
|
506
|
+
val = getattr(self, field_name)
|
|
507
|
+
if val is not None:
|
|
508
|
+
result[field_name] = val
|
|
509
|
+
if self.extra:
|
|
510
|
+
result["extra"] = self.extra
|
|
511
|
+
return result
|
|
512
|
+
|
|
513
|
+
@classmethod
|
|
514
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TraceStyleSpec":
|
|
515
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@dataclass
|
|
519
|
+
class ThemeSpec:
|
|
520
|
+
"""Theme specification."""
|
|
521
|
+
mode: str = "light" # "light", "dark", "auto"
|
|
522
|
+
colors: Dict[str, str] = field(default_factory=lambda: {
|
|
523
|
+
"background": "transparent",
|
|
524
|
+
"axes_bg": "white",
|
|
525
|
+
"text": "black",
|
|
526
|
+
"spine": "black",
|
|
527
|
+
"tick": "black",
|
|
528
|
+
})
|
|
529
|
+
palette: Optional[str] = None # Color palette name
|
|
530
|
+
|
|
531
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
532
|
+
result = {"mode": self.mode, "colors": self.colors}
|
|
533
|
+
if self.palette:
|
|
534
|
+
result["palette"] = self.palette
|
|
535
|
+
return result
|
|
536
|
+
|
|
537
|
+
@classmethod
|
|
538
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ThemeSpec":
|
|
539
|
+
return cls(
|
|
540
|
+
mode=data.get("mode", "light"),
|
|
541
|
+
colors=data.get("colors", {}),
|
|
542
|
+
palette=data.get("palette"),
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
@dataclass
|
|
547
|
+
class FontSpec:
|
|
548
|
+
"""Font specification."""
|
|
549
|
+
family: str = "sans-serif"
|
|
550
|
+
size_pt: float = 7.0
|
|
551
|
+
title_size_pt: float = 8.0
|
|
552
|
+
label_size_pt: float = 7.0
|
|
553
|
+
tick_size_pt: float = 6.0
|
|
554
|
+
|
|
555
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
556
|
+
return asdict(self)
|
|
557
|
+
|
|
558
|
+
@classmethod
|
|
559
|
+
def from_dict(cls, data: Dict[str, Any]) -> "FontSpec":
|
|
560
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
@dataclass
|
|
564
|
+
class SizeSpec:
|
|
565
|
+
"""Panel size specification (canonical in mm)."""
|
|
566
|
+
width_mm: float = 80.0
|
|
567
|
+
height_mm: float = 68.0
|
|
568
|
+
|
|
569
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
570
|
+
return asdict(self)
|
|
571
|
+
|
|
572
|
+
@classmethod
|
|
573
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SizeSpec":
|
|
574
|
+
return cls(
|
|
575
|
+
width_mm=data.get("width_mm", 80.0),
|
|
576
|
+
height_mm=data.get("height_mm", 68.0),
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# Valid matplotlib legend location strings
|
|
581
|
+
LegendLocation = Literal[
|
|
582
|
+
"best", "upper right", "upper left", "lower left", "lower right",
|
|
583
|
+
"right", "center left", "center right", "lower center", "upper center",
|
|
584
|
+
"center",
|
|
585
|
+
]
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@dataclass
|
|
589
|
+
class LegendSpec:
|
|
590
|
+
"""
|
|
591
|
+
Legend configuration specification.
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
visible : bool
|
|
596
|
+
Whether to show the legend (default True)
|
|
597
|
+
location : str
|
|
598
|
+
Legend location. Valid values:
|
|
599
|
+
- "best" (auto-placement)
|
|
600
|
+
- "upper right", "upper left", "lower right", "lower left"
|
|
601
|
+
- "right", "center left", "center right"
|
|
602
|
+
- "upper center", "lower center", "center"
|
|
603
|
+
frameon : bool
|
|
604
|
+
Whether to draw a frame around the legend
|
|
605
|
+
fontsize : float, optional
|
|
606
|
+
Font size for legend text (in points)
|
|
607
|
+
ncols : int
|
|
608
|
+
Number of columns in the legend
|
|
609
|
+
title : str, optional
|
|
610
|
+
Legend title
|
|
611
|
+
"""
|
|
612
|
+
visible: bool = True
|
|
613
|
+
location: str = "best"
|
|
614
|
+
frameon: bool = True
|
|
615
|
+
fontsize: Optional[float] = None
|
|
616
|
+
ncols: int = 1
|
|
617
|
+
title: Optional[str] = None
|
|
618
|
+
|
|
619
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
620
|
+
result = {
|
|
621
|
+
"visible": self.visible,
|
|
622
|
+
"location": self.location,
|
|
623
|
+
"frameon": self.frameon,
|
|
624
|
+
"ncols": self.ncols,
|
|
625
|
+
}
|
|
626
|
+
if self.fontsize is not None:
|
|
627
|
+
result["fontsize"] = self.fontsize
|
|
628
|
+
if self.title is not None:
|
|
629
|
+
result["title"] = self.title
|
|
630
|
+
return result
|
|
631
|
+
|
|
632
|
+
@classmethod
|
|
633
|
+
def from_dict(cls, data: Dict[str, Any]) -> "LegendSpec":
|
|
634
|
+
# Handle backward compatibility: boolean legend value
|
|
635
|
+
if isinstance(data, bool):
|
|
636
|
+
return cls(visible=data)
|
|
637
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@dataclass
|
|
641
|
+
class PlotStyle:
|
|
642
|
+
"""
|
|
643
|
+
Appearance specification for a plot.
|
|
644
|
+
|
|
645
|
+
Stored in style.json. Contains HOW the plot looks.
|
|
646
|
+
Only stores overrides from defaults.
|
|
647
|
+
|
|
648
|
+
Parameters
|
|
649
|
+
----------
|
|
650
|
+
theme : ThemeSpec
|
|
651
|
+
Theme configuration
|
|
652
|
+
size : SizeSpec
|
|
653
|
+
Panel size in mm (canonical unit)
|
|
654
|
+
font : FontSpec
|
|
655
|
+
Font settings
|
|
656
|
+
traces : list of TraceStyleSpec
|
|
657
|
+
Per-trace style overrides
|
|
658
|
+
legend : LegendSpec
|
|
659
|
+
Legend configuration (visibility, location, styling)
|
|
660
|
+
grid : bool
|
|
661
|
+
Whether to show grid lines
|
|
662
|
+
"""
|
|
663
|
+
theme: ThemeSpec = field(default_factory=ThemeSpec)
|
|
664
|
+
size: SizeSpec = field(default_factory=SizeSpec)
|
|
665
|
+
font: FontSpec = field(default_factory=FontSpec)
|
|
666
|
+
traces: List[TraceStyleSpec] = field(default_factory=list)
|
|
667
|
+
legend: LegendSpec = field(default_factory=LegendSpec)
|
|
668
|
+
|
|
669
|
+
# Axes-level overrides
|
|
670
|
+
grid: bool = False
|
|
671
|
+
|
|
672
|
+
# Schema metadata
|
|
673
|
+
scitex_schema: str = "scitex.plt.style"
|
|
674
|
+
scitex_schema_version: str = PLOT_STYLE_VERSION
|
|
675
|
+
|
|
676
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
677
|
+
return {
|
|
678
|
+
"schema": {
|
|
679
|
+
"name": self.scitex_schema,
|
|
680
|
+
"version": self.scitex_schema_version,
|
|
681
|
+
},
|
|
682
|
+
"theme": self.theme.to_dict(),
|
|
683
|
+
"size": self.size.to_dict(),
|
|
684
|
+
"font": self.font.to_dict(),
|
|
685
|
+
"traces": [tr.to_dict() for tr in self.traces],
|
|
686
|
+
"legend": self.legend.to_dict(),
|
|
687
|
+
"grid": self.grid,
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
def to_json(self, indent: int = 2) -> str:
|
|
691
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
692
|
+
|
|
693
|
+
@classmethod
|
|
694
|
+
def from_dict(cls, data: Dict[str, Any]) -> "PlotStyle":
|
|
695
|
+
# Handle backward compatibility: legend can be bool or dict
|
|
696
|
+
legend_data = data.get("legend", True)
|
|
697
|
+
if isinstance(legend_data, bool):
|
|
698
|
+
legend = LegendSpec(visible=legend_data)
|
|
699
|
+
elif isinstance(legend_data, dict):
|
|
700
|
+
legend = LegendSpec.from_dict(legend_data)
|
|
701
|
+
else:
|
|
702
|
+
legend = LegendSpec()
|
|
703
|
+
|
|
704
|
+
return cls(
|
|
705
|
+
theme=ThemeSpec.from_dict(data.get("theme", {})),
|
|
706
|
+
size=SizeSpec.from_dict(data.get("size", {})),
|
|
707
|
+
font=FontSpec.from_dict(data.get("font", {})),
|
|
708
|
+
traces=[TraceStyleSpec.from_dict(tr) for tr in data.get("traces", [])],
|
|
709
|
+
legend=legend,
|
|
710
|
+
grid=data.get("grid", False),
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
@classmethod
|
|
714
|
+
def from_json(cls, json_str: str) -> "PlotStyle":
|
|
715
|
+
return cls.from_dict(json.loads(json_str))
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
# =============================================================================
|
|
719
|
+
# PlotGeometry - Cached Render Output
|
|
720
|
+
# =============================================================================
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
@dataclass
|
|
724
|
+
class RenderedArtist:
|
|
725
|
+
"""
|
|
726
|
+
Cached pixel-level data for a rendered artist.
|
|
727
|
+
|
|
728
|
+
This is DERIVED from PlotSpec + PlotStyle, not source of truth.
|
|
729
|
+
"""
|
|
730
|
+
id: str
|
|
731
|
+
type: str
|
|
732
|
+
axes_index: int
|
|
733
|
+
label: Optional[str] = None
|
|
734
|
+
bbox_px: Optional[BboxPx] = None
|
|
735
|
+
path_px: Optional[List[List[float]]] = None # [[x, y], [x, y], ...]
|
|
736
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
737
|
+
|
|
738
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
739
|
+
result = {
|
|
740
|
+
"id": self.id,
|
|
741
|
+
"type": self.type,
|
|
742
|
+
"axes_index": self.axes_index,
|
|
743
|
+
}
|
|
744
|
+
if self.label:
|
|
745
|
+
result["label"] = self.label
|
|
746
|
+
if self.bbox_px:
|
|
747
|
+
result["bbox_px"] = self.bbox_px.to_dict()
|
|
748
|
+
if self.path_px:
|
|
749
|
+
result["path_px"] = self.path_px
|
|
750
|
+
if self.extra:
|
|
751
|
+
result.update(self.extra)
|
|
752
|
+
return result
|
|
753
|
+
|
|
754
|
+
@classmethod
|
|
755
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RenderedArtist":
|
|
756
|
+
data_copy = data.copy()
|
|
757
|
+
if "bbox_px" in data_copy and data_copy["bbox_px"]:
|
|
758
|
+
data_copy["bbox_px"] = BboxPx.from_dict(data_copy["bbox_px"])
|
|
759
|
+
return cls(**{k: v for k, v in data_copy.items() if k in cls.__dataclass_fields__})
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
@dataclass
|
|
763
|
+
class RenderedAxes:
|
|
764
|
+
"""Cached pixel-level data for rendered axes."""
|
|
765
|
+
id: str
|
|
766
|
+
xlim: List[float]
|
|
767
|
+
ylim: List[float]
|
|
768
|
+
bbox_px: BboxPx
|
|
769
|
+
|
|
770
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
771
|
+
return {
|
|
772
|
+
"id": self.id,
|
|
773
|
+
"xlim": self.xlim,
|
|
774
|
+
"ylim": self.ylim,
|
|
775
|
+
"bbox_px": self.bbox_px.to_dict(),
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
@classmethod
|
|
779
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RenderedAxes":
|
|
780
|
+
return cls(
|
|
781
|
+
id=data.get("id", "ax0"),
|
|
782
|
+
xlim=data.get("xlim", [0, 1]),
|
|
783
|
+
ylim=data.get("ylim", [0, 1]),
|
|
784
|
+
bbox_px=BboxPx.from_dict(data.get("bbox_px", {"x0": 0, "y0": 0, "width": 100, "height": 100})),
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
@dataclass
|
|
789
|
+
class HitRegionEntry:
|
|
790
|
+
"""Entry in the hit region color map."""
|
|
791
|
+
id: int
|
|
792
|
+
type: str
|
|
793
|
+
label: str
|
|
794
|
+
axes_index: int
|
|
795
|
+
rgb: List[int]
|
|
796
|
+
group_id: Optional[str] = None
|
|
797
|
+
role: str = "standalone"
|
|
798
|
+
|
|
799
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
800
|
+
return asdict(self)
|
|
801
|
+
|
|
802
|
+
@classmethod
|
|
803
|
+
def from_dict(cls, data: Dict[str, Any]) -> "HitRegionEntry":
|
|
804
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@dataclass
|
|
808
|
+
class SelectableRegion:
|
|
809
|
+
"""Selectable region for GUI interaction."""
|
|
810
|
+
bbox_px: List[float] # [x0, y0, x1, y1]
|
|
811
|
+
text: Optional[str] = None
|
|
812
|
+
fontsize: Optional[float] = None
|
|
813
|
+
color: Optional[str] = None
|
|
814
|
+
|
|
815
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
816
|
+
result = {"bbox_px": self.bbox_px}
|
|
817
|
+
if self.text:
|
|
818
|
+
result["text"] = self.text
|
|
819
|
+
if self.fontsize:
|
|
820
|
+
result["fontsize"] = self.fontsize
|
|
821
|
+
if self.color:
|
|
822
|
+
result["color"] = self.color
|
|
823
|
+
return result
|
|
824
|
+
|
|
825
|
+
@classmethod
|
|
826
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SelectableRegion":
|
|
827
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
@dataclass
|
|
831
|
+
class PlotGeometry:
|
|
832
|
+
"""
|
|
833
|
+
Cached geometry data for a rendered plot.
|
|
834
|
+
|
|
835
|
+
Stored in cache/geometry_px.json. This is DERIVED output.
|
|
836
|
+
Can be deleted and regenerated from PlotSpec + PlotStyle.
|
|
837
|
+
|
|
838
|
+
Parameters
|
|
839
|
+
----------
|
|
840
|
+
source_hash : str
|
|
841
|
+
Hash of spec + style that produced this geometry
|
|
842
|
+
figure_px : tuple
|
|
843
|
+
Rendered figure size (width, height) in pixels
|
|
844
|
+
dpi : int
|
|
845
|
+
DPI used for rendering
|
|
846
|
+
axes : list of RenderedAxes
|
|
847
|
+
Pixel-level axes data
|
|
848
|
+
artists : list of RenderedArtist
|
|
849
|
+
Pixel-level artist data
|
|
850
|
+
hit_regions : dict
|
|
851
|
+
Hit testing data (color_map, groups)
|
|
852
|
+
selectable_regions : dict
|
|
853
|
+
GUI-selectable regions
|
|
854
|
+
"""
|
|
855
|
+
source_hash: str
|
|
856
|
+
figure_px: List[int] # [width, height]
|
|
857
|
+
dpi: int
|
|
858
|
+
axes: List[RenderedAxes] = field(default_factory=list)
|
|
859
|
+
artists: List[RenderedArtist] = field(default_factory=list)
|
|
860
|
+
hit_regions: Dict[str, Any] = field(default_factory=dict)
|
|
861
|
+
selectable_regions: Dict[str, Any] = field(default_factory=dict)
|
|
862
|
+
crop_box: Optional[Dict[str, int]] = None
|
|
863
|
+
|
|
864
|
+
# Schema metadata
|
|
865
|
+
scitex_schema: str = "scitex.plt.geometry"
|
|
866
|
+
scitex_schema_version: str = PLOT_GEOMETRY_VERSION
|
|
867
|
+
|
|
868
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
869
|
+
return {
|
|
870
|
+
"schema": {
|
|
871
|
+
"name": self.scitex_schema,
|
|
872
|
+
"version": self.scitex_schema_version,
|
|
873
|
+
},
|
|
874
|
+
"source_hash": self.source_hash,
|
|
875
|
+
"figure_px": self.figure_px,
|
|
876
|
+
"dpi": self.dpi,
|
|
877
|
+
"axes": [ax.to_dict() for ax in self.axes],
|
|
878
|
+
"artists": [ar.to_dict() for ar in self.artists],
|
|
879
|
+
"hit_regions": self.hit_regions,
|
|
880
|
+
"selectable_regions": self.selectable_regions,
|
|
881
|
+
"crop_box": self.crop_box,
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
def to_json(self, indent: int = 2) -> str:
|
|
885
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
886
|
+
|
|
887
|
+
@classmethod
|
|
888
|
+
def from_dict(cls, data: Dict[str, Any]) -> "PlotGeometry":
|
|
889
|
+
return cls(
|
|
890
|
+
source_hash=data.get("source_hash", ""),
|
|
891
|
+
figure_px=data.get("figure_px", [944, 803]),
|
|
892
|
+
dpi=data.get("dpi", DPI_FALLBACK),
|
|
893
|
+
axes=[RenderedAxes.from_dict(ax) for ax in data.get("axes", [])],
|
|
894
|
+
artists=[RenderedArtist.from_dict(ar) for ar in data.get("artists", [])],
|
|
895
|
+
hit_regions=data.get("hit_regions", {}),
|
|
896
|
+
selectable_regions=data.get("selectable_regions", {}),
|
|
897
|
+
crop_box=data.get("crop_box"),
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
@classmethod
|
|
901
|
+
def from_json(cls, json_str: str) -> "PlotGeometry":
|
|
902
|
+
return cls.from_dict(json.loads(json_str))
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
# =============================================================================
|
|
906
|
+
# RenderManifest - Render Configuration and Metadata
|
|
907
|
+
# =============================================================================
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
@dataclass
|
|
911
|
+
class RenderManifest:
|
|
912
|
+
"""
|
|
913
|
+
Manifest for rendered outputs.
|
|
914
|
+
|
|
915
|
+
Stored in cache/render_manifest.json.
|
|
916
|
+
Contains metadata about how the render was produced.
|
|
917
|
+
"""
|
|
918
|
+
source_hash: str # Hash of spec + style
|
|
919
|
+
panel_size_mm: List[float] # [width, height]
|
|
920
|
+
|
|
921
|
+
# Output files
|
|
922
|
+
overview_png: Optional[str] = None
|
|
923
|
+
overview_svg: Optional[str] = None
|
|
924
|
+
hitmap_png: Optional[str] = None
|
|
925
|
+
hitmap_svg: Optional[str] = None
|
|
926
|
+
|
|
927
|
+
# Render settings
|
|
928
|
+
dpi: int = DPI_FALLBACK # Use scitex.plt.styles.get_default_dpi() for dynamic resolution
|
|
929
|
+
render_px: Optional[List[int]] = None # [width, height]
|
|
930
|
+
crop_margin_mm: float = 1.0
|
|
931
|
+
|
|
932
|
+
# Timestamps
|
|
933
|
+
rendered_at: Optional[str] = None
|
|
934
|
+
|
|
935
|
+
# Schema metadata
|
|
936
|
+
scitex_schema: str = "scitex.plt.render_manifest"
|
|
937
|
+
scitex_schema_version: str = "1.0.0"
|
|
938
|
+
|
|
939
|
+
def __post_init__(self):
|
|
940
|
+
if self.rendered_at is None:
|
|
941
|
+
self.rendered_at = datetime.now().isoformat()
|
|
942
|
+
|
|
943
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
944
|
+
return {
|
|
945
|
+
"schema": {
|
|
946
|
+
"name": self.scitex_schema,
|
|
947
|
+
"version": self.scitex_schema_version,
|
|
948
|
+
},
|
|
949
|
+
"source_hash": self.source_hash,
|
|
950
|
+
"panel_size_mm": self.panel_size_mm,
|
|
951
|
+
"overview_png": self.overview_png,
|
|
952
|
+
"overview_svg": self.overview_svg,
|
|
953
|
+
"hitmap_png": self.hitmap_png,
|
|
954
|
+
"hitmap_svg": self.hitmap_svg,
|
|
955
|
+
"dpi": self.dpi,
|
|
956
|
+
"render_px": self.render_px,
|
|
957
|
+
"crop_margin_mm": self.crop_margin_mm,
|
|
958
|
+
"rendered_at": self.rendered_at,
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
def to_json(self, indent: int = 2) -> str:
|
|
962
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
963
|
+
|
|
964
|
+
@classmethod
|
|
965
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RenderManifest":
|
|
966
|
+
return cls(**{k: v for k, v in data.items()
|
|
967
|
+
if k in cls.__dataclass_fields__ and k != "scitex_schema" and k != "scitex_schema_version"})
|
|
968
|
+
|
|
969
|
+
@classmethod
|
|
970
|
+
def from_json(cls, json_str: str) -> "RenderManifest":
|
|
971
|
+
return cls.from_dict(json.loads(json_str))
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
# =============================================================================
|
|
975
|
+
# Public API
|
|
976
|
+
# =============================================================================
|
|
977
|
+
|
|
978
|
+
__all__ = [
|
|
979
|
+
# Version constants
|
|
980
|
+
"PLOT_SPEC_VERSION",
|
|
981
|
+
"PLOT_STYLE_VERSION",
|
|
982
|
+
"PLOT_GEOMETRY_VERSION",
|
|
983
|
+
# Type aliases
|
|
984
|
+
"TraceType",
|
|
985
|
+
"CoordinateSpace",
|
|
986
|
+
"LegendLocation",
|
|
987
|
+
# Bbox classes
|
|
988
|
+
"BboxRatio",
|
|
989
|
+
"BboxPx",
|
|
990
|
+
# Spec classes (canonical)
|
|
991
|
+
"TraceSpec",
|
|
992
|
+
"AxesLimits",
|
|
993
|
+
"AxesLabels",
|
|
994
|
+
"AxesSpecItem",
|
|
995
|
+
"DataSourceSpec",
|
|
996
|
+
"PlotSpec",
|
|
997
|
+
# Style classes
|
|
998
|
+
"TraceStyleSpec",
|
|
999
|
+
"ThemeSpec",
|
|
1000
|
+
"FontSpec",
|
|
1001
|
+
"SizeSpec",
|
|
1002
|
+
"LegendSpec",
|
|
1003
|
+
"PlotStyle",
|
|
1004
|
+
# Geometry classes (cache)
|
|
1005
|
+
"RenderedArtist",
|
|
1006
|
+
"RenderedAxes",
|
|
1007
|
+
"HitRegionEntry",
|
|
1008
|
+
"SelectableRegion",
|
|
1009
|
+
"PlotGeometry",
|
|
1010
|
+
# Manifest
|
|
1011
|
+
"RenderManifest",
|
|
1012
|
+
]
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
# EOF
|