matterviz 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EmptyState.svelte.d.ts +9 -0
- package/dist/FilePicker.svelte +360 -0
- package/dist/FilePicker.svelte.d.ts +17 -0
- package/dist/Icon.svelte +44 -0
- package/dist/Icon.svelte.d.ts +13 -0
- package/dist/MillerIndexInput.svelte +66 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/api/mp.d.ts +6 -0
- package/dist/api/mp.js +22 -0
- package/dist/api/optimade.d.ts +45 -0
- package/dist/api/optimade.js +141 -0
- package/dist/app.css +244 -0
- package/dist/brillouin/BrillouinZone.svelte +554 -0
- package/dist/brillouin/BrillouinZone.svelte.d.ts +84 -0
- package/dist/brillouin/BrillouinZoneControls.svelte +144 -0
- package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +17 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte +146 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +146 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
- package/dist/brillouin/BrillouinZoneScene.svelte +522 -0
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +49 -0
- package/dist/brillouin/BrillouinZoneTooltip.svelte +83 -0
- package/dist/brillouin/BrillouinZoneTooltip.svelte.d.ts +8 -0
- package/dist/brillouin/compute.d.ts +17 -0
- package/dist/brillouin/compute.js +422 -0
- package/dist/brillouin/index.d.ts +8 -0
- package/dist/brillouin/index.js +7 -0
- package/dist/brillouin/types.d.ts +43 -0
- package/dist/brillouin/types.js +1 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte +328 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +843 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3191 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
- package/dist/chempot-diagram/async-compute.svelte.js +80 -0
- package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
- package/dist/chempot-diagram/chempot-worker.js +12 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +32 -0
- package/dist/chempot-diagram/compute.d.ts +48 -0
- package/dist/chempot-diagram/compute.js +804 -0
- package/dist/chempot-diagram/index.d.ts +6 -0
- package/dist/chempot-diagram/index.js +6 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +34 -0
- package/dist/chempot-diagram/types.d.ts +81 -0
- package/dist/chempot-diagram/types.js +28 -0
- package/dist/colors/index.d.ts +47 -0
- package/dist/colors/index.js +204 -0
- package/dist/composition/BarChart.svelte +297 -0
- package/dist/composition/BarChart.svelte.d.ts +39 -0
- package/dist/composition/BubbleChart.svelte +218 -0
- package/dist/composition/BubbleChart.svelte.d.ts +28 -0
- package/dist/composition/Composition.svelte +165 -0
- package/dist/composition/Composition.svelte.d.ts +15 -0
- package/dist/composition/Formula.svelte +268 -0
- package/dist/composition/Formula.svelte.d.ts +19 -0
- package/dist/composition/FormulaFilter.svelte +1263 -0
- package/dist/composition/FormulaFilter.svelte.d.ts +51 -0
- package/dist/composition/PieChart.svelte +324 -0
- package/dist/composition/PieChart.svelte.d.ts +37 -0
- package/dist/composition/chem-sys.d.ts +8 -0
- package/dist/composition/chem-sys.js +85 -0
- package/dist/composition/format.d.ts +15 -0
- package/dist/composition/format.js +111 -0
- package/dist/composition/index.d.ts +21 -0
- package/dist/composition/index.js +15 -0
- package/dist/composition/parse.d.ts +56 -0
- package/dist/composition/parse.js +486 -0
- package/dist/constants.d.ts +29 -0
- package/dist/constants.js +99 -0
- package/dist/controls.d.ts +14 -0
- package/dist/controls.js +30 -0
- package/dist/convex-hull/ConvexHull.svelte +157 -0
- package/dist/convex-hull/ConvexHull.svelte.d.ts +13 -0
- package/dist/convex-hull/ConvexHull2D.svelte +827 -0
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +11 -0
- package/dist/convex-hull/ConvexHull3D.svelte +1801 -0
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +8 -0
- package/dist/convex-hull/ConvexHull4D.svelte +1394 -0
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +8 -0
- package/dist/convex-hull/ConvexHullControls.svelte +535 -0
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +48 -0
- package/dist/convex-hull/ConvexHullInfoPane.svelte +125 -0
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +20 -0
- package/dist/convex-hull/ConvexHullStats.svelte +929 -0
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +17 -0
- package/dist/convex-hull/ConvexHullTooltip.svelte +131 -0
- package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +33 -0
- package/dist/convex-hull/GasPressureControls.svelte +247 -0
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +11 -0
- package/dist/convex-hull/StructurePopup.svelte +151 -0
- package/dist/convex-hull/StructurePopup.svelte.d.ts +18 -0
- package/dist/convex-hull/TemperatureSlider.svelte +140 -0
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +8 -0
- package/dist/convex-hull/barycentric-coords.d.ts +18 -0
- package/dist/convex-hull/barycentric-coords.js +182 -0
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +38 -0
- package/dist/convex-hull/gas-thermodynamics.d.ts +16 -0
- package/dist/convex-hull/gas-thermodynamics.js +306 -0
- package/dist/convex-hull/helpers.d.ts +117 -0
- package/dist/convex-hull/helpers.js +718 -0
- package/dist/convex-hull/index.d.ts +119 -0
- package/dist/convex-hull/index.js +58 -0
- package/dist/convex-hull/thermodynamics.d.ts +67 -0
- package/dist/convex-hull/thermodynamics.js +1757 -0
- package/dist/convex-hull/types.d.ts +162 -0
- package/dist/convex-hull/types.js +36 -0
- package/dist/coordination/CoordinationBarPlot.svelte +311 -0
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +30 -0
- package/dist/coordination/calc-coordination.d.ts +15 -0
- package/dist/coordination/calc-coordination.js +63 -0
- package/dist/coordination/index.d.ts +8 -0
- package/dist/coordination/index.js +7 -0
- package/dist/effects.svelte.d.ts +12 -0
- package/dist/effects.svelte.js +37 -0
- package/dist/element/BohrAtom.svelte.d.ts +20 -0
- package/dist/element/ElementHeading.svelte +26 -0
- package/dist/element/ElementHeading.svelte.d.ts +8 -0
- package/dist/element/ElementPhoto.svelte +57 -0
- package/dist/element/ElementPhoto.svelte.d.ts +9 -0
- package/dist/element/ElementStats.svelte +80 -0
- package/dist/element/ElementStats.svelte.d.ts +8 -0
- package/dist/element/ElementTile.svelte +484 -0
- package/dist/element/ElementTile.svelte.d.ts +29 -0
- package/dist/element/Nucleus.svelte.d.ts +17 -0
- package/dist/element/data.d.ts +2 -0
- package/dist/element/data.js +2 -0
- package/dist/element/index.d.ts +8 -0
- package/dist/element/index.js +7 -0
- package/dist/element/types.d.ts +57 -0
- package/dist/element/types.js +1 -0
- package/dist/feedback/ClickFeedback.svelte +58 -0
- package/dist/feedback/ClickFeedback.svelte.d.ts +12 -0
- package/dist/feedback/DragOverlay.svelte +42 -0
- package/dist/feedback/DragOverlay.svelte.d.ts +7 -0
- package/dist/feedback/Spinner.svelte.d.ts +7 -0
- package/dist/feedback/StatusMessage.svelte.d.ts +9 -0
- package/dist/feedback/index.d.ts +4 -0
- package/dist/feedback/index.js +4 -0
- package/dist/fermi-surface/FermiSlice.svelte +197 -0
- package/dist/fermi-surface/FermiSlice.svelte.d.ts +24 -0
- package/dist/fermi-surface/FermiSurface.svelte +606 -0
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +83 -0
- package/dist/fermi-surface/FermiSurfaceControls.svelte +448 -0
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +35 -0
- package/dist/fermi-surface/FermiSurfaceScene.svelte +797 -0
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +50 -0
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +85 -0
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte.d.ts +8 -0
- package/dist/fermi-surface/compute.d.ts +5 -0
- package/dist/fermi-surface/compute.js +538 -0
- package/dist/fermi-surface/constants.d.ts +9 -0
- package/dist/fermi-surface/constants.js +27 -0
- package/dist/fermi-surface/export.d.ts +5 -0
- package/dist/fermi-surface/export.js +51 -0
- package/dist/fermi-surface/index.d.ts +12 -0
- package/dist/fermi-surface/index.js +13 -0
- package/dist/fermi-surface/marching-cubes.d.ts +2 -0
- package/dist/fermi-surface/marching-cubes.js +2 -0
- package/dist/fermi-surface/parse.d.ts +2 -0
- package/dist/fermi-surface/parse.js +494 -0
- package/dist/fermi-surface/symmetry.d.ts +3 -0
- package/dist/fermi-surface/symmetry.js +46 -0
- package/dist/fermi-surface/types.d.ts +111 -0
- package/dist/fermi-surface/types.js +4 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1547 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +569 -0
- package/dist/icons.js +648 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +39 -0
- package/dist/io/decompress.d.ts +11 -0
- package/dist/io/decompress.js +76 -0
- package/dist/io/export.d.ts +16 -0
- package/dist/io/export.js +338 -0
- package/dist/io/fetch.d.ts +5 -0
- package/dist/io/fetch.js +43 -0
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +42 -0
- package/dist/io/index.d.ts +7 -0
- package/dist/io/index.js +6 -0
- package/dist/io/is-binary.d.ts +1 -0
- package/dist/io/is-binary.js +20 -0
- package/dist/io/types.d.ts +8 -0
- package/dist/io/types.js +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +154 -0
- package/dist/isosurface/Isosurface.svelte +285 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +277 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +552 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +141 -0
- package/dist/isosurface/types.d.ts +56 -0
- package/dist/isosurface/types.js +227 -0
- package/dist/keyboard.d.ts +3 -0
- package/dist/keyboard.js +23 -0
- package/dist/labels.d.ts +53 -0
- package/dist/labels.js +278 -0
- package/dist/layout/FullscreenToggle.svelte +50 -0
- package/dist/layout/FullscreenToggle.svelte.d.ts +7 -0
- package/dist/layout/InfoCard.svelte +120 -0
- package/dist/layout/InfoCard.svelte.d.ts +21 -0
- package/dist/layout/InfoTag.svelte +185 -0
- package/dist/layout/InfoTag.svelte.d.ts +19 -0
- package/dist/layout/PropertyFilter.svelte +247 -0
- package/dist/layout/PropertyFilter.svelte.d.ts +24 -0
- package/dist/layout/SettingsSection.svelte +148 -0
- package/dist/layout/SettingsSection.svelte.d.ts +17 -0
- package/dist/layout/SubpageGrid.svelte +82 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/fullscreen.d.ts +9 -0
- package/dist/layout/fullscreen.js +53 -0
- package/dist/layout/index.d.ts +10 -0
- package/dist/layout/index.js +8 -0
- package/dist/layout/json-tree/JsonNode.svelte +548 -0
- package/dist/layout/json-tree/JsonNode.svelte.d.ts +11 -0
- package/dist/layout/json-tree/JsonTree.svelte +1230 -0
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +6 -0
- package/dist/layout/json-tree/JsonValue.svelte.d.ts +9 -0
- package/dist/layout/json-tree/index.d.ts +3 -0
- package/dist/layout/json-tree/index.js +3 -0
- package/dist/layout/json-tree/types.d.ts +74 -0
- package/dist/layout/json-tree/types.js +2 -0
- package/dist/layout/json-tree/utils.d.ts +29 -0
- package/dist/layout/json-tree/utils.js +642 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +535 -0
- package/dist/math.d.ts +105 -0
- package/dist/math.js +920 -0
- package/dist/overlays/ContextMenu.svelte +162 -0
- package/dist/overlays/ContextMenu.svelte.d.ts +25 -0
- package/dist/overlays/CopyButton.svelte +45 -0
- package/dist/overlays/CopyButton.svelte.d.ts +8 -0
- package/dist/overlays/DragControlTab.svelte +98 -0
- package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
- package/dist/overlays/DraggablePane.svelte +487 -0
- package/dist/overlays/DraggablePane.svelte.d.ts +36 -0
- package/dist/overlays/InfoPaneCards.svelte +149 -0
- package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
- package/dist/overlays/index.d.ts +3 -0
- package/dist/overlays/index.js +3 -0
- package/dist/periodic-table/PeriodicTable.svelte +480 -0
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +55 -0
- package/dist/periodic-table/PeriodicTableControls.svelte +557 -0
- package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +24 -0
- package/dist/periodic-table/PropertySelect.svelte +38 -0
- package/dist/periodic-table/PropertySelect.svelte.d.ts +13 -0
- package/dist/periodic-table/TableInset.svelte.d.ts +9 -0
- package/dist/periodic-table/index.d.ts +10 -0
- package/dist/periodic-table/index.js +4 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +1092 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +44 -0
- package/dist/phase-diagram/PhaseDiagramControls.svelte +444 -0
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +30 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +127 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +184 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +19 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +391 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +16 -0
- package/dist/phase-diagram/TdbInfoPanel.svelte +203 -0
- package/dist/phase-diagram/TdbInfoPanel.svelte.d.ts +12 -0
- package/dist/phase-diagram/build-diagram.d.ts +11 -0
- package/dist/phase-diagram/build-diagram.js +160 -0
- package/dist/phase-diagram/colors.d.ts +35 -0
- package/dist/phase-diagram/colors.js +51 -0
- package/dist/phase-diagram/diagram-input.d.ts +29 -0
- package/dist/phase-diagram/diagram-input.js +3 -0
- package/dist/phase-diagram/index.d.ts +13 -0
- package/dist/phase-diagram/index.js +11 -0
- package/dist/phase-diagram/parse.d.ts +55 -0
- package/dist/phase-diagram/parse.js +273 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +867 -0
- package/dist/phase-diagram/types.d.ts +93 -0
- package/dist/phase-diagram/types.js +1 -0
- package/dist/phase-diagram/utils.d.ts +118 -0
- package/dist/phase-diagram/utils.js +600 -0
- package/dist/plot/bar/BarPlot.svelte +1755 -0
- package/dist/plot/bar/BarPlot.svelte.d.ts +84 -0
- package/dist/plot/bar/BarPlotControls.svelte +67 -0
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +18 -0
- package/dist/plot/bar/SpacegroupBarPlot.svelte +293 -0
- package/dist/plot/bar/SpacegroupBarPlot.svelte.d.ts +9 -0
- package/dist/plot/bar/data.d.ts +40 -0
- package/dist/plot/bar/data.js +154 -0
- package/dist/plot/bar/geometry.d.ts +39 -0
- package/dist/plot/bar/geometry.js +60 -0
- package/dist/plot/bar/index.d.ts +3 -0
- package/dist/plot/bar/index.js +3 -0
- package/dist/plot/box/BoxPlot.svelte +1462 -0
- package/dist/plot/box/BoxPlot.svelte.d.ts +94 -0
- package/dist/plot/box/BoxPlotControls.svelte +109 -0
- package/dist/plot/box/BoxPlotControls.svelte.d.ts +19 -0
- package/dist/plot/box/Violin.svelte +14 -0
- package/dist/plot/box/Violin.svelte.d.ts +70 -0
- package/dist/plot/box/box-plot.d.ts +55 -0
- package/dist/plot/box/box-plot.js +126 -0
- package/dist/plot/box/index.d.ts +5 -0
- package/dist/plot/box/index.js +5 -0
- package/dist/plot/box/kde.d.ts +16 -0
- package/dist/plot/box/kde.js +160 -0
- package/dist/plot/box/quantile.d.ts +3 -0
- package/dist/plot/box/quantile.js +53 -0
- package/dist/plot/core/auto-place.d.ts +43 -0
- package/dist/plot/core/auto-place.js +122 -0
- package/dist/plot/core/axis-utils.d.ts +46 -0
- package/dist/plot/core/axis-utils.js +110 -0
- package/dist/plot/core/components/AxisLabel.svelte +51 -0
- package/dist/plot/core/components/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/core/components/ColorBar.svelte +724 -0
- package/dist/plot/core/components/ColorBar.svelte.d.ts +31 -0
- package/dist/plot/core/components/ColorScaleSelect.svelte +55 -0
- package/dist/plot/core/components/ColorScaleSelect.svelte.d.ts +15 -0
- package/dist/plot/core/components/ControlPane.svelte +46 -0
- package/dist/plot/core/components/ControlPane.svelte.d.ts +13 -0
- package/dist/plot/core/components/FillArea.svelte +234 -0
- package/dist/plot/core/components/FillArea.svelte.d.ts +21 -0
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +96 -0
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +14 -0
- package/dist/plot/core/components/Line.svelte +101 -0
- package/dist/plot/core/components/Line.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotAxis.svelte +171 -0
- package/dist/plot/core/components/PlotAxis.svelte.d.ts +25 -0
- package/dist/plot/core/components/PlotControls.svelte +525 -0
- package/dist/plot/core/components/PlotControls.svelte.d.ts +4 -0
- package/dist/plot/core/components/PlotLegend.svelte +580 -0
- package/dist/plot/core/components/PlotLegend.svelte.d.ts +30 -0
- package/dist/plot/core/components/PlotTooltip.svelte +83 -0
- package/dist/plot/core/components/PlotTooltip.svelte.d.ts +25 -0
- package/dist/plot/core/components/PortalSelect.svelte +257 -0
- package/dist/plot/core/components/PortalSelect.svelte.d.ts +16 -0
- package/dist/plot/core/components/ReferenceLine.svelte +204 -0
- package/dist/plot/core/components/ReferenceLine.svelte.d.ts +20 -0
- package/dist/plot/core/components/ReferenceLine3D.svelte +156 -0
- package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +14 -0
- package/dist/plot/core/components/ReferencePlane.svelte +175 -0
- package/dist/plot/core/components/ReferencePlane.svelte.d.ts +14 -0
- package/dist/plot/core/components/ZeroLines.svelte +97 -0
- package/dist/plot/core/components/ZeroLines.svelte.d.ts +33 -0
- package/dist/plot/core/components/ZoomRect.svelte +23 -0
- package/dist/plot/core/components/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/core/components/index.d.ts +17 -0
- package/dist/plot/core/components/index.js +17 -0
- package/dist/plot/core/data-cleaning.d.ts +107 -0
- package/dist/plot/core/data-cleaning.js +853 -0
- package/dist/plot/core/data-transform.d.ts +16 -0
- package/dist/plot/core/data-transform.js +45 -0
- package/dist/plot/core/fill-utils.d.ts +33 -0
- package/dist/plot/core/fill-utils.js +388 -0
- package/dist/plot/core/hover-lock.svelte.d.ts +14 -0
- package/dist/plot/core/hover-lock.svelte.js +45 -0
- package/dist/plot/core/index.d.ts +10 -0
- package/dist/plot/core/index.js +11 -0
- package/dist/plot/core/interactions.d.ts +35 -0
- package/dist/plot/core/interactions.js +195 -0
- package/dist/plot/core/layout.d.ts +79 -0
- package/dist/plot/core/layout.js +281 -0
- package/dist/plot/core/reference-line.d.ts +60 -0
- package/dist/plot/core/reference-line.js +301 -0
- package/dist/plot/core/scales.d.ts +48 -0
- package/dist/plot/core/scales.js +480 -0
- package/dist/plot/core/svg.d.ts +2 -0
- package/dist/plot/core/svg.js +41 -0
- package/dist/plot/core/types.d.ts +771 -0
- package/dist/plot/core/types.js +99 -0
- package/dist/plot/core/utils/label-placement.d.ts +68 -0
- package/dist/plot/core/utils/label-placement.js +326 -0
- package/dist/plot/core/utils/series-visibility.d.ts +26 -0
- package/dist/plot/core/utils/series-visibility.js +112 -0
- package/dist/plot/core/utils.d.ts +11 -0
- package/dist/plot/core/utils.js +27 -0
- package/dist/plot/histogram/Histogram.svelte +1418 -0
- package/dist/plot/histogram/Histogram.svelte.d.ts +50 -0
- package/dist/plot/histogram/HistogramControls.svelte +212 -0
- package/dist/plot/histogram/HistogramControls.svelte.d.ts +22 -0
- package/dist/plot/histogram/index.d.ts +2 -0
- package/dist/plot/histogram/index.js +2 -0
- package/dist/plot/index.d.ts +8 -0
- package/dist/plot/index.js +10 -0
- package/dist/plot/sankey/Sankey.svelte +700 -0
- package/dist/plot/sankey/Sankey.svelte.d.ts +74 -0
- package/dist/plot/sankey/SankeyControls.svelte +98 -0
- package/dist/plot/sankey/SankeyControls.svelte.d.ts +19 -0
- package/dist/plot/sankey/index.d.ts +4 -0
- package/dist/plot/sankey/index.js +3 -0
- package/dist/plot/sankey/sankey-types.d.ts +42 -0
- package/dist/plot/sankey/sankey-types.js +4 -0
- package/dist/plot/sankey/sankey.d.ts +52 -0
- package/dist/plot/sankey/sankey.js +187 -0
- package/dist/plot/scatter/BinnedScatterPlot.svelte +1116 -0
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +66 -0
- package/dist/plot/scatter/ElementScatter.svelte +63 -0
- package/dist/plot/scatter/ElementScatter.svelte.d.ts +14 -0
- package/dist/plot/scatter/ScatterPlot.svelte +2357 -0
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +96 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +307 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +17 -0
- package/dist/plot/scatter/ScatterPoint.svelte +182 -0
- package/dist/plot/scatter/ScatterPoint.svelte.d.ts +22 -0
- package/dist/plot/scatter/adaptive-density.d.ts +79 -0
- package/dist/plot/scatter/adaptive-density.js +217 -0
- package/dist/plot/scatter/binned-scatter-types.d.ts +59 -0
- package/dist/plot/scatter/binned-scatter-types.js +1 -0
- package/dist/plot/scatter/index.d.ts +7 -0
- package/dist/plot/scatter/index.js +5 -0
- package/dist/plot/scatter/scatter-data.d.ts +19 -0
- package/dist/plot/scatter/scatter-data.js +212 -0
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +531 -0
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +95 -0
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +438 -0
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +20 -0
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +912 -0
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +74 -0
- package/dist/plot/scatter-3d/Surface3D.svelte +197 -0
- package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +13 -0
- package/dist/plot/scatter-3d/index.d.ts +4 -0
- package/dist/plot/scatter-3d/index.js +4 -0
- package/dist/plot/sunburst/Sunburst.svelte +1045 -0
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +96 -0
- package/dist/plot/sunburst/SunburstControls.svelte +200 -0
- package/dist/plot/sunburst/SunburstControls.svelte.d.ts +26 -0
- package/dist/plot/sunburst/index.d.ts +4 -0
- package/dist/plot/sunburst/index.js +4 -0
- package/dist/plot/sunburst/render.d.ts +34 -0
- package/dist/plot/sunburst/render.js +122 -0
- package/dist/plot/sunburst/sunburst.d.ts +62 -0
- package/dist/plot/sunburst/sunburst.js +266 -0
- package/dist/rdf/RdfPlot.svelte +248 -0
- package/dist/rdf/RdfPlot.svelte.d.ts +27 -0
- package/dist/rdf/calc-rdf.d.ts +4 -0
- package/dist/rdf/calc-rdf.js +98 -0
- package/dist/rdf/index.d.ts +23 -0
- package/dist/rdf/index.js +2 -0
- package/dist/sanitize.d.ts +6 -0
- package/dist/sanitize.js +116 -0
- package/dist/settings.d.ts +319 -0
- package/dist/settings.js +1394 -0
- package/dist/spectral/Bands.svelte +1050 -0
- package/dist/spectral/Bands.svelte.d.ts +39 -0
- package/dist/spectral/BandsAndDos.svelte +134 -0
- package/dist/spectral/BandsAndDos.svelte.d.ts +18 -0
- package/dist/spectral/BrillouinBandsDos.svelte +264 -0
- package/dist/spectral/BrillouinBandsDos.svelte.d.ts +20 -0
- package/dist/spectral/Dos.svelte +688 -0
- package/dist/spectral/Dos.svelte.d.ts +29 -0
- package/dist/spectral/helpers.d.ts +121 -0
- package/dist/spectral/helpers.js +1098 -0
- package/dist/spectral/index.d.ts +6 -0
- package/dist/spectral/index.js +6 -0
- package/dist/spectral/types.d.ts +84 -0
- package/dist/spectral/types.js +2 -0
- package/dist/state.svelte.d.ts +25 -0
- package/dist/state.svelte.js +45 -0
- package/dist/structure/Arrow.svelte +72 -0
- package/dist/structure/Arrow.svelte.d.ts +15 -0
- package/dist/structure/AtomLegend.svelte +814 -0
- package/dist/structure/AtomLegend.svelte.d.ts +35 -0
- package/dist/structure/Bond.svelte +140 -0
- package/dist/structure/Bond.svelte.d.ts +9 -0
- package/dist/structure/CanvasTooltip.svelte +33 -0
- package/dist/structure/CanvasTooltip.svelte.d.ts +12 -0
- package/dist/structure/CellSelect.svelte +348 -0
- package/dist/structure/CellSelect.svelte.d.ts +13 -0
- package/dist/structure/Cylinder.svelte +49 -0
- package/dist/structure/Cylinder.svelte.d.ts +13 -0
- package/dist/structure/Lattice.svelte +196 -0
- package/dist/structure/Lattice.svelte.d.ts +17 -0
- package/dist/structure/Structure.svelte +2254 -0
- package/dist/structure/Structure.svelte.d.ts +89 -0
- package/dist/structure/StructureControls.svelte +1273 -0
- package/dist/structure/StructureControls.svelte.d.ts +31 -0
- package/dist/structure/StructureExportPane.svelte +252 -0
- package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
- package/dist/structure/StructureInfoPane.svelte +736 -0
- package/dist/structure/StructureInfoPane.svelte.d.ts +19 -0
- package/dist/structure/StructureScene.svelte +2256 -0
- package/dist/structure/StructureScene.svelte.d.ts +111 -0
- package/dist/structure/atom-properties.d.ts +37 -0
- package/dist/structure/atom-properties.js +200 -0
- package/dist/structure/bond-order-perception.d.ts +13 -0
- package/dist/structure/bond-order-perception.js +384 -0
- package/dist/structure/bonding.d.ts +69 -0
- package/dist/structure/bonding.js +724 -0
- package/dist/structure/export.d.ts +20 -0
- package/dist/structure/export.js +731 -0
- package/dist/structure/index.d.ts +124 -0
- package/dist/structure/index.js +167 -0
- package/dist/structure/label-placement.d.ts +14 -0
- package/dist/structure/label-placement.js +72 -0
- package/dist/structure/measure.d.ts +7 -0
- package/dist/structure/measure.js +30 -0
- package/dist/structure/parse.d.ts +66 -0
- package/dist/structure/parse.js +1410 -0
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +99 -0
- package/dist/structure/pbc.d.ts +9 -0
- package/dist/structure/pbc.js +127 -0
- package/dist/structure/supercell.d.ts +8 -0
- package/dist/structure/supercell.js +170 -0
- package/dist/structure/validation.d.ts +2 -0
- package/dist/structure/validation.js +10 -0
- package/dist/symmetry/SymmetryStats.svelte +226 -0
- package/dist/symmetry/SymmetryStats.svelte.d.ts +21 -0
- package/dist/symmetry/WyckoffTable.svelte +120 -0
- package/dist/symmetry/WyckoffTable.svelte.d.ts +11 -0
- package/dist/symmetry/cell-transform.d.ts +12 -0
- package/dist/symmetry/cell-transform.js +91 -0
- package/dist/symmetry/index.d.ts +43 -0
- package/dist/symmetry/index.js +226 -0
- package/dist/symmetry/spacegroups.d.ts +16 -0
- package/dist/symmetry/spacegroups.js +429 -0
- package/dist/table/HeatmapTable.svelte +1885 -0
- package/dist/table/HeatmapTable.svelte.d.ts +49 -0
- package/dist/table/ToggleMenu.svelte +385 -0
- package/dist/table/ToggleMenu.svelte.d.ts +11 -0
- package/dist/table/index.d.ts +72 -0
- package/dist/table/index.js +38 -0
- package/dist/theme/ThemeControl.svelte +53 -0
- package/dist/theme/ThemeControl.svelte.d.ts +9 -0
- package/dist/theme/index.d.ts +29 -0
- package/dist/theme/index.js +79 -0
- package/dist/time.d.ts +4 -0
- package/dist/time.js +70 -0
- package/dist/tooltip/KCoords.svelte +45 -0
- package/dist/tooltip/KCoords.svelte.d.ts +8 -0
- package/dist/tooltip/TooltipContent.svelte +58 -0
- package/dist/tooltip/TooltipContent.svelte.d.ts +31 -0
- package/dist/tooltip/index.d.ts +3 -0
- package/dist/tooltip/index.js +2 -0
- package/dist/tooltip/types.d.ts +8 -0
- package/dist/tooltip/types.js +1 -0
- package/dist/trajectory/Trajectory.svelte +1571 -0
- package/dist/trajectory/Trajectory.svelte.d.ts +78 -0
- package/dist/trajectory/TrajectoryError.svelte +128 -0
- package/dist/trajectory/TrajectoryError.svelte.d.ts +13 -0
- package/dist/trajectory/TrajectoryExportPane.svelte +358 -0
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +17 -0
- package/dist/trajectory/TrajectoryInfoPane.svelte +314 -0
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +17 -0
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.d.ts +5 -0
- package/dist/trajectory/extract.js +162 -0
- package/dist/trajectory/format-detect.d.ts +10 -0
- package/dist/trajectory/format-detect.js +90 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +299 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +164 -0
- package/dist/trajectory/index.d.ts +63 -0
- package/dist/trajectory/index.js +126 -0
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +73 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +127 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +306 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +179 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +87 -0
- package/dist/trajectory/parse/xyz.d.ts +26 -0
- package/dist/trajectory/parse/xyz.js +123 -0
- package/dist/trajectory/plotting.d.ts +28 -0
- package/dist/trajectory/plotting.js +423 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +47 -0
- package/dist/xrd/XrdPlot.svelte +616 -0
- package/dist/xrd/XrdPlot.svelte.d.ts +28 -0
- package/dist/xrd/broadening.d.ts +20 -0
- package/dist/xrd/broadening.js +97 -0
- package/dist/xrd/calc-xrd.d.ts +37 -0
- package/dist/xrd/calc-xrd.js +339 -0
- package/dist/xrd/index.d.ts +37 -0
- package/dist/xrd/index.js +4 -0
- package/dist/xrd/parse.d.ts +13 -0
- package/dist/xrd/parse.js +749 -0
- package/license +1 -1
- package/package.json +237 -1458
- package/readme.md +98 -171
- package/.vscode/launch.json +0 -13
- package/.vscodeignore +0 -7
- package/dist/assets/STLExporter-BpTH3YHE.js +0 -8
- package/dist/assets/browser-DdDecX_W.js +0 -1
- package/dist/assets/export-qgn-H9y6.js +0 -2
- package/dist/assets/main-DiKYzti2.css +0 -1
- package/dist/assets/moyo_wasm_bg-0ocwg7xY.wasm +0 -0
- package/dist/extension.js +0 -31293
- package/dist/src/lib/FilePicker.svelte +0 -360
- package/dist/src/lib/Icon.svelte +0 -41
- package/dist/src/lib/MillerIndexInput.svelte +0 -66
- package/dist/src/lib/api/mp.ts +0 -26
- package/dist/src/lib/api/optimade.ts +0 -204
- package/dist/src/lib/app.css +0 -247
- package/dist/src/lib/brillouin/BrillouinZone.svelte +0 -549
- package/dist/src/lib/brillouin/BrillouinZoneControls.svelte +0 -144
- package/dist/src/lib/brillouin/BrillouinZoneExportPane.svelte +0 -146
- package/dist/src/lib/brillouin/BrillouinZoneInfoPane.svelte +0 -146
- package/dist/src/lib/brillouin/BrillouinZoneScene.svelte +0 -476
- package/dist/src/lib/brillouin/BrillouinZoneTooltip.svelte +0 -92
- package/dist/src/lib/brillouin/compute.ts +0 -529
- package/dist/src/lib/brillouin/index.ts +0 -8
- package/dist/src/lib/brillouin/types.ts +0 -51
- package/dist/src/lib/chempot-diagram/ChemPotDiagram.svelte +0 -327
- package/dist/src/lib/chempot-diagram/ChemPotDiagram2D.svelte +0 -846
- package/dist/src/lib/chempot-diagram/ChemPotDiagram3D.svelte +0 -3193
- package/dist/src/lib/chempot-diagram/async-compute.svelte.ts +0 -94
- package/dist/src/lib/chempot-diagram/chempot-worker.ts +0 -11
- package/dist/src/lib/chempot-diagram/color.ts +0 -42
- package/dist/src/lib/chempot-diagram/compute.ts +0 -1014
- package/dist/src/lib/chempot-diagram/index.ts +0 -6
- package/dist/src/lib/chempot-diagram/pointer.ts +0 -56
- package/dist/src/lib/chempot-diagram/temperature.ts +0 -77
- package/dist/src/lib/chempot-diagram/types.ts +0 -130
- package/dist/src/lib/colors/index.ts +0 -249
- package/dist/src/lib/composition/BarChart.svelte +0 -297
- package/dist/src/lib/composition/BubbleChart.svelte +0 -218
- package/dist/src/lib/composition/Composition.svelte +0 -165
- package/dist/src/lib/composition/Formula.svelte +0 -268
- package/dist/src/lib/composition/FormulaFilter.svelte +0 -1257
- package/dist/src/lib/composition/PieChart.svelte +0 -323
- package/dist/src/lib/composition/format.ts +0 -155
- package/dist/src/lib/composition/index.ts +0 -37
- package/dist/src/lib/composition/parse.ts +0 -605
- package/dist/src/lib/constants.ts +0 -134
- package/dist/src/lib/controls.ts +0 -42
- package/dist/src/lib/convex-hull/ConvexHull.svelte +0 -157
- package/dist/src/lib/convex-hull/ConvexHull2D.svelte +0 -825
- package/dist/src/lib/convex-hull/ConvexHull3D.svelte +0 -1801
- package/dist/src/lib/convex-hull/ConvexHull4D.svelte +0 -1398
- package/dist/src/lib/convex-hull/ConvexHullControls.svelte +0 -535
- package/dist/src/lib/convex-hull/ConvexHullInfoPane.svelte +0 -125
- package/dist/src/lib/convex-hull/ConvexHullStats.svelte +0 -929
- package/dist/src/lib/convex-hull/ConvexHullTooltip.svelte +0 -131
- package/dist/src/lib/convex-hull/GasPressureControls.svelte +0 -247
- package/dist/src/lib/convex-hull/StructurePopup.svelte +0 -151
- package/dist/src/lib/convex-hull/TemperatureSlider.svelte +0 -140
- package/dist/src/lib/convex-hull/barycentric-coords.ts +0 -246
- package/dist/src/lib/convex-hull/demo-temperature.ts +0 -63
- package/dist/src/lib/convex-hull/gas-thermodynamics.ts +0 -405
- package/dist/src/lib/convex-hull/helpers.ts +0 -932
- package/dist/src/lib/convex-hull/index.ts +0 -202
- package/dist/src/lib/convex-hull/thermodynamics.ts +0 -2192
- package/dist/src/lib/convex-hull/types.ts +0 -267
- package/dist/src/lib/coordination/CoordinationBarPlot.svelte +0 -311
- package/dist/src/lib/coordination/calc-coordination.ts +0 -93
- package/dist/src/lib/coordination/index.ts +0 -9
- package/dist/src/lib/effects.svelte.ts +0 -48
- package/dist/src/lib/element/ElementHeading.svelte +0 -26
- package/dist/src/lib/element/ElementPhoto.svelte +0 -57
- package/dist/src/lib/element/ElementStats.svelte +0 -80
- package/dist/src/lib/element/ElementTile.svelte +0 -484
- package/dist/src/lib/element/data.ts +0 -14
- package/dist/src/lib/element/index.ts +0 -8
- package/dist/src/lib/element/types.ts +0 -62
- package/dist/src/lib/feedback/ClickFeedback.svelte +0 -58
- package/dist/src/lib/feedback/DragOverlay.svelte +0 -42
- package/dist/src/lib/feedback/index.ts +0 -4
- package/dist/src/lib/fermi-surface/FermiSlice.svelte +0 -189
- package/dist/src/lib/fermi-surface/FermiSurface.svelte +0 -600
- package/dist/src/lib/fermi-surface/FermiSurfaceControls.svelte +0 -448
- package/dist/src/lib/fermi-surface/FermiSurfaceScene.svelte +0 -794
- package/dist/src/lib/fermi-surface/FermiSurfaceTooltip.svelte +0 -111
- package/dist/src/lib/fermi-surface/compute.ts +0 -728
- package/dist/src/lib/fermi-surface/constants.ts +0 -32
- package/dist/src/lib/fermi-surface/export.ts +0 -64
- package/dist/src/lib/fermi-surface/index.ts +0 -14
- package/dist/src/lib/fermi-surface/marching-cubes.ts +0 -3
- package/dist/src/lib/fermi-surface/parse.ts +0 -574
- package/dist/src/lib/fermi-surface/symmetry.ts +0 -56
- package/dist/src/lib/fermi-surface/types.ts +0 -159
- package/dist/src/lib/heatmap-matrix/HeatmapMatrix.svelte +0 -1545
- package/dist/src/lib/heatmap-matrix/HeatmapMatrixControls.svelte +0 -225
- package/dist/src/lib/heatmap-matrix/index.ts +0 -167
- package/dist/src/lib/heatmap-matrix/shared.ts +0 -7
- package/dist/src/lib/icons.ts +0 -650
- package/dist/src/lib/index.ts +0 -61
- package/dist/src/lib/io/decompress.ts +0 -92
- package/dist/src/lib/io/export.ts +0 -385
- package/dist/src/lib/io/fetch.ts +0 -46
- package/dist/src/lib/io/file-drop.ts +0 -51
- package/dist/src/lib/io/index.ts +0 -7
- package/dist/src/lib/io/is-binary.ts +0 -24
- package/dist/src/lib/io/types.ts +0 -8
- package/dist/src/lib/io/url-drop.ts +0 -141
- package/dist/src/lib/isosurface/Isosurface.svelte +0 -285
- package/dist/src/lib/isosurface/IsosurfaceControls.svelte +0 -277
- package/dist/src/lib/isosurface/index.ts +0 -7
- package/dist/src/lib/isosurface/parse.ts +0 -656
- package/dist/src/lib/isosurface/slice.ts +0 -175
- package/dist/src/lib/isosurface/types.ts +0 -309
- package/dist/src/lib/labels.ts +0 -320
- package/dist/src/lib/layout/FullscreenToggle.svelte +0 -50
- package/dist/src/lib/layout/InfoCard.svelte +0 -120
- package/dist/src/lib/layout/InfoTag.svelte +0 -185
- package/dist/src/lib/layout/PropertyFilter.svelte +0 -246
- package/dist/src/lib/layout/SettingsSection.svelte +0 -148
- package/dist/src/lib/layout/SubpageGrid.svelte +0 -82
- package/dist/src/lib/layout/fullscreen.ts +0 -65
- package/dist/src/lib/layout/index.ts +0 -11
- package/dist/src/lib/layout/json-tree/JsonNode.svelte +0 -548
- package/dist/src/lib/layout/json-tree/JsonTree.svelte +0 -1230
- package/dist/src/lib/layout/json-tree/index.ts +0 -3
- package/dist/src/lib/layout/json-tree/types.ts +0 -126
- package/dist/src/lib/layout/json-tree/utils.ts +0 -682
- package/dist/src/lib/marching-cubes.ts +0 -614
- package/dist/src/lib/math.ts +0 -1081
- package/dist/src/lib/overlays/ContextMenu.svelte +0 -162
- package/dist/src/lib/overlays/CopyButton.svelte +0 -45
- package/dist/src/lib/overlays/DragControlTab.svelte +0 -98
- package/dist/src/lib/overlays/DraggablePane.svelte +0 -487
- package/dist/src/lib/overlays/InfoPaneCards.svelte +0 -149
- package/dist/src/lib/overlays/index.ts +0 -3
- package/dist/src/lib/periodic-table/PeriodicTable.svelte +0 -469
- package/dist/src/lib/periodic-table/PeriodicTableControls.svelte +0 -557
- package/dist/src/lib/periodic-table/PropertySelect.svelte +0 -37
- package/dist/src/lib/periodic-table/index.ts +0 -12
- package/dist/src/lib/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +0 -1086
- package/dist/src/lib/phase-diagram/PhaseDiagramControls.svelte +0 -444
- package/dist/src/lib/phase-diagram/PhaseDiagramEditorPane.svelte +0 -126
- package/dist/src/lib/phase-diagram/PhaseDiagramExportPane.svelte +0 -184
- package/dist/src/lib/phase-diagram/PhaseDiagramTooltip.svelte +0 -391
- package/dist/src/lib/phase-diagram/TdbInfoPanel.svelte +0 -203
- package/dist/src/lib/phase-diagram/build-diagram.ts +0 -186
- package/dist/src/lib/phase-diagram/colors.ts +0 -58
- package/dist/src/lib/phase-diagram/diagram-input.ts +0 -40
- package/dist/src/lib/phase-diagram/index.ts +0 -13
- package/dist/src/lib/phase-diagram/parse.ts +0 -348
- package/dist/src/lib/phase-diagram/svg-to-diagram.ts +0 -1023
- package/dist/src/lib/phase-diagram/types.ts +0 -144
- package/dist/src/lib/phase-diagram/utils.ts +0 -775
- package/dist/src/lib/plot/AxisLabel.svelte +0 -51
- package/dist/src/lib/plot/BarPlot.svelte +0 -2113
- package/dist/src/lib/plot/BarPlotControls.svelte +0 -66
- package/dist/src/lib/plot/BinnedScatterPlot.svelte +0 -1114
- package/dist/src/lib/plot/ColorBar.svelte +0 -721
- package/dist/src/lib/plot/ColorScaleSelect.svelte +0 -54
- package/dist/src/lib/plot/ElementScatter.svelte +0 -63
- package/dist/src/lib/plot/FillArea.svelte +0 -223
- package/dist/src/lib/plot/Histogram.svelte +0 -1558
- package/dist/src/lib/plot/HistogramControls.svelte +0 -212
- package/dist/src/lib/plot/InteractiveAxisLabel.svelte +0 -96
- package/dist/src/lib/plot/Line.svelte +0 -84
- package/dist/src/lib/plot/PlotAxis.svelte +0 -169
- package/dist/src/lib/plot/PlotControls.svelte +0 -537
- package/dist/src/lib/plot/PlotLegend.svelte +0 -569
- package/dist/src/lib/plot/PlotTooltip.svelte +0 -67
- package/dist/src/lib/plot/PortalSelect.svelte +0 -253
- package/dist/src/lib/plot/ReferenceLine.svelte +0 -204
- package/dist/src/lib/plot/ReferenceLine3D.svelte +0 -156
- package/dist/src/lib/plot/ReferencePlane.svelte +0 -175
- package/dist/src/lib/plot/ScatterPlot.svelte +0 -2778
- package/dist/src/lib/plot/ScatterPlot3D.svelte +0 -529
- package/dist/src/lib/plot/ScatterPlot3DControls.svelte +0 -437
- package/dist/src/lib/plot/ScatterPlot3DScene.svelte +0 -912
- package/dist/src/lib/plot/ScatterPlotControls.svelte +0 -306
- package/dist/src/lib/plot/ScatterPoint.svelte +0 -182
- package/dist/src/lib/plot/SpacegroupBarPlot.svelte +0 -293
- package/dist/src/lib/plot/Surface3D.svelte +0 -197
- package/dist/src/lib/plot/ZeroLines.svelte +0 -97
- package/dist/src/lib/plot/ZoomRect.svelte +0 -23
- package/dist/src/lib/plot/adaptive-density.ts +0 -316
- package/dist/src/lib/plot/auto-place.ts +0 -184
- package/dist/src/lib/plot/axis-utils.ts +0 -122
- package/dist/src/lib/plot/binned-scatter-types.ts +0 -83
- package/dist/src/lib/plot/data-cleaning.ts +0 -1069
- package/dist/src/lib/plot/data-transform.ts +0 -69
- package/dist/src/lib/plot/defaults.ts +0 -9
- package/dist/src/lib/plot/fill-utils.ts +0 -494
- package/dist/src/lib/plot/hover-lock.svelte.ts +0 -60
- package/dist/src/lib/plot/index.ts +0 -53
- package/dist/src/lib/plot/interactions.ts +0 -119
- package/dist/src/lib/plot/layout.ts +0 -425
- package/dist/src/lib/plot/reference-line.ts +0 -426
- package/dist/src/lib/plot/scales.ts +0 -654
- package/dist/src/lib/plot/svg.ts +0 -23
- package/dist/src/lib/plot/types.ts +0 -1144
- package/dist/src/lib/plot/utils/label-placement.ts +0 -541
- package/dist/src/lib/plot/utils/series-visibility.ts +0 -140
- package/dist/src/lib/plot/utils.ts +0 -11
- package/dist/src/lib/rdf/RdfPlot.svelte +0 -247
- package/dist/src/lib/rdf/calc-rdf.ts +0 -167
- package/dist/src/lib/rdf/index.ts +0 -27
- package/dist/src/lib/sanitize.ts +0 -126
- package/dist/src/lib/settings.ts +0 -1479
- package/dist/src/lib/spectral/Bands.svelte +0 -1040
- package/dist/src/lib/spectral/BandsAndDos.svelte +0 -134
- package/dist/src/lib/spectral/BrillouinBandsDos.svelte +0 -252
- package/dist/src/lib/spectral/Dos.svelte +0 -697
- package/dist/src/lib/spectral/helpers.ts +0 -1381
- package/dist/src/lib/spectral/index.ts +0 -8
- package/dist/src/lib/spectral/types.ts +0 -112
- package/dist/src/lib/state.svelte.ts +0 -64
- package/dist/src/lib/structure/Arrow.svelte +0 -72
- package/dist/src/lib/structure/AtomLegend.svelte +0 -815
- package/dist/src/lib/structure/Bond.svelte +0 -140
- package/dist/src/lib/structure/CanvasTooltip.svelte +0 -33
- package/dist/src/lib/structure/CellSelect.svelte +0 -349
- package/dist/src/lib/structure/Cylinder.svelte +0 -45
- package/dist/src/lib/structure/Lattice.svelte +0 -196
- package/dist/src/lib/structure/Structure.svelte +0 -2248
- package/dist/src/lib/structure/StructureControls.svelte +0 -1273
- package/dist/src/lib/structure/StructureExportPane.svelte +0 -252
- package/dist/src/lib/structure/StructureInfoPane.svelte +0 -737
- package/dist/src/lib/structure/StructureScene.svelte +0 -2255
- package/dist/src/lib/structure/atom-properties.ts +0 -316
- package/dist/src/lib/structure/bond-order-perception.ts +0 -447
- package/dist/src/lib/structure/bonding.ts +0 -944
- package/dist/src/lib/structure/export.ts +0 -861
- package/dist/src/lib/structure/index.ts +0 -291
- package/dist/src/lib/structure/label-placement.ts +0 -130
- package/dist/src/lib/structure/measure.ts +0 -45
- package/dist/src/lib/structure/parse.ts +0 -1705
- package/dist/src/lib/structure/partial-occupancy.ts +0 -183
- package/dist/src/lib/structure/pbc.ts +0 -164
- package/dist/src/lib/structure/supercell.ts +0 -226
- package/dist/src/lib/structure/validation.ts +0 -11
- package/dist/src/lib/symmetry/SymmetryStats.svelte +0 -226
- package/dist/src/lib/symmetry/WyckoffTable.svelte +0 -120
- package/dist/src/lib/symmetry/cell-transform.ts +0 -118
- package/dist/src/lib/symmetry/index.ts +0 -348
- package/dist/src/lib/symmetry/spacegroups.ts +0 -404
- package/dist/src/lib/table/HeatmapTable.svelte +0 -1833
- package/dist/src/lib/table/ToggleMenu.svelte +0 -385
- package/dist/src/lib/table/index.ts +0 -139
- package/dist/src/lib/theme/ThemeControl.svelte +0 -53
- package/dist/src/lib/theme/index.ts +0 -107
- package/dist/src/lib/time.ts +0 -71
- package/dist/src/lib/tooltip/TooltipContent.svelte +0 -58
- package/dist/src/lib/tooltip/index.ts +0 -2
- package/dist/src/lib/tooltip/types.ts +0 -13
- package/dist/src/lib/trajectory/Trajectory.svelte +0 -1545
- package/dist/src/lib/trajectory/TrajectoryError.svelte +0 -128
- package/dist/src/lib/trajectory/TrajectoryExportPane.svelte +0 -357
- package/dist/src/lib/trajectory/TrajectoryInfoPane.svelte +0 -313
- package/dist/src/lib/trajectory/constants.ts +0 -7
- package/dist/src/lib/trajectory/extract.ts +0 -196
- package/dist/src/lib/trajectory/format-detect.ts +0 -96
- package/dist/src/lib/trajectory/frame-reader.ts +0 -456
- package/dist/src/lib/trajectory/helpers.ts +0 -217
- package/dist/src/lib/trajectory/index.ts +0 -218
- package/dist/src/lib/trajectory/parse/ase.ts +0 -109
- package/dist/src/lib/trajectory/parse/hdf5.ts +0 -173
- package/dist/src/lib/trajectory/parse/index.ts +0 -411
- package/dist/src/lib/trajectory/parse/lammps.ts +0 -215
- package/dist/src/lib/trajectory/parse/vasp.ts +0 -102
- package/dist/src/lib/trajectory/parse/xyz.ts +0 -143
- package/dist/src/lib/trajectory/plotting.ts +0 -599
- package/dist/src/lib/trajectory/types.ts +0 -13
- package/dist/src/lib/utils.ts +0 -56
- package/dist/src/lib/xrd/XrdPlot.svelte +0 -615
- package/dist/src/lib/xrd/broadening.ts +0 -130
- package/dist/src/lib/xrd/calc-xrd.ts +0 -397
- package/dist/src/lib/xrd/index.ts +0 -38
- package/dist/src/lib/xrd/parse.ts +0 -858
- package/dist/webview.js +0 -29421
- package/icon.png +0 -0
- package/matterviz-0.3.2.vsix +0 -0
- package/matterviz-0.3.4.vsix +0 -0
- package/matterviz-0.3.5.vsix +0 -0
- package/scripts/sync-config.ts +0 -101
- package/src/declarations.d.ts +0 -2
- package/src/extension.ts +0 -972
- package/src/node-io.ts +0 -65
- package/src/types.ts +0 -17
- package/src/webview/JsonBrowser.svelte +0 -1079
- package/src/webview/PlotPanel.svelte +0 -346
- package/src/webview/detect.ts +0 -444
- package/src/webview/main.ts +0 -764
- package/src/webview/plot-utils.ts +0 -250
- package/test-fixtures/all-viz-types.json.gz +0 -0
- package/test-fixtures/plot-demo-data.json.gz +0 -0
- package/tests/detect.test.ts +0 -604
- package/tests/extension.test.ts +0 -2041
- package/tests/node-io.test.ts +0 -39
- package/tests/plot-utils.test.ts +0 -302
- package/tests/vite-plugin-json-gz.test.ts +0 -114
- package/tests/vscode-mock.ts +0 -18
- package/tests/webview.test.ts +0 -231
- package/tsconfig.json +0 -20
- package/vite-plugin-json-gz.ts +0 -29
- package/vite.config.ts +0 -34
- package/vite.extension.config.ts +0 -34
- /package/dist/{src/lib/EmptyState.svelte → EmptyState.svelte} +0 -0
- /package/dist/{src/lib/chempot-diagram → chempot-diagram}/ChemPotScene3D.svelte +0 -0
- /package/dist/{src/lib/colors → colors}/alloy-colors.json +0 -0
- /package/dist/{src/lib/colors → colors}/dark-mode-colors.json +0 -0
- /package/dist/{src/lib/colors → colors}/jmol-colors.json +0 -0
- /package/dist/{src/lib/colors → colors}/muted-colors.json +0 -0
- /package/dist/{src/lib/colors → colors}/pastel-colors.json +0 -0
- /package/dist/{src/lib/colors → colors}/vesta-colors.json +0 -0
- /package/dist/{src/lib/element → element}/BohrAtom.svelte +0 -0
- /package/dist/{src/lib/element → element}/Nucleus.svelte +0 -0
- /package/dist/{src/lib/element → element}/data.json +0 -0
- /package/dist/{src/lib/element → element}/data.json.gz +0 -0
- /package/dist/{src/lib/element → element}/data.json.gz.d.ts +0 -0
- /package/dist/{src/lib/element → element}/data.schema.json +0 -0
- /package/dist/{src/lib/element-image-urls.json → element-image-urls.json} +0 -0
- /package/dist/{src/lib/feedback → feedback}/Spinner.svelte +0 -0
- /package/dist/{src/lib/feedback → feedback}/StatusMessage.svelte +0 -0
- /package/dist/{src/lib/layout → layout}/json-tree/JsonValue.svelte +0 -0
- /package/dist/{src/lib/periodic-table → periodic-table}/TableInset.svelte +0 -0
- /package/dist/{src/lib/theme → theme}/themes.mjs +0 -0
- /package/dist/{src/lib/xrd → xrd}/atomic_scattering_params.json +0 -0
|
@@ -0,0 +1,2357 @@
|
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
|
|
4
|
+
>
|
|
5
|
+
import type { D3ColorSchemeName, D3InterpolateName } from '../../colors'
|
|
6
|
+
import { format_value } from '../../labels'
|
|
7
|
+
import { sanitize_html } from '../../sanitize'
|
|
8
|
+
import { FullscreenToggle, set_fullscreen_bg } from '../../layout'
|
|
9
|
+
import type { Point2D, Vec2 } from '../../math'
|
|
10
|
+
import type {
|
|
11
|
+
AxisLoadError,
|
|
12
|
+
BasePlotProps,
|
|
13
|
+
ControlsConfig,
|
|
14
|
+
DataLoaderFn,
|
|
15
|
+
DataSeries,
|
|
16
|
+
ErrorBand,
|
|
17
|
+
FillHandlerEvent,
|
|
18
|
+
FillRegion,
|
|
19
|
+
HoverConfig,
|
|
20
|
+
InitialRanges,
|
|
21
|
+
InternalPoint,
|
|
22
|
+
LabelPlacementConfig,
|
|
23
|
+
LegendConfig,
|
|
24
|
+
PanConfig,
|
|
25
|
+
PlotConfig,
|
|
26
|
+
Point,
|
|
27
|
+
RefLine,
|
|
28
|
+
RefLineEvent,
|
|
29
|
+
ScaleType,
|
|
30
|
+
ScatterHandlerEvent,
|
|
31
|
+
ScatterHandlerProps,
|
|
32
|
+
StyleOverrides,
|
|
33
|
+
UserContentProps,
|
|
34
|
+
} from '..'
|
|
35
|
+
import {
|
|
36
|
+
ColorBar,
|
|
37
|
+
compute_element_placement,
|
|
38
|
+
FillArea,
|
|
39
|
+
get_tick_label,
|
|
40
|
+
Line,
|
|
41
|
+
PlotAxis,
|
|
42
|
+
PlotLegend,
|
|
43
|
+
PlotTooltip,
|
|
44
|
+
ReferenceLine,
|
|
45
|
+
ScatterPlotControls,
|
|
46
|
+
ScatterPoint,
|
|
47
|
+
ZeroLines,
|
|
48
|
+
ZoomRect,
|
|
49
|
+
} from '..'
|
|
50
|
+
import {
|
|
51
|
+
build_obstacles_norm,
|
|
52
|
+
has_explicit_position,
|
|
53
|
+
measured_footprint,
|
|
54
|
+
place_decorations,
|
|
55
|
+
} from '../core/auto-place'
|
|
56
|
+
import type { AxisChangeState } from '../core/axis-utils'
|
|
57
|
+
import { AXIS_DEFAULTS, create_axis_loader } from '../core/axis-utils'
|
|
58
|
+
import { get_series_color, get_series_symbol } from '../core/data-transform'
|
|
59
|
+
import {
|
|
60
|
+
create_dimension_tracker,
|
|
61
|
+
create_hover_lock,
|
|
62
|
+
} from '../core/hover-lock.svelte'
|
|
63
|
+
import {
|
|
64
|
+
DEFAULT_MARKERS,
|
|
65
|
+
get_scale_type_name,
|
|
66
|
+
is_time_scale,
|
|
67
|
+
} from '../core/types'
|
|
68
|
+
import { compute_label_positions } from '../core/utils/label-placement'
|
|
69
|
+
import { create_legend_visibility } from '../core/utils/series-visibility'
|
|
70
|
+
import { DEFAULTS } from '../../settings'
|
|
71
|
+
import { extent } from 'd3-array'
|
|
72
|
+
import { scaleTime } from 'd3-scale'
|
|
73
|
+
import type { ComponentProps, Snippet } from 'svelte'
|
|
74
|
+
import { onDestroy, untrack } from 'svelte'
|
|
75
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
76
|
+
import { Tween, type TweenOptions } from 'svelte/motion'
|
|
77
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
78
|
+
import type { Pt } from '../core/fill-utils'
|
|
79
|
+
import {
|
|
80
|
+
compute_fill_segments,
|
|
81
|
+
convert_error_band_to_fill_region,
|
|
82
|
+
generate_fill_path,
|
|
83
|
+
} from '../core/fill-utils'
|
|
84
|
+
import {
|
|
85
|
+
expand_range_if_needed,
|
|
86
|
+
get_relative_coords,
|
|
87
|
+
MIN_TOUCH_DISTANCE_PIXELS,
|
|
88
|
+
normalize_y2_sync,
|
|
89
|
+
pan_range_by_pixels,
|
|
90
|
+
PINCH_ZOOM_THRESHOLD,
|
|
91
|
+
remove_drag_listeners,
|
|
92
|
+
sorted_range,
|
|
93
|
+
sync_y2_range,
|
|
94
|
+
to_epoch_num,
|
|
95
|
+
zoom_range_by_factor,
|
|
96
|
+
} from '../core/interactions'
|
|
97
|
+
import type { Rect, Sides } from '../core/layout'
|
|
98
|
+
import {
|
|
99
|
+
calc_auto_padding,
|
|
100
|
+
filter_padding,
|
|
101
|
+
LABEL_GAP_DEFAULT,
|
|
102
|
+
y2_axis_label_x,
|
|
103
|
+
measure_full_footprint,
|
|
104
|
+
measure_max_tick_width,
|
|
105
|
+
sample_series_obstacle_points,
|
|
106
|
+
} from '../core/layout'
|
|
107
|
+
import type { IndexedRefLine } from '../core/reference-line'
|
|
108
|
+
import { group_ref_lines_by_z, index_ref_lines } from '../core/reference-line'
|
|
109
|
+
import {
|
|
110
|
+
create_color_scale,
|
|
111
|
+
create_scale,
|
|
112
|
+
create_size_scale,
|
|
113
|
+
generate_ticks,
|
|
114
|
+
get_nice_data_range,
|
|
115
|
+
} from '../core/scales'
|
|
116
|
+
import { resolve_line_tween, unique_id } from '../core/utils'
|
|
117
|
+
import {
|
|
118
|
+
build_legend_data,
|
|
119
|
+
filter_series_to_ranges,
|
|
120
|
+
pick_tooltip_bg,
|
|
121
|
+
} from './scatter-data'
|
|
122
|
+
|
|
123
|
+
let {
|
|
124
|
+
series = $bindable([]),
|
|
125
|
+
x_axis = $bindable({}),
|
|
126
|
+
x2_axis = $bindable({}),
|
|
127
|
+
y_axis = $bindable({}),
|
|
128
|
+
y2_axis = $bindable({}),
|
|
129
|
+
display = $bindable(DEFAULTS.scatter.display),
|
|
130
|
+
styles: styles_init = {},
|
|
131
|
+
controls: controls_init = {},
|
|
132
|
+
padding = {},
|
|
133
|
+
range_padding = 0.05,
|
|
134
|
+
current_x_value = null,
|
|
135
|
+
tooltip_point = $bindable(null),
|
|
136
|
+
selected_point = null,
|
|
137
|
+
hovered = $bindable(false),
|
|
138
|
+
tooltip,
|
|
139
|
+
user_content,
|
|
140
|
+
change = () => {},
|
|
141
|
+
color_scale = {
|
|
142
|
+
type: `linear`,
|
|
143
|
+
scheme: `interpolateViridis`,
|
|
144
|
+
value_range: undefined,
|
|
145
|
+
},
|
|
146
|
+
color_bar = {},
|
|
147
|
+
size_scale = { type: `linear`, radius_range: [2, 10], value_range: undefined },
|
|
148
|
+
label_placement_config = {},
|
|
149
|
+
hover_config = {},
|
|
150
|
+
legend = {},
|
|
151
|
+
point_tween,
|
|
152
|
+
line_tween,
|
|
153
|
+
point_events,
|
|
154
|
+
on_point_click,
|
|
155
|
+
on_point_hover,
|
|
156
|
+
fill_regions = $bindable([]),
|
|
157
|
+
error_bands = [],
|
|
158
|
+
on_fill_click,
|
|
159
|
+
on_fill_hover,
|
|
160
|
+
ref_lines = $bindable([]),
|
|
161
|
+
on_ref_line_click,
|
|
162
|
+
on_ref_line_hover,
|
|
163
|
+
selected_series_idx = $bindable(0),
|
|
164
|
+
wrapper = $bindable(),
|
|
165
|
+
fullscreen = $bindable(false),
|
|
166
|
+
fullscreen_toggle = true,
|
|
167
|
+
children,
|
|
168
|
+
header_controls,
|
|
169
|
+
controls_extra,
|
|
170
|
+
data_loader,
|
|
171
|
+
on_axis_change,
|
|
172
|
+
on_error,
|
|
173
|
+
pan = {},
|
|
174
|
+
...rest
|
|
175
|
+
}: HTMLAttributes<HTMLDivElement> & Omit<BasePlotProps, `change`> & PlotConfig & {
|
|
176
|
+
series?: DataSeries<Metadata>[]
|
|
177
|
+
styles?: StyleOverrides
|
|
178
|
+
controls?: ControlsConfig
|
|
179
|
+
current_x_value?: number | null
|
|
180
|
+
tooltip_point?: InternalPoint<Metadata> | null
|
|
181
|
+
selected_point?: { series_idx: number; point_idx: number } | null
|
|
182
|
+
tooltip?: Snippet<[ScatterHandlerProps<Metadata>]>
|
|
183
|
+
user_content?: Snippet<[UserContentProps]>
|
|
184
|
+
header_controls?: Snippet<
|
|
185
|
+
[{ height: number; width: number; fullscreen: boolean }]
|
|
186
|
+
>
|
|
187
|
+
controls_extra?: Snippet<
|
|
188
|
+
[
|
|
189
|
+
& { styles: StyleOverrides; selected_series_idx: number }
|
|
190
|
+
& Required<PlotConfig>,
|
|
191
|
+
]
|
|
192
|
+
>
|
|
193
|
+
change?: (
|
|
194
|
+
data: (Point<Metadata> & { series: DataSeries<Metadata> }) | null,
|
|
195
|
+
) => void
|
|
196
|
+
color_scale?: {
|
|
197
|
+
type?: ScaleType
|
|
198
|
+
scheme?: D3ColorSchemeName | D3InterpolateName
|
|
199
|
+
value_range?: [number, number]
|
|
200
|
+
} | D3InterpolateName
|
|
201
|
+
size_scale?: {
|
|
202
|
+
type?: ScaleType
|
|
203
|
+
radius_range?: [number, number]
|
|
204
|
+
value_range?: [number, number]
|
|
205
|
+
}
|
|
206
|
+
color_bar?:
|
|
207
|
+
| (ComponentProps<typeof ColorBar> & {
|
|
208
|
+
margin?: number | Sides
|
|
209
|
+
tween?: TweenOptions<Point2D>
|
|
210
|
+
responsive?: boolean // Allow colorbar to reposition if density changes (default: false)
|
|
211
|
+
axis_clearance?: number // Min distance kept from plot edges/axes (default: 15)
|
|
212
|
+
})
|
|
213
|
+
| null
|
|
214
|
+
label_placement_config?: Partial<LabelPlacementConfig>
|
|
215
|
+
hover_config?: Partial<HoverConfig>
|
|
216
|
+
legend?: LegendConfig | null
|
|
217
|
+
point_tween?: TweenOptions<Point2D>
|
|
218
|
+
line_tween?: TweenOptions<string>
|
|
219
|
+
point_events?: Record<
|
|
220
|
+
string,
|
|
221
|
+
(payload: { point: InternalPoint<Metadata>; event: Event }) => void
|
|
222
|
+
>
|
|
223
|
+
on_point_click?: (data: ScatterHandlerEvent<Metadata>) => void
|
|
224
|
+
on_point_hover?: (data: ScatterHandlerEvent<Metadata> | null) => void
|
|
225
|
+
fill_regions?: FillRegion[] // Bindable for legend toggle support
|
|
226
|
+
error_bands?: ErrorBand[]
|
|
227
|
+
on_fill_click?: (event: FillHandlerEvent) => void
|
|
228
|
+
on_fill_hover?: (event: FillHandlerEvent | null) => void
|
|
229
|
+
ref_lines?: RefLine[] // Bindable for legend toggle support
|
|
230
|
+
on_ref_line_click?: (event: RefLineEvent) => void
|
|
231
|
+
on_ref_line_hover?: (event: RefLineEvent | null) => void
|
|
232
|
+
selected_series_idx?: number
|
|
233
|
+
wrapper?: HTMLDivElement
|
|
234
|
+
// Interactive axis props
|
|
235
|
+
data_loader?: DataLoaderFn<Metadata>
|
|
236
|
+
on_axis_change?: (
|
|
237
|
+
axis: `x` | `x2` | `y` | `y2`,
|
|
238
|
+
key: string,
|
|
239
|
+
new_series: DataSeries<Metadata>[],
|
|
240
|
+
) => void
|
|
241
|
+
on_error?: (error: AxisLoadError) => void
|
|
242
|
+
pan?: PanConfig
|
|
243
|
+
} = $props()
|
|
244
|
+
|
|
245
|
+
// Merged axis/display values with defaults (use $derived to avoid breaking $bindable)
|
|
246
|
+
const final_x_axis = $derived({
|
|
247
|
+
...AXIS_DEFAULTS,
|
|
248
|
+
label_shift: { x: 0, y: -40 }, // x-axis needs different label position
|
|
249
|
+
...x_axis,
|
|
250
|
+
})
|
|
251
|
+
const final_y_axis = $derived({ ...AXIS_DEFAULTS, ...y_axis })
|
|
252
|
+
const final_x2_axis = $derived({
|
|
253
|
+
...AXIS_DEFAULTS,
|
|
254
|
+
label_shift: { x: 0, y: 40 }, // x2-axis label above top edge
|
|
255
|
+
...x2_axis,
|
|
256
|
+
})
|
|
257
|
+
const final_y2_axis = $derived({ ...AXIS_DEFAULTS, ...y2_axis })
|
|
258
|
+
// Cache time-axis check — used in ~10 places for scale/tick/tooltip logic
|
|
259
|
+
let is_time_x = $derived(
|
|
260
|
+
is_time_scale(final_x_axis.scale_type, final_x_axis.format),
|
|
261
|
+
)
|
|
262
|
+
let is_time_x2 = $derived(
|
|
263
|
+
is_time_scale(final_x2_axis.scale_type, final_x2_axis.format),
|
|
264
|
+
)
|
|
265
|
+
const final_display = $derived({ ...DEFAULTS.scatter.display, ...display })
|
|
266
|
+
// Local state for styles (initialized from prop, owned by this component for controls)
|
|
267
|
+
// Using $state because styles has bindings in ScatterPlotControls
|
|
268
|
+
// untrack() explicitly captures initial prop value (intentional - props provide initial config)
|
|
269
|
+
let styles = $state(untrack(() => ({
|
|
270
|
+
show_points: DEFAULTS.scatter.show_points,
|
|
271
|
+
show_lines: DEFAULTS.scatter.show_lines,
|
|
272
|
+
point: { ...DEFAULTS.scatter.point, ...styles_init?.point },
|
|
273
|
+
line: { ...DEFAULTS.scatter.line, ...styles_init?.line },
|
|
274
|
+
...styles_init,
|
|
275
|
+
})))
|
|
276
|
+
let controls = $derived({ show: true, open: false, ...controls_init })
|
|
277
|
+
|
|
278
|
+
let [width, height] = $state([0, 0])
|
|
279
|
+
let svg_element: SVGElement | null = $state(null) // Bind the SVG element
|
|
280
|
+
let svg_bounding_box: DOMRect | null = $state(null) // Store SVG bounds during drag
|
|
281
|
+
|
|
282
|
+
// Track which specific control properties user has modified
|
|
283
|
+
let touched = new SvelteSet<string>()
|
|
284
|
+
|
|
285
|
+
// Unique component ID to avoid clipPath conflicts between multiple instances
|
|
286
|
+
let component_id = $state(unique_id(`scatter`))
|
|
287
|
+
let clip_path_id = $derived(`plot-area-clip-${component_id}`)
|
|
288
|
+
|
|
289
|
+
// Assign stable IDs to series for keying
|
|
290
|
+
let series_with_ids = $derived(
|
|
291
|
+
series.map((srs: DataSeries<Metadata>, idx: number) => {
|
|
292
|
+
if (!srs || typeof srs !== `object`) return srs
|
|
293
|
+
// Use series.id if provided, otherwise fall back to index
|
|
294
|
+
// prevents re-mounts when series are reordered if stable IDs are provided
|
|
295
|
+
return { ...srs, _id: srs.id ?? idx }
|
|
296
|
+
}),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
// State for rectangle zoom selection
|
|
300
|
+
let drag_start_coords = $state<Point2D | null>(null)
|
|
301
|
+
let drag_current_coords = $state<Point2D | null>(null)
|
|
302
|
+
|
|
303
|
+
// Zoom/pan state - track both initial (data-driven) and current (after pan/zoom) ranges
|
|
304
|
+
let initial_x_range = $state<[number, number]>([0, 1])
|
|
305
|
+
let initial_x2_range = $state<[number, number]>([0, 1])
|
|
306
|
+
let initial_y_range = $state<[number, number]>([0, 1])
|
|
307
|
+
let initial_y2_range = $state<[number, number]>([0, 1])
|
|
308
|
+
let zoom_x_range = $state<[number, number]>([0, 1])
|
|
309
|
+
let zoom_x2_range = $state<[number, number]>([0, 1])
|
|
310
|
+
let zoom_y_range = $state<[number, number]>([0, 1])
|
|
311
|
+
let zoom_y2_range = $state<[number, number]>([0, 1])
|
|
312
|
+
const legend_vis = create_legend_visibility(() => series, (next) => (series = next))
|
|
313
|
+
|
|
314
|
+
// Y2 axis sync configuration
|
|
315
|
+
let y2_sync_config = $derived(normalize_y2_sync(y2_axis?.sync))
|
|
316
|
+
// Track previous sync mode to detect changes (updated in $effect.pre to avoid race conditions)
|
|
317
|
+
let prev_sync_mode = $state<string>(`none`)
|
|
318
|
+
|
|
319
|
+
// Helper to compute synced y2 range or return fallback when sync disabled
|
|
320
|
+
const get_synced_y2 = (y1_range: Vec2, fallback: Vec2): Vec2 =>
|
|
321
|
+
y2_sync_config.mode !== `none`
|
|
322
|
+
? sync_y2_range(y1_range, initial_y2_range, y2_sync_config)
|
|
323
|
+
: fallback
|
|
324
|
+
|
|
325
|
+
// Effect to update y2 range when sync mode changes - use $effect.pre to capture
|
|
326
|
+
// mode change before the main range-update effect runs, ensuring sync is applied
|
|
327
|
+
// immediately when toggled (not delayed until next data change)
|
|
328
|
+
$effect.pre(() => {
|
|
329
|
+
const mode = y2_sync_config.mode
|
|
330
|
+
if (mode !== prev_sync_mode) {
|
|
331
|
+
// When sync mode becomes enabled (or changes), apply sync immediately
|
|
332
|
+
if (mode !== `none`) {
|
|
333
|
+
zoom_y2_range = sync_y2_range(zoom_y_range, initial_y2_range, y2_sync_config)
|
|
334
|
+
} else {
|
|
335
|
+
// When switching to independent mode, reset Y2 to its data range
|
|
336
|
+
zoom_y2_range = [...initial_y2_range] as [number, number]
|
|
337
|
+
}
|
|
338
|
+
prev_sync_mode = mode
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// Pan state
|
|
343
|
+
let is_focused = $state(false)
|
|
344
|
+
let shift_held = $state(false)
|
|
345
|
+
let pan_drag_state = $state<
|
|
346
|
+
InitialRanges & { start: { x: number; y: number } } | null
|
|
347
|
+
>(null)
|
|
348
|
+
let touch_state = $state<
|
|
349
|
+
InitialRanges & { start_touches: { x: number; y: number }[] } | null
|
|
350
|
+
>(null)
|
|
351
|
+
|
|
352
|
+
// Fill region hover state
|
|
353
|
+
let hovered_fill_key = $state<string | null>(null)
|
|
354
|
+
|
|
355
|
+
// Reference line hover state
|
|
356
|
+
let hovered_ref_line_idx = $state<number | null>(null)
|
|
357
|
+
|
|
358
|
+
// Interactive axis loading state
|
|
359
|
+
let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
|
|
360
|
+
|
|
361
|
+
// State to hold the calculated label positions after simulation
|
|
362
|
+
let label_positions = $state<Record<string, Point2D>>({})
|
|
363
|
+
|
|
364
|
+
// State for legend dragging
|
|
365
|
+
let legend_is_dragging = $state(false)
|
|
366
|
+
let legend_drag_offset = $state<{ x: number; y: number }>({ x: 0, y: 0 })
|
|
367
|
+
let legend_manual_position = $state<{ x: number; y: number } | null>(null)
|
|
368
|
+
let hovered_legend_series_idx = $state<number | null>(null)
|
|
369
|
+
|
|
370
|
+
// State for legend/colorbar placement stability
|
|
371
|
+
let legend_element = $state<HTMLDivElement | undefined>()
|
|
372
|
+
let colorbar_element = $state<HTMLDivElement | undefined>()
|
|
373
|
+
const legend_hover = create_hover_lock()
|
|
374
|
+
const colorbar_hover = create_hover_lock()
|
|
375
|
+
const dim_tracker = create_dimension_tracker()
|
|
376
|
+
let has_initial_legend_placement = $state(false)
|
|
377
|
+
let has_initial_colorbar_placement = $state(false)
|
|
378
|
+
|
|
379
|
+
// Clear pending hover lock timeouts on unmount
|
|
380
|
+
$effect(() => () => {
|
|
381
|
+
legend_hover.cleanup()
|
|
382
|
+
colorbar_hover.cleanup()
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
// Module-level constants to avoid repeated allocations
|
|
386
|
+
// Create and categorize points in a single pass (instead of 3 separate iterations)
|
|
387
|
+
type SimplePoint = { x: number; y: number }
|
|
388
|
+
let points_by_axis = $derived.by(() => {
|
|
389
|
+
const all: SimplePoint[] = []
|
|
390
|
+
const y1: SimplePoint[] = []
|
|
391
|
+
const y2: SimplePoint[] = []
|
|
392
|
+
const x2: SimplePoint[] = []
|
|
393
|
+
|
|
394
|
+
for (const srs of series_with_ids) {
|
|
395
|
+
if (!srs) continue
|
|
396
|
+
const { x: xs, y: ys, visible = true, y_axis: series_y_axis = `y1`, x_axis: x_ax = `x1` } =
|
|
397
|
+
srs as DataSeries
|
|
398
|
+
for (let idx = 0; idx < xs.length; idx++) {
|
|
399
|
+
const point = { x: xs[idx], y: ys[idx] }
|
|
400
|
+
all.push(point)
|
|
401
|
+
if (visible) {
|
|
402
|
+
if (series_y_axis === `y2`) y2.push(point)
|
|
403
|
+
else y1.push(point)
|
|
404
|
+
if (x_ax === `x2`) x2.push(point)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { all, y1, y2, x2 }
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
let all_points = $derived(points_by_axis.all)
|
|
412
|
+
let y1_points = $derived(points_by_axis.y1)
|
|
413
|
+
let y2_points = $derived(points_by_axis.y2)
|
|
414
|
+
let x2_points = $derived(points_by_axis.x2)
|
|
415
|
+
|
|
416
|
+
// Layout: tick-label padding (decoration reservations are added in `pad` below)
|
|
417
|
+
const default_padding = { t: 5, b: 50, l: 50, r: 20 }
|
|
418
|
+
let base_pad = $state(untrack(() => filter_padding(padding, default_padding)))
|
|
419
|
+
|
|
420
|
+
// Update padding when format or ticks change
|
|
421
|
+
$effect(() => {
|
|
422
|
+
const new_pad = width && height &&
|
|
423
|
+
(y_tick_values.length > 0 || y2_tick_values.length > 0 || x2_tick_values.length > 0)
|
|
424
|
+
? calc_auto_padding({
|
|
425
|
+
padding,
|
|
426
|
+
default_padding,
|
|
427
|
+
x2_axis: { ...final_x2_axis, tick_values: x2_tick_values },
|
|
428
|
+
y_axis: { ...final_y_axis, tick_values: y_tick_values },
|
|
429
|
+
y2_axis: { ...final_y2_axis, tick_values: y2_tick_values },
|
|
430
|
+
})
|
|
431
|
+
: filter_padding(padding, default_padding)
|
|
432
|
+
|
|
433
|
+
if (
|
|
434
|
+
base_pad.t !== new_pad.t ||
|
|
435
|
+
base_pad.b !== new_pad.b ||
|
|
436
|
+
base_pad.l !== new_pad.l ||
|
|
437
|
+
base_pad.r !== new_pad.r
|
|
438
|
+
) base_pad = new_pad
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// === Auto-move legend/colorbar outside the plot when interior overlap is unavoidable ===
|
|
442
|
+
// (shared logic lives in auto-place.ts so every 2D plot reuses it)
|
|
443
|
+
// ColorBar's orientation prop defaults to horizontal, so treat unset as horizontal too
|
|
444
|
+
const colorbar_is_horizontal = $derived((color_bar?.orientation ?? `horizontal`) === `horizontal`)
|
|
445
|
+
const colorbar_footprint = $derived(
|
|
446
|
+
colorbar_element?.offsetWidth && colorbar_element?.offsetHeight
|
|
447
|
+
? measure_full_footprint(colorbar_element)
|
|
448
|
+
: colorbar_is_horizontal
|
|
449
|
+
? { width: 220, height: 56 }
|
|
450
|
+
: { width: 56, height: 100 },
|
|
451
|
+
)
|
|
452
|
+
const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 80 }))
|
|
453
|
+
const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
|
|
454
|
+
|
|
455
|
+
// Plot-specific obstacle field: series points/lines normalized to [0,1] (y=0 at top)
|
|
456
|
+
const obstacles_norm = $derived.by(() => {
|
|
457
|
+
if (!width || !height || !filtered_series) return []
|
|
458
|
+
const base_w = width - base_pad.l - base_pad.r
|
|
459
|
+
const base_h = height - base_pad.t - base_pad.b
|
|
460
|
+
if (base_w <= 0 || base_h <= 0) return []
|
|
461
|
+
const norm_x = is_time_x
|
|
462
|
+
? scaleTime().domain([new Date(x_min), new Date(x_max)]).range([0, 1])
|
|
463
|
+
: create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
|
|
464
|
+
const norm_y = create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [0, 1])
|
|
465
|
+
return build_obstacles_norm(
|
|
466
|
+
filtered_series
|
|
467
|
+
.filter((srs) => srs?.filtered_data)
|
|
468
|
+
.map((srs) => ({
|
|
469
|
+
points: srs.filtered_data.map((pt) => ({
|
|
470
|
+
x: is_time_x ? norm_x(new Date(pt.x)) : norm_x(pt.x),
|
|
471
|
+
y: 1 - norm_y(pt.y), // norm_y is 0 at bottom; invert so 0 = top
|
|
472
|
+
})),
|
|
473
|
+
draws_line: styles.show_lines && (srs.markers ?? DEFAULT_MARKERS).includes(`line`),
|
|
474
|
+
})),
|
|
475
|
+
base_w,
|
|
476
|
+
base_h,
|
|
477
|
+
)
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
const decor = $derived.by(() =>
|
|
481
|
+
place_decorations({
|
|
482
|
+
base_pad,
|
|
483
|
+
width,
|
|
484
|
+
height,
|
|
485
|
+
obstacles_norm,
|
|
486
|
+
// gate on legend_element (the actual render signal) not legend_data, whose fill entries read
|
|
487
|
+
// computed_fills -> pad and would make this derived reference itself
|
|
488
|
+
legend: legend != null && legend_element != null &&
|
|
489
|
+
!legend_has_explicit_pos && !legend_is_dragging && !legend_manual_position
|
|
490
|
+
? { footprint: legend_footprint, clearance: legend?.axis_clearance }
|
|
491
|
+
: null,
|
|
492
|
+
// gate on a measured colorbar: its outside style stretches it to full width, so deciding from
|
|
493
|
+
// the (wide) pre-measure fallback would flip-flop placement between interior and outside
|
|
494
|
+
colorbar: Boolean(color_bar) && all_color_values.length > 0 && !color_bar?.wrapper_style &&
|
|
495
|
+
(colorbar_element?.offsetWidth ?? 0) > 0 && (colorbar_element?.offsetHeight ?? 0) > 0
|
|
496
|
+
? {
|
|
497
|
+
footprint: colorbar_footprint,
|
|
498
|
+
horizontal: colorbar_is_horizontal,
|
|
499
|
+
clearance: color_bar?.axis_clearance,
|
|
500
|
+
}
|
|
501
|
+
: null,
|
|
502
|
+
})
|
|
503
|
+
)
|
|
504
|
+
const pad = $derived(decor.pad)
|
|
505
|
+
const legend_auto_outside = $derived(decor.legend_outside)
|
|
506
|
+
const legend_outside_x = $derived(decor.legend_pos.x)
|
|
507
|
+
const legend_outside_y = $derived(decor.legend_pos.y)
|
|
508
|
+
const effective_cbar_wrapper_style = $derived(
|
|
509
|
+
color_bar?.wrapper_style ?? (decor.colorbar_outside ? decor.colorbar_style : undefined),
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
// Reactive clip area dimensions to ensure proper responsiveness
|
|
513
|
+
let clip_area = $derived({
|
|
514
|
+
x: pad.l || 0,
|
|
515
|
+
y: pad.t || 0,
|
|
516
|
+
width: isFinite(width - pad.l - pad.r) ? Math.max(1, width - pad.l - pad.r) : 1,
|
|
517
|
+
height: isFinite(height - pad.t - pad.b)
|
|
518
|
+
? Math.max(1, height - pad.t - pad.b)
|
|
519
|
+
: 1,
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
// Calculate plot area center coordinates
|
|
523
|
+
let plot_center_x = $derived(pad.l + (width - pad.r - pad.l) / 2)
|
|
524
|
+
let plot_center_y = $derived(pad.t + (height - pad.b - pad.t) / 2)
|
|
525
|
+
|
|
526
|
+
// Extract color and size values in single pass (used for scale computations)
|
|
527
|
+
let series_value_arrays = $derived.by(() => {
|
|
528
|
+
const color_values: number[] = []
|
|
529
|
+
const size_values: number[] = []
|
|
530
|
+
for (const srs of series_with_ids) {
|
|
531
|
+
if (!srs) continue
|
|
532
|
+
const { color_values: cvs, size_values: svs } = srs as DataSeries
|
|
533
|
+
if (cvs) { for (const val of cvs) if (val != null) color_values.push(val) }
|
|
534
|
+
if (svs) { for (const val of svs) if (val != null) size_values.push(val) }
|
|
535
|
+
}
|
|
536
|
+
return { color_values, size_values }
|
|
537
|
+
})
|
|
538
|
+
let all_color_values = $derived(series_value_arrays.color_values)
|
|
539
|
+
|
|
540
|
+
// Compute auto ranges based on data and limits
|
|
541
|
+
let auto_x_range = $derived(
|
|
542
|
+
get_nice_data_range(
|
|
543
|
+
all_points,
|
|
544
|
+
({ x }) => x,
|
|
545
|
+
final_x_axis.range ?? [null, null],
|
|
546
|
+
final_x_axis.scale_type ?? `linear`,
|
|
547
|
+
range_padding,
|
|
548
|
+
is_time_x,
|
|
549
|
+
),
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
let auto_y_range = $derived(
|
|
553
|
+
get_nice_data_range(
|
|
554
|
+
y1_points,
|
|
555
|
+
({ y }) => y,
|
|
556
|
+
final_y_axis.range ?? [null, null],
|
|
557
|
+
final_y_axis.scale_type ?? `linear`,
|
|
558
|
+
range_padding,
|
|
559
|
+
false,
|
|
560
|
+
),
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
let auto_x2_range = $derived(
|
|
564
|
+
get_nice_data_range(
|
|
565
|
+
x2_points,
|
|
566
|
+
({ x }) => x,
|
|
567
|
+
final_x2_axis.range ?? [null, null],
|
|
568
|
+
final_x2_axis.scale_type ?? `linear`,
|
|
569
|
+
range_padding,
|
|
570
|
+
is_time_x2,
|
|
571
|
+
),
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
let auto_y2_range = $derived(
|
|
575
|
+
get_nice_data_range(
|
|
576
|
+
y2_points,
|
|
577
|
+
({ y }) => y,
|
|
578
|
+
final_y2_axis.range ?? [null, null],
|
|
579
|
+
final_y2_axis.scale_type ?? `linear`,
|
|
580
|
+
range_padding,
|
|
581
|
+
false,
|
|
582
|
+
),
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
// Update zoom ranges when auto ranges or explicit ranges change
|
|
586
|
+
// - Explicit ranges (from zoom/pan): apply directly
|
|
587
|
+
// - Auto ranges (from data changes): use lazy expansion to preserve view context
|
|
588
|
+
$effect(() => {
|
|
589
|
+
// Helper to get effective range (explicit ?? auto) and check if explicit
|
|
590
|
+
const get_range = (
|
|
591
|
+
axis: { range?: [number | null, number | null] },
|
|
592
|
+
auto: Vec2,
|
|
593
|
+
): { explicit: boolean; range: Vec2 } => {
|
|
594
|
+
const explicit = axis.range?.[0] != null && axis.range?.[1] != null
|
|
595
|
+
const range = [axis.range?.[0] ?? auto[0], axis.range?.[1] ?? auto[1]] as Vec2
|
|
596
|
+
return { explicit, range }
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const x = get_range(final_x_axis, auto_x_range)
|
|
600
|
+
const x2 = get_range(final_x2_axis, auto_x2_range)
|
|
601
|
+
const y = get_range(final_y_axis, auto_y_range)
|
|
602
|
+
const y2 = get_range(final_y2_axis, auto_y2_range)
|
|
603
|
+
|
|
604
|
+
// X axis: explicit → direct, auto → lazy expand
|
|
605
|
+
if (x.explicit) {
|
|
606
|
+
zoom_x_range = x.range
|
|
607
|
+
} else {
|
|
608
|
+
const result = expand_range_if_needed(initial_x_range, x.range)
|
|
609
|
+
if (result.changed) {
|
|
610
|
+
;[initial_x_range, zoom_x_range] = [result.range, result.range]
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// X2 axis: explicit → direct, auto → lazy expand
|
|
615
|
+
if (x2.explicit) {
|
|
616
|
+
zoom_x2_range = x2.range
|
|
617
|
+
} else {
|
|
618
|
+
const result = expand_range_if_needed(initial_x2_range, x2.range)
|
|
619
|
+
if (result.changed) {
|
|
620
|
+
;[initial_x2_range, zoom_x2_range] = [result.range, result.range]
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Y axis: explicit → direct, auto → lazy expand
|
|
625
|
+
if (y.explicit) {
|
|
626
|
+
zoom_y_range = y.range
|
|
627
|
+
} else {
|
|
628
|
+
const result = expand_range_if_needed(initial_y_range, y.range)
|
|
629
|
+
if (result.changed) {
|
|
630
|
+
;[initial_y_range, zoom_y_range] = [result.range, result.range]
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Y2 axis: explicit → direct, else expand initial range then optionally sync
|
|
635
|
+
if (y2.explicit) {
|
|
636
|
+
zoom_y2_range = y2.range
|
|
637
|
+
} else {
|
|
638
|
+
const result = expand_range_if_needed(initial_y2_range, y2.range)
|
|
639
|
+
if (result.changed) initial_y2_range = result.range
|
|
640
|
+
// Apply sync if enabled, otherwise use expanded range (or keep current if unchanged)
|
|
641
|
+
if (y2_sync_config.mode !== `none`) {
|
|
642
|
+
// untrack the read of zoom_y_range: this effect also writes it (fresh array per
|
|
643
|
+
// run when y.explicit), so a tracked read would loop until
|
|
644
|
+
// effect_update_depth_exceeded. Pan/zoom handlers sync y2 themselves.
|
|
645
|
+
zoom_y2_range = sync_y2_range(
|
|
646
|
+
untrack(() => zoom_y_range),
|
|
647
|
+
initial_y2_range,
|
|
648
|
+
y2_sync_config,
|
|
649
|
+
)
|
|
650
|
+
} else if (result.changed) {
|
|
651
|
+
zoom_y2_range = result.range
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
let [x_min, x_max] = $derived(zoom_x_range)
|
|
657
|
+
let [x2_min, x2_max] = $derived(zoom_x2_range)
|
|
658
|
+
let [y_min, y_max] = $derived(zoom_y_range)
|
|
659
|
+
let [y2_min, y2_max] = $derived(zoom_y2_range)
|
|
660
|
+
|
|
661
|
+
// Create auto color range
|
|
662
|
+
let auto_color_range = $derived(
|
|
663
|
+
// Ensure we only calculate extent on actual numbers, filtering out nulls/undefined
|
|
664
|
+
all_color_values.length > 0
|
|
665
|
+
? extent(
|
|
666
|
+
all_color_values.filter((color_val: number | null): color_val is number =>
|
|
667
|
+
typeof color_val === `number`
|
|
668
|
+
),
|
|
669
|
+
)
|
|
670
|
+
: [0, 1],
|
|
671
|
+
) as Vec2
|
|
672
|
+
|
|
673
|
+
// Create scale functions
|
|
674
|
+
// For time scales, use scaleTime directly; otherwise use create_scale (supports linear/log/arcsinh)
|
|
675
|
+
let x_scale_fn = $derived(
|
|
676
|
+
is_time_x
|
|
677
|
+
? scaleTime()
|
|
678
|
+
.domain([new Date(x_min), new Date(x_max)])
|
|
679
|
+
.range([pad.l, width - pad.r])
|
|
680
|
+
: create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [
|
|
681
|
+
pad.l,
|
|
682
|
+
width - pad.r,
|
|
683
|
+
]),
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
let x2_scale_fn = $derived(
|
|
687
|
+
is_time_x2
|
|
688
|
+
? scaleTime()
|
|
689
|
+
.domain([new Date(x2_min), new Date(x2_max)])
|
|
690
|
+
.range([pad.l, width - pad.r])
|
|
691
|
+
: create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [
|
|
692
|
+
pad.l,
|
|
693
|
+
width - pad.r,
|
|
694
|
+
]),
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
let y_scale_fn = $derived(
|
|
698
|
+
create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [
|
|
699
|
+
height - pad.b,
|
|
700
|
+
pad.t,
|
|
701
|
+
]),
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
let y2_scale_fn = $derived(
|
|
705
|
+
create_scale(final_y2_axis.scale_type ?? `linear`, [y2_min, y2_max], [
|
|
706
|
+
height - pad.b,
|
|
707
|
+
pad.t,
|
|
708
|
+
]),
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
// All size values from series (for size scale) - extracted in series_value_arrays
|
|
712
|
+
let all_size_values = $derived(series_value_arrays.size_values)
|
|
713
|
+
|
|
714
|
+
// Size scale function (using shared utility)
|
|
715
|
+
let size_scale_fn = $derived(create_size_scale(size_scale, all_size_values))
|
|
716
|
+
|
|
717
|
+
// Color scale function (using shared utility)
|
|
718
|
+
let color_scale_fn = $derived(create_color_scale(color_scale, auto_color_range))
|
|
719
|
+
|
|
720
|
+
// Filter series data to only include points within bounds and augment with internal data
|
|
721
|
+
let filtered_series = $derived(
|
|
722
|
+
filter_series_to_ranges(series_with_ids, {
|
|
723
|
+
x: [x_min, x_max],
|
|
724
|
+
x2: [x2_min, x2_max],
|
|
725
|
+
y: [y_min, y_max],
|
|
726
|
+
y2: [y2_min, y2_max],
|
|
727
|
+
}),
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
// Tally line series/points to budget path-morph tweens (see resolve_line_tween).
|
|
731
|
+
// Disabling the morph for high-cardinality plots (e.g. phonon bands) keeps them
|
|
732
|
+
// snappy; Line.svelte short-circuits the Tween when duration <= 0.
|
|
733
|
+
let line_tween_load = $derived.by(() => {
|
|
734
|
+
if (!styles.show_lines) return { series: 0, points: 0 }
|
|
735
|
+
let [n_series, n_points] = [0, 0]
|
|
736
|
+
for (const srs of filtered_series ?? []) {
|
|
737
|
+
if (!(srs.markers ?? DEFAULT_MARKERS).includes(`line`)) continue
|
|
738
|
+
n_series += 1
|
|
739
|
+
n_points += srs.x.length
|
|
740
|
+
}
|
|
741
|
+
return { series: n_series, points: n_points }
|
|
742
|
+
})
|
|
743
|
+
let effective_line_tween = $derived(resolve_line_tween(line_tween, line_tween_load))
|
|
744
|
+
|
|
745
|
+
// Obstacle field for legend/colorbar auto-placement. Sampling only data points lets the
|
|
746
|
+
// legend land on top of a steep connecting line whose markers are sparse (e.g. y=x^2), so
|
|
747
|
+
// sample_series_obstacle_points also walks each drawn segment at a fixed pixel cadence.
|
|
748
|
+
const SEGMENT_SAMPLE_STEP = 12 // px between samples taken along a connecting line
|
|
749
|
+
let plot_points_for_placement = $derived.by(() => {
|
|
750
|
+
if (!width || !height || !filtered_series) return []
|
|
751
|
+
|
|
752
|
+
const points: { x: number; y: number }[] = []
|
|
753
|
+
|
|
754
|
+
for (const series_data of filtered_series) {
|
|
755
|
+
if (!series_data?.filtered_data) continue
|
|
756
|
+
const use_x2_scale = series_data.x_axis === `x2`
|
|
757
|
+
const active_x_scale = use_x2_scale ? x2_scale_fn : x_scale_fn
|
|
758
|
+
const active_is_time_x = use_x2_scale ? is_time_x2 : is_time_x
|
|
759
|
+
const active_y_scale = series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn
|
|
760
|
+
const draws_line = styles.show_lines &&
|
|
761
|
+
(series_data.markers ?? DEFAULT_MARKERS).includes(`line`)
|
|
762
|
+
|
|
763
|
+
const pixel_points = series_data.filtered_data.map((point) => ({
|
|
764
|
+
x: active_is_time_x ? active_x_scale(new Date(point.x)) : active_x_scale(point.x),
|
|
765
|
+
y: active_y_scale(point.y),
|
|
766
|
+
}))
|
|
767
|
+
points.push(...sample_series_obstacle_points(pixel_points, draws_line, SEGMENT_SAMPLE_STEP))
|
|
768
|
+
}
|
|
769
|
+
return points
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
const fill_hover_key = (
|
|
773
|
+
source_type: `fill_region` | `error_band`,
|
|
774
|
+
source_idx: number,
|
|
775
|
+
id?: string | number,
|
|
776
|
+
is_duplicate_id = false,
|
|
777
|
+
): string => {
|
|
778
|
+
if (id == null) return `${source_type}:idx:${source_idx}`
|
|
779
|
+
if (is_duplicate_id) return `${source_type}:id:${id}:idx:${source_idx}`
|
|
780
|
+
return `${source_type}:id:${id}`
|
|
781
|
+
}
|
|
782
|
+
const has_duplicate_id = <T extends { id?: string | number }>(
|
|
783
|
+
items: readonly T[] | undefined,
|
|
784
|
+
source_idx: number,
|
|
785
|
+
id?: string | number,
|
|
786
|
+
): boolean =>
|
|
787
|
+
id != null && (items?.some((item, idx) => idx !== source_idx && item.id === id) ?? false)
|
|
788
|
+
|
|
789
|
+
// Computed fill regions: merge fill_regions and converted error_bands, resolve boundaries
|
|
790
|
+
type ComputedFill = FillRegion & {
|
|
791
|
+
idx: number
|
|
792
|
+
source_type: `fill_region` | `error_band`
|
|
793
|
+
source_idx: number
|
|
794
|
+
hover_key: string
|
|
795
|
+
path_segments: string[]
|
|
796
|
+
}
|
|
797
|
+
let computed_fills = $derived.by((): ComputedFill[] => {
|
|
798
|
+
// Early exit: skip expensive computation if no fills to render
|
|
799
|
+
const has_fill_regions = fill_regions && fill_regions.length > 0
|
|
800
|
+
const has_error_bands = error_bands && error_bands.length > 0
|
|
801
|
+
if (!has_fill_regions && !has_error_bands) return []
|
|
802
|
+
|
|
803
|
+
// Merge fill_regions and converted error_bands, tracking source
|
|
804
|
+
const all_regions: {
|
|
805
|
+
region: FillRegion | null
|
|
806
|
+
source_type: `fill_region` | `error_band`
|
|
807
|
+
source_idx: number
|
|
808
|
+
hover_key: string
|
|
809
|
+
}[] = [
|
|
810
|
+
...(fill_regions ?? []).map((region, source_idx) => ({
|
|
811
|
+
region,
|
|
812
|
+
source_type: `fill_region` as const,
|
|
813
|
+
source_idx,
|
|
814
|
+
hover_key: fill_hover_key(
|
|
815
|
+
`fill_region`,
|
|
816
|
+
source_idx,
|
|
817
|
+
region.id,
|
|
818
|
+
has_duplicate_id(fill_regions, source_idx, region.id),
|
|
819
|
+
),
|
|
820
|
+
})),
|
|
821
|
+
...(error_bands ?? []).map((band, source_idx) => ({
|
|
822
|
+
region: convert_error_band_to_fill_region(band, series_with_ids),
|
|
823
|
+
source_type: `error_band` as const,
|
|
824
|
+
source_idx,
|
|
825
|
+
hover_key: fill_hover_key(
|
|
826
|
+
`error_band`,
|
|
827
|
+
source_idx,
|
|
828
|
+
band.id,
|
|
829
|
+
has_duplicate_id(error_bands, source_idx, band.id),
|
|
830
|
+
),
|
|
831
|
+
})),
|
|
832
|
+
]
|
|
833
|
+
|
|
834
|
+
// On log axes, clamp non-positive coords to the scale's domain floor (x_min/y_min) before
|
|
835
|
+
// scaling. A fixed tiny epsilon can sit far below the domain and map to extreme pixel coords.
|
|
836
|
+
const x_scale_type = final_x_axis.scale_type ?? `linear`
|
|
837
|
+
const y_scale_type = final_y_axis.scale_type ?? `linear`
|
|
838
|
+
const to_px = (pt: Pt): Pt => ({
|
|
839
|
+
x: x_scale_fn(x_scale_type === `log` && pt.x <= 0 ? x_min : pt.x),
|
|
840
|
+
y: y_scale_fn(y_scale_type === `log` && pt.y <= 0 ? y_min : pt.y),
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
// Each boundary is traced through its own points with the same curve the series line uses,
|
|
844
|
+
// so fill edges coincide exactly with the lines they border (x_domain anchors flat boundaries).
|
|
845
|
+
const domains = {
|
|
846
|
+
x_domain: [x_min, x_max] as Vec2,
|
|
847
|
+
y_domain: [y_min, y_max] as Vec2,
|
|
848
|
+
y2_domain: [y2_min, y2_max] as Vec2,
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return all_regions
|
|
852
|
+
.filter((
|
|
853
|
+
entry,
|
|
854
|
+
): entry is {
|
|
855
|
+
region: FillRegion
|
|
856
|
+
source_type: `fill_region` | `error_band`
|
|
857
|
+
source_idx: number
|
|
858
|
+
hover_key: string
|
|
859
|
+
} => entry.region !== null)
|
|
860
|
+
.map(({ region, source_type, source_idx, hover_key }, idx) => {
|
|
861
|
+
// Hidden fills keep their entry (with empty path_segments -> nothing renders) so the
|
|
862
|
+
// legend item persists greyed-out and can be toggled back on.
|
|
863
|
+
const hidden = region.visible === false
|
|
864
|
+
const path_segments = hidden
|
|
865
|
+
? []
|
|
866
|
+
: compute_fill_segments(region, series_with_ids, domains)
|
|
867
|
+
.map((seg) =>
|
|
868
|
+
generate_fill_path(
|
|
869
|
+
seg.upper.map(to_px),
|
|
870
|
+
seg.lower.map(to_px),
|
|
871
|
+
seg.upper_curve,
|
|
872
|
+
seg.lower_curve,
|
|
873
|
+
)
|
|
874
|
+
)
|
|
875
|
+
.filter((path) => path.length > 0)
|
|
876
|
+
|
|
877
|
+
// Drop only visible fills with no geometry; keep hidden ones for the legend
|
|
878
|
+
if (!hidden && path_segments.length === 0) return null
|
|
879
|
+
|
|
880
|
+
return { ...region, idx, source_type, source_idx, hover_key, path_segments }
|
|
881
|
+
})
|
|
882
|
+
.filter((fill): fill is ComputedFill => fill !== null)
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
// Prepare data needed for the legend component
|
|
886
|
+
let legend_data = $derived(
|
|
887
|
+
build_legend_data(series_with_ids, computed_fills, color_scale_fn),
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
// Group fills by z-index for ordered rendering (single pass instead of 4 filters)
|
|
891
|
+
let fills_by_z = $derived.by(() => {
|
|
892
|
+
const groups: {
|
|
893
|
+
below_grid: typeof computed_fills
|
|
894
|
+
below_lines: typeof computed_fills
|
|
895
|
+
below_points: typeof computed_fills
|
|
896
|
+
above_all: typeof computed_fills
|
|
897
|
+
} = { below_grid: [], below_lines: [], below_points: [], above_all: [] }
|
|
898
|
+
|
|
899
|
+
for (const fill of computed_fills) {
|
|
900
|
+
if (fill.z_index === `below-grid`) groups.below_grid.push(fill)
|
|
901
|
+
else if (fill.z_index === `below-points`) groups.below_points.push(fill)
|
|
902
|
+
else if (fill.z_index === `above-all`) groups.above_all.push(fill)
|
|
903
|
+
else groups.below_lines.push(fill) // default: no z_index or 'below-lines'
|
|
904
|
+
}
|
|
905
|
+
return groups
|
|
906
|
+
})
|
|
907
|
+
|
|
908
|
+
// Compute ref_lines with index and group by z-index (using shared utilities)
|
|
909
|
+
let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
|
|
910
|
+
let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
|
|
911
|
+
|
|
912
|
+
// Calculate best legend placement using continuous grid sampling
|
|
913
|
+
let legend_placement = $derived.by(() => {
|
|
914
|
+
const should_place = legend != null &&
|
|
915
|
+
(legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)
|
|
916
|
+
|
|
917
|
+
if (!should_place || !width || !height) return null
|
|
918
|
+
|
|
919
|
+
const plot_width = width - pad.l - pad.r
|
|
920
|
+
const plot_height = height - pad.t - pad.b
|
|
921
|
+
|
|
922
|
+
const placement_config = {
|
|
923
|
+
plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
|
|
924
|
+
element: legend_element,
|
|
925
|
+
element_size: { width: 120, height: 80 }, // fallback before first render
|
|
926
|
+
axis_clearance: legend?.axis_clearance,
|
|
927
|
+
exclude_rects: [],
|
|
928
|
+
points: plot_points_for_placement,
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return compute_element_placement(placement_config)
|
|
932
|
+
})
|
|
933
|
+
|
|
934
|
+
// Calculate color bar placement (coordinates with legend to avoid overlap)
|
|
935
|
+
let color_bar_placement = $derived.by(() => {
|
|
936
|
+
if (!color_bar || all_color_values.length === 0 || !width || !height) return null
|
|
937
|
+
|
|
938
|
+
const plot_width = width - pad.l - pad.r
|
|
939
|
+
const plot_height = height - pad.t - pad.b
|
|
940
|
+
|
|
941
|
+
// Fallback estimate (with room for tick labels) used before the colorbar first
|
|
942
|
+
// renders; compute_element_placement measures the real footprint once it's laid out
|
|
943
|
+
const is_horizontal = (color_bar.orientation ?? `horizontal`) === `horizontal`
|
|
944
|
+
const colorbar_size = is_horizontal
|
|
945
|
+
? { width: 220, height: 56 }
|
|
946
|
+
: { width: 56, height: 100 }
|
|
947
|
+
|
|
948
|
+
// Build exclusion rects (avoid legend if it's placed)
|
|
949
|
+
const exclude_rects: Rect[] = []
|
|
950
|
+
if (legend_element && legend_placement) {
|
|
951
|
+
exclude_rects.push({
|
|
952
|
+
x: legend_placement.x,
|
|
953
|
+
y: legend_placement.y,
|
|
954
|
+
width: legend_element.offsetWidth || 120,
|
|
955
|
+
height: legend_element.offsetHeight || 80,
|
|
956
|
+
})
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return compute_element_placement({
|
|
960
|
+
plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
|
|
961
|
+
element: colorbar_element,
|
|
962
|
+
element_size: colorbar_size,
|
|
963
|
+
// Small gap from the corner; the full-footprint measurement reserves the tick
|
|
964
|
+
// labels, so this alone keeps the colorbar off the axes
|
|
965
|
+
axis_clearance: color_bar?.axis_clearance ?? 15,
|
|
966
|
+
exclude_rects,
|
|
967
|
+
points: plot_points_for_placement,
|
|
968
|
+
})
|
|
969
|
+
})
|
|
970
|
+
|
|
971
|
+
// Active legend placement (null if user set explicit position)
|
|
972
|
+
let active_legend_placement = $derived.by(() => {
|
|
973
|
+
if (!legend_placement) return null
|
|
974
|
+
|
|
975
|
+
// Skip auto-placement if user set explicit position in style
|
|
976
|
+
const legend_style = legend?.style ?? ``
|
|
977
|
+
if (
|
|
978
|
+
/(^|[;{]\s*)(top|bottom|left|right)\s*:|position\s*:\s*absolute/.test(
|
|
979
|
+
legend_style,
|
|
980
|
+
)
|
|
981
|
+
) return null
|
|
982
|
+
|
|
983
|
+
return legend_placement
|
|
984
|
+
})
|
|
985
|
+
|
|
986
|
+
// Initialize tweened values for color bar position - create once, update target via effect
|
|
987
|
+
// untrack() explicitly captures initial tween config (intentional - config set once at mount)
|
|
988
|
+
const tweened_colorbar_coords = new Tween(
|
|
989
|
+
{ x: 0, y: 0 },
|
|
990
|
+
untrack(() => ({ duration: 400, ...color_bar?.tween })),
|
|
991
|
+
)
|
|
992
|
+
// Initialize tweened values for legend position - create once, update target via effect
|
|
993
|
+
const tweened_legend_coords = new Tween(
|
|
994
|
+
{ x: 0, y: 0 },
|
|
995
|
+
untrack(() => ({ duration: 400, ...legend?.tween })),
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
// Update placement positions (with animation and stability checks)
|
|
999
|
+
$effect(() => {
|
|
1000
|
+
if (!width || !height) return
|
|
1001
|
+
|
|
1002
|
+
// Track dimensions for resize detection
|
|
1003
|
+
const dims_changed = dim_tracker.has_changed(width, height)
|
|
1004
|
+
if (dims_changed) dim_tracker.update(width, height)
|
|
1005
|
+
|
|
1006
|
+
// Update colorbar position (stable after initial placement unless responsive)
|
|
1007
|
+
if (color_bar_placement) {
|
|
1008
|
+
const is_responsive = color_bar?.responsive ?? false
|
|
1009
|
+
const should_update = dims_changed || (!colorbar_hover.is_locked.current &&
|
|
1010
|
+
(is_responsive || !has_initial_colorbar_placement))
|
|
1011
|
+
|
|
1012
|
+
if (should_update) {
|
|
1013
|
+
tweened_colorbar_coords.set(
|
|
1014
|
+
{ x: color_bar_placement.x, y: color_bar_placement.y },
|
|
1015
|
+
has_initial_colorbar_placement ? undefined : { duration: 0 },
|
|
1016
|
+
)
|
|
1017
|
+
if (colorbar_element && !has_initial_colorbar_placement) {
|
|
1018
|
+
has_initial_colorbar_placement = true
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Update legend position (stable after initial placement unless responsive)
|
|
1024
|
+
if (legend_manual_position && !legend_is_dragging) {
|
|
1025
|
+
// Immediate update (no animation) for manually dragged positions
|
|
1026
|
+
tweened_legend_coords.set(legend_manual_position, { duration: 0 })
|
|
1027
|
+
} else if (active_legend_placement && !legend_is_dragging) {
|
|
1028
|
+
const is_responsive = legend?.responsive ?? false
|
|
1029
|
+
const should_update = dims_changed || (!legend_hover.is_locked.current &&
|
|
1030
|
+
(is_responsive || !has_initial_legend_placement))
|
|
1031
|
+
|
|
1032
|
+
if (should_update) {
|
|
1033
|
+
tweened_legend_coords.set(
|
|
1034
|
+
{ x: active_legend_placement.x, y: active_legend_placement.y },
|
|
1035
|
+
has_initial_legend_placement ? undefined : { duration: 0 },
|
|
1036
|
+
)
|
|
1037
|
+
if (legend_element) has_initial_legend_placement = true
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
// Generate axis ticks - consolidated into single derived for efficiency
|
|
1043
|
+
let axis_ticks = $derived.by(() => {
|
|
1044
|
+
if (!width || !height) return { x: [], x2: [], y: [], y2: [] }
|
|
1045
|
+
|
|
1046
|
+
// X-axis ticks: choose appropriate scale for tick generation
|
|
1047
|
+
// Time scales (format starts with %) use scaleTime for better tick placement
|
|
1048
|
+
const x_scale_for_ticks = is_time_x
|
|
1049
|
+
? scaleTime().domain([new Date(x_min), new Date(x_max)])
|
|
1050
|
+
: create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
|
|
1051
|
+
|
|
1052
|
+
const x2_scale_for_ticks = is_time_x2
|
|
1053
|
+
? scaleTime().domain([new Date(x2_min), new Date(x2_max)])
|
|
1054
|
+
: create_scale(final_x2_axis.scale_type ?? `linear`, [x2_min, x2_max], [0, 1])
|
|
1055
|
+
|
|
1056
|
+
return {
|
|
1057
|
+
x: generate_ticks(
|
|
1058
|
+
[x_min, x_max],
|
|
1059
|
+
final_x_axis.scale_type ?? `linear`,
|
|
1060
|
+
final_x_axis.ticks,
|
|
1061
|
+
x_scale_for_ticks,
|
|
1062
|
+
{ format: final_x_axis.format },
|
|
1063
|
+
),
|
|
1064
|
+
x2: x2_points.length > 0
|
|
1065
|
+
? generate_ticks(
|
|
1066
|
+
[x2_min, x2_max],
|
|
1067
|
+
final_x2_axis.scale_type ?? `linear`,
|
|
1068
|
+
final_x2_axis.ticks,
|
|
1069
|
+
x2_scale_for_ticks,
|
|
1070
|
+
{ format: final_x2_axis.format },
|
|
1071
|
+
)
|
|
1072
|
+
: [],
|
|
1073
|
+
y: generate_ticks(
|
|
1074
|
+
[y_min, y_max],
|
|
1075
|
+
final_y_axis.scale_type ?? `linear`,
|
|
1076
|
+
final_y_axis.ticks,
|
|
1077
|
+
y_scale_fn,
|
|
1078
|
+
{ default_count: 5 },
|
|
1079
|
+
),
|
|
1080
|
+
y2: y2_points.length > 0
|
|
1081
|
+
? generate_ticks(
|
|
1082
|
+
[y2_min, y2_max],
|
|
1083
|
+
final_y2_axis.scale_type ?? `linear`,
|
|
1084
|
+
final_y2_axis.ticks,
|
|
1085
|
+
y2_scale_fn,
|
|
1086
|
+
{ default_count: 5 },
|
|
1087
|
+
)
|
|
1088
|
+
: [],
|
|
1089
|
+
}
|
|
1090
|
+
})
|
|
1091
|
+
|
|
1092
|
+
let x_tick_values = $derived(axis_ticks.x)
|
|
1093
|
+
let x2_tick_values = $derived(axis_ticks.x2)
|
|
1094
|
+
let y_tick_values = $derived(axis_ticks.y)
|
|
1095
|
+
let y2_tick_values = $derived(axis_ticks.y2)
|
|
1096
|
+
|
|
1097
|
+
// Cache measured tick-label widths so expensive text measurement only runs
|
|
1098
|
+
// when tick values/format change, not on every template rerender.
|
|
1099
|
+
let tick_label_widths = $derived({
|
|
1100
|
+
x2_max: measure_max_tick_width(x2_tick_values, final_x2_axis.format ?? ``),
|
|
1101
|
+
y_max: measure_max_tick_width(y_tick_values, final_y_axis.format ?? ``),
|
|
1102
|
+
y2_max: measure_max_tick_width(y2_tick_values, final_y2_axis.format ?? ``),
|
|
1103
|
+
})
|
|
1104
|
+
|
|
1105
|
+
// Define global handlers reference for adding/removing listeners
|
|
1106
|
+
const on_window_mouse_move = (evt: MouseEvent) => {
|
|
1107
|
+
if (!drag_start_coords || !svg_bounding_box) return // Exit if not dragging or no bounds
|
|
1108
|
+
|
|
1109
|
+
// Calculate mouse position relative to the stored SVG bounding box
|
|
1110
|
+
const current_x = evt.clientX - svg_bounding_box.left
|
|
1111
|
+
const current_y = evt.clientY - svg_bounding_box.top
|
|
1112
|
+
drag_current_coords = { x: current_x, y: current_y }
|
|
1113
|
+
|
|
1114
|
+
// Optional: update tooltip only if inside SVG bounds
|
|
1115
|
+
const is_inside_svg = current_x >= 0 &&
|
|
1116
|
+
current_x <= svg_bounding_box.width &&
|
|
1117
|
+
current_y >= 0 &&
|
|
1118
|
+
current_y <= svg_bounding_box.height
|
|
1119
|
+
|
|
1120
|
+
if (is_inside_svg) {
|
|
1121
|
+
// Use the already calculated relative coordinates
|
|
1122
|
+
update_tooltip_point(current_x, current_y)
|
|
1123
|
+
} else tooltip_point = null // Clear tooltip if outside
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const on_window_mouse_up = (_evt: MouseEvent) => {
|
|
1127
|
+
if (drag_start_coords && drag_current_coords) {
|
|
1128
|
+
// Use current scales to invert screen coords to data coords
|
|
1129
|
+
const start_data_x_val = x_scale_fn.invert(drag_start_coords.x)
|
|
1130
|
+
const end_data_x_val = x_scale_fn.invert(drag_current_coords.x)
|
|
1131
|
+
const start_data_y_val = y_scale_fn.invert(drag_start_coords.y)
|
|
1132
|
+
const end_data_y_val = y_scale_fn.invert(drag_current_coords.y)
|
|
1133
|
+
|
|
1134
|
+
// Same scale inverts both coords, so both are numbers or both are Dates
|
|
1135
|
+
const [x1, x2] = [to_epoch_num(start_data_x_val), to_epoch_num(end_data_x_val)]
|
|
1136
|
+
const next_x_range = sorted_range(x1, x2)
|
|
1137
|
+
// Y axis is always number
|
|
1138
|
+
const next_y_range = sorted_range(start_data_y_val, end_data_y_val)
|
|
1139
|
+
|
|
1140
|
+
// Check for minuscule zoom box (e.g. accidental click)
|
|
1141
|
+
const min_zoom_size = 5 // Minimum pixels to trigger zoom
|
|
1142
|
+
const dx = Math.abs(drag_start_coords.x - drag_current_coords.x)
|
|
1143
|
+
const dy = Math.abs(drag_start_coords.y - drag_current_coords.y)
|
|
1144
|
+
|
|
1145
|
+
if (
|
|
1146
|
+
dx > min_zoom_size &&
|
|
1147
|
+
dy > min_zoom_size &&
|
|
1148
|
+
next_x_range[0] !== next_x_range[1] &&
|
|
1149
|
+
next_y_range[0] !== next_y_range[1]
|
|
1150
|
+
) {
|
|
1151
|
+
// Update axis ranges to trigger reactivity (like BarPlot/Histogram do)
|
|
1152
|
+
x_axis = { ...x_axis, range: next_x_range }
|
|
1153
|
+
y_axis = { ...y_axis, range: next_y_range }
|
|
1154
|
+
|
|
1155
|
+
// X2 axis: invert screen coords using x2 scale
|
|
1156
|
+
if (x2_points.length > 0) {
|
|
1157
|
+
const x2_a = to_epoch_num(x2_scale_fn.invert(drag_start_coords.x))
|
|
1158
|
+
const x2_b = to_epoch_num(x2_scale_fn.invert(drag_current_coords.x))
|
|
1159
|
+
x2_axis = { ...x2_axis, range: sorted_range(x2_a, x2_b) }
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Y2 axis: when sync is enabled the y_axis effect derives y2; with sync 'none'
|
|
1163
|
+
// y2 must zoom from the rect directly (parity with BarPlot/Histogram/BoxPlot)
|
|
1164
|
+
if (y2_points.length > 0 && y2_sync_config.mode === `none`) {
|
|
1165
|
+
const y2_a = y2_scale_fn.invert(drag_start_coords.y)
|
|
1166
|
+
const y2_b = y2_scale_fn.invert(drag_current_coords.y)
|
|
1167
|
+
y2_axis = { ...y2_axis, range: sorted_range(y2_a, y2_b) }
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Reset states and remove listeners
|
|
1173
|
+
drag_start_coords = null
|
|
1174
|
+
drag_current_coords = null
|
|
1175
|
+
svg_bounding_box = null
|
|
1176
|
+
window.removeEventListener(`mousemove`, on_window_mouse_move)
|
|
1177
|
+
window.removeEventListener(`mouseup`, on_window_mouse_up)
|
|
1178
|
+
document.body.style.cursor = `default`
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Pan/zoom all four axes from an interaction-start snapshot, each in its own
|
|
1182
|
+
// scale's transform space (log axes pan by a constant factor, linear by a shift).
|
|
1183
|
+
// Plot dims clamped to 1px so degenerate containers can't produce Infinity deltas.
|
|
1184
|
+
const pan_all_axes = (init: InitialRanges, dx_px: number, dy_px: number) => {
|
|
1185
|
+
const plot_width = Math.max(1, width - pad.l - pad.r)
|
|
1186
|
+
const plot_height = Math.max(1, height - pad.t - pad.b)
|
|
1187
|
+
zoom_x_range = pan_range_by_pixels(init.initial_x_range, dx_px, plot_width, final_x_axis.scale_type)
|
|
1188
|
+
zoom_x2_range = pan_range_by_pixels(init.initial_x2_range, dx_px, plot_width, final_x2_axis.scale_type)
|
|
1189
|
+
zoom_y_range = pan_range_by_pixels(init.initial_y_range, dy_px, plot_height, final_y_axis.scale_type)
|
|
1190
|
+
zoom_y2_range = get_synced_y2(
|
|
1191
|
+
zoom_y_range,
|
|
1192
|
+
pan_range_by_pixels(init.initial_y2_range, dy_px, plot_height, final_y2_axis.scale_type),
|
|
1193
|
+
)
|
|
1194
|
+
}
|
|
1195
|
+
const zoom_all_axes = (init: InitialRanges, factor: number) => {
|
|
1196
|
+
zoom_x_range = zoom_range_by_factor(init.initial_x_range, factor, final_x_axis.scale_type)
|
|
1197
|
+
zoom_x2_range = zoom_range_by_factor(init.initial_x2_range, factor, final_x2_axis.scale_type)
|
|
1198
|
+
zoom_y_range = zoom_range_by_factor(init.initial_y_range, factor, final_y_axis.scale_type)
|
|
1199
|
+
zoom_y2_range = get_synced_y2(
|
|
1200
|
+
zoom_y_range,
|
|
1201
|
+
zoom_range_by_factor(init.initial_y2_range, factor, final_y2_axis.scale_type),
|
|
1202
|
+
)
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Pan drag handler (drag direction inverted on x for natural pan feel)
|
|
1206
|
+
const on_pan_move = (evt: MouseEvent) => {
|
|
1207
|
+
if (!pan_drag_state) return
|
|
1208
|
+
const sensitivity = pan?.drag_sensitivity ?? 1
|
|
1209
|
+
pan_all_axes(
|
|
1210
|
+
pan_drag_state,
|
|
1211
|
+
-(evt.clientX - pan_drag_state.start.x) * sensitivity,
|
|
1212
|
+
(evt.clientY - pan_drag_state.start.y) * sensitivity,
|
|
1213
|
+
)
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
const on_pan_end = () => {
|
|
1217
|
+
pan_drag_state = null
|
|
1218
|
+
document.body.style.cursor = ``
|
|
1219
|
+
window.removeEventListener(`mousemove`, on_pan_move)
|
|
1220
|
+
window.removeEventListener(`mouseup`, on_pan_end)
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// Tear down any window listeners + cursor override if the component unmounts mid-drag
|
|
1224
|
+
// (mouseup/panend would otherwise never fire, leaking listeners and a stuck cursor).
|
|
1225
|
+
// onDestroy also runs during SSR teardown, where window/document don't exist.
|
|
1226
|
+
onDestroy(() => {
|
|
1227
|
+
remove_drag_listeners([on_window_mouse_move, on_pan_move], [on_window_mouse_up, on_pan_end])
|
|
1228
|
+
drag_start_coords = null
|
|
1229
|
+
drag_current_coords = null
|
|
1230
|
+
svg_bounding_box = null
|
|
1231
|
+
pan_drag_state = null
|
|
1232
|
+
})
|
|
1233
|
+
|
|
1234
|
+
function handle_mouse_down(evt: MouseEvent) {
|
|
1235
|
+
if (!svg_element) return
|
|
1236
|
+
|
|
1237
|
+
// Check if pan is enabled and shift is held for pan mode
|
|
1238
|
+
const pan_enabled = pan?.enabled !== false
|
|
1239
|
+
if (pan_enabled && evt.shiftKey) {
|
|
1240
|
+
evt.preventDefault()
|
|
1241
|
+
pan_drag_state = {
|
|
1242
|
+
start: { x: evt.clientX, y: evt.clientY },
|
|
1243
|
+
initial_x_range: [...zoom_x_range] as [number, number],
|
|
1244
|
+
initial_x2_range: [...zoom_x2_range] as [number, number],
|
|
1245
|
+
initial_y_range: [...zoom_y_range] as [number, number],
|
|
1246
|
+
initial_y2_range: [...zoom_y2_range] as [number, number],
|
|
1247
|
+
}
|
|
1248
|
+
document.body.style.cursor = `grabbing`
|
|
1249
|
+
window.addEventListener(`mousemove`, on_pan_move)
|
|
1250
|
+
window.addEventListener(`mouseup`, on_pan_end)
|
|
1251
|
+
return
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Store bounding box first, then calculate coords using it
|
|
1255
|
+
svg_bounding_box = svg_element.getBoundingClientRect()
|
|
1256
|
+
|
|
1257
|
+
// Calculate initial coords using the same bounding box that will be used during drag
|
|
1258
|
+
const initial_x = evt.clientX - svg_bounding_box.left
|
|
1259
|
+
const initial_y = evt.clientY - svg_bounding_box.top
|
|
1260
|
+
const coords = { x: initial_x, y: initial_y }
|
|
1261
|
+
|
|
1262
|
+
drag_start_coords = coords
|
|
1263
|
+
drag_current_coords = coords
|
|
1264
|
+
|
|
1265
|
+
window.addEventListener(`mousemove`, on_window_mouse_move)
|
|
1266
|
+
window.addEventListener(`mouseup`, on_window_mouse_up)
|
|
1267
|
+
document.body.style.cursor = `crosshair`
|
|
1268
|
+
evt.preventDefault()
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Wheel handler for pan (requires focus and shift)
|
|
1272
|
+
function handle_wheel(evt: WheelEvent) {
|
|
1273
|
+
const pan_enabled = pan?.enabled !== false
|
|
1274
|
+
// Only capture wheel when focused AND Shift is held
|
|
1275
|
+
// Use shift_held state (tracked via keydown/keyup) for compatibility with synthetic events
|
|
1276
|
+
if (!pan_enabled || !is_focused || !shift_held) return
|
|
1277
|
+
|
|
1278
|
+
evt.preventDefault()
|
|
1279
|
+
|
|
1280
|
+
// Clamp to at least 1 to avoid Infinity deltas when padding equals container size
|
|
1281
|
+
const plot_width = Math.max(1, width - pad.l - pad.r)
|
|
1282
|
+
const plot_height = Math.max(1, height - pad.t - pad.b)
|
|
1283
|
+
const sensitivity = pan?.wheel_sensitivity ?? 1
|
|
1284
|
+
|
|
1285
|
+
// Pan along the dominant wheel direction
|
|
1286
|
+
// (deltaX for horizontal scroll on trackpads, deltaY for vertical)
|
|
1287
|
+
if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
|
|
1288
|
+
const dx = evt.deltaX * sensitivity
|
|
1289
|
+
zoom_x_range = pan_range_by_pixels(zoom_x_range, dx, plot_width, final_x_axis.scale_type)
|
|
1290
|
+
zoom_x2_range = pan_range_by_pixels(zoom_x2_range, dx, plot_width, final_x2_axis.scale_type)
|
|
1291
|
+
} else {
|
|
1292
|
+
const dy = evt.deltaY * sensitivity
|
|
1293
|
+
zoom_y_range = pan_range_by_pixels(zoom_y_range, dy, plot_height, final_y_axis.scale_type)
|
|
1294
|
+
zoom_y2_range = get_synced_y2(
|
|
1295
|
+
zoom_y_range,
|
|
1296
|
+
pan_range_by_pixels(zoom_y2_range, dy, plot_height, final_y2_axis.scale_type),
|
|
1297
|
+
)
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// Touch handlers for pinch-zoom and two-finger pan
|
|
1302
|
+
function handle_touch_start(evt: TouchEvent) {
|
|
1303
|
+
const touch_enabled = pan?.enabled !== false && pan?.touch_enabled !== false
|
|
1304
|
+
if (!touch_enabled || evt.touches.length !== 2) return
|
|
1305
|
+
|
|
1306
|
+
evt.preventDefault()
|
|
1307
|
+
const touches = Array.from(evt.touches)
|
|
1308
|
+
touch_state = {
|
|
1309
|
+
start_touches: touches.map((touch) => ({ x: touch.clientX, y: touch.clientY })),
|
|
1310
|
+
initial_x_range: [...zoom_x_range] as [number, number],
|
|
1311
|
+
initial_x2_range: [...zoom_x2_range] as [number, number],
|
|
1312
|
+
initial_y_range: [...zoom_y_range] as [number, number],
|
|
1313
|
+
initial_y2_range: [...zoom_y2_range] as [number, number],
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function handle_touch_move(evt: TouchEvent) {
|
|
1318
|
+
if (!touch_state || evt.touches.length !== 2) return
|
|
1319
|
+
evt.preventDefault()
|
|
1320
|
+
|
|
1321
|
+
const [t1, t2] = Array.from(evt.touches)
|
|
1322
|
+
const [s1, s2] = touch_state.start_touches
|
|
1323
|
+
|
|
1324
|
+
// Calculate center movement for pan
|
|
1325
|
+
const start_center = { x: (s1.x + s2.x) / 2, y: (s1.y + s2.y) / 2 }
|
|
1326
|
+
const curr_center = {
|
|
1327
|
+
x: (t1.clientX + t2.clientX) / 2,
|
|
1328
|
+
y: (t1.clientY + t2.clientY) / 2,
|
|
1329
|
+
}
|
|
1330
|
+
const dx = curr_center.x - start_center.x
|
|
1331
|
+
const dy = curr_center.y - start_center.y
|
|
1332
|
+
|
|
1333
|
+
// Calculate pinch scale (curr/start so spread = zoom out, pinch = zoom in)
|
|
1334
|
+
const start_dist = Math.hypot(s2.x - s1.x, s2.y - s1.y)
|
|
1335
|
+
// ignore near-coincident touches so curr_dist / start_dist can't blow up the scale
|
|
1336
|
+
if (start_dist < MIN_TOUCH_DISTANCE_PIXELS) return
|
|
1337
|
+
const curr_dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
|
|
1338
|
+
const scale = curr_dist / start_dist
|
|
1339
|
+
|
|
1340
|
+
// Pinch zoom about the view center (spread = zoom in, pinch = zoom out)
|
|
1341
|
+
if (Math.abs(scale - 1) > PINCH_ZOOM_THRESHOLD && scale > Number.EPSILON) {
|
|
1342
|
+
zoom_all_axes(touch_state, scale)
|
|
1343
|
+
} else pan_all_axes(touch_state, -dx, dy)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function handle_touch_end() {
|
|
1347
|
+
touch_state = null
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// tooltip logic: find closest point and update tooltip state
|
|
1351
|
+
function update_tooltip_point(x_rel: number, y_rel: number, evt?: MouseEvent) {
|
|
1352
|
+
if (!width || !height) return
|
|
1353
|
+
|
|
1354
|
+
let closest_point: InternalPoint<Metadata> | null = null
|
|
1355
|
+
let closest_series: DataSeries<Metadata> | null = null
|
|
1356
|
+
let min_screen_dist_sq = Infinity
|
|
1357
|
+
const { threshold_px = 20 } = hover_config // Use configured threshold
|
|
1358
|
+
const hover_threshold_px_sq = threshold_px * threshold_px
|
|
1359
|
+
|
|
1360
|
+
// Iterate through points to find the closest one in screen coordinates
|
|
1361
|
+
for (const series_data of filtered_series) {
|
|
1362
|
+
if (!series_data?.filtered_data) continue
|
|
1363
|
+
|
|
1364
|
+
const tooltip_use_x2 = series_data.x_axis === `x2`
|
|
1365
|
+
const tooltip_x_scale = tooltip_use_x2 ? x2_scale_fn : x_scale_fn
|
|
1366
|
+
const tooltip_is_time_x = tooltip_use_x2 ? is_time_x2 : is_time_x
|
|
1367
|
+
for (const point of series_data.filtered_data) {
|
|
1368
|
+
// Calculate screen coordinates of the point
|
|
1369
|
+
const point_cx = tooltip_is_time_x
|
|
1370
|
+
? tooltip_x_scale(new Date(point.x))
|
|
1371
|
+
: tooltip_x_scale(point.x)
|
|
1372
|
+
const point_cy = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(
|
|
1373
|
+
point.y,
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
// Calculate squared screen distance between mouse and point
|
|
1377
|
+
const screen_dx = x_rel - point_cx
|
|
1378
|
+
const screen_dy = y_rel - point_cy
|
|
1379
|
+
const screen_distance_sq = screen_dx * screen_dx + screen_dy * screen_dy
|
|
1380
|
+
|
|
1381
|
+
// Update if this point is closer
|
|
1382
|
+
if (screen_distance_sq < min_screen_dist_sq) {
|
|
1383
|
+
min_screen_dist_sq = screen_distance_sq
|
|
1384
|
+
closest_point = point
|
|
1385
|
+
closest_series = series_data
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Check if the closest point is within the hover threshold
|
|
1391
|
+
if (
|
|
1392
|
+
closest_point &&
|
|
1393
|
+
closest_series &&
|
|
1394
|
+
min_screen_dist_sq <= hover_threshold_px_sq
|
|
1395
|
+
) {
|
|
1396
|
+
// Construct handler props synchronously to avoid stale derived reads
|
|
1397
|
+
const props = construct_handler_props(closest_point)
|
|
1398
|
+
tooltip_point = closest_point
|
|
1399
|
+
// Construct object matching change signature
|
|
1400
|
+
const { x, y, metadata } = closest_point
|
|
1401
|
+
change({ x, y, metadata, series: closest_series })
|
|
1402
|
+
// Call hover handler with synchronously constructed props
|
|
1403
|
+
if (evt && props) {
|
|
1404
|
+
on_point_hover?.({ ...props, event: evt, point: closest_point })
|
|
1405
|
+
}
|
|
1406
|
+
} else {
|
|
1407
|
+
tooltip_point = null
|
|
1408
|
+
change(null)
|
|
1409
|
+
on_point_hover?.(null)
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function on_mouse_move(evt: MouseEvent) {
|
|
1414
|
+
hovered = true
|
|
1415
|
+
|
|
1416
|
+
const coords = get_relative_coords(evt)
|
|
1417
|
+
if (!coords) return
|
|
1418
|
+
|
|
1419
|
+
update_tooltip_point(coords.x, coords.y, evt)
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Merge user config with defaults before the effect that uses it
|
|
1423
|
+
let actual_label_config = $derived({
|
|
1424
|
+
sa_iterations: 2000,
|
|
1425
|
+
max_labels: 300,
|
|
1426
|
+
leader_line_threshold: 15,
|
|
1427
|
+
...label_placement_config,
|
|
1428
|
+
})
|
|
1429
|
+
|
|
1430
|
+
$effect(() => {
|
|
1431
|
+
if (!width || !height) {
|
|
1432
|
+
label_positions = {}
|
|
1433
|
+
return
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
label_positions = compute_label_positions(
|
|
1437
|
+
filtered_series,
|
|
1438
|
+
actual_label_config,
|
|
1439
|
+
{ x_scale_fn, y_scale_fn, y2_scale_fn, x_axis: final_x_axis },
|
|
1440
|
+
{ width, height, pad },
|
|
1441
|
+
)
|
|
1442
|
+
})
|
|
1443
|
+
|
|
1444
|
+
// Legend drag handlers
|
|
1445
|
+
function handle_legend_drag_start(event: MouseEvent) {
|
|
1446
|
+
if (!svg_element) return
|
|
1447
|
+
|
|
1448
|
+
legend_is_dragging = true
|
|
1449
|
+
|
|
1450
|
+
// Get the actual rendered position of the legend element (accounts for transforms)
|
|
1451
|
+
const legend_el = event.currentTarget
|
|
1452
|
+
if (!(legend_el instanceof HTMLElement)) return
|
|
1453
|
+
const legend_rect = legend_el.getBoundingClientRect()
|
|
1454
|
+
|
|
1455
|
+
// Calculate offset from mouse to legend's actual rendered position relative to SVG
|
|
1456
|
+
const [x, y] = [event.clientX - legend_rect.left, event.clientY - legend_rect.top]
|
|
1457
|
+
legend_drag_offset = { x, y }
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
function handle_legend_drag(event: MouseEvent) {
|
|
1461
|
+
if (!legend_is_dragging || !svg_element || !legend_element) return
|
|
1462
|
+
|
|
1463
|
+
const svg_rect = svg_element.getBoundingClientRect()
|
|
1464
|
+
|
|
1465
|
+
// Calculate new position: mouse position relative to SVG, minus the offset within the legend
|
|
1466
|
+
const new_x = event.clientX - svg_rect.left - legend_drag_offset.x
|
|
1467
|
+
const new_y = event.clientY - svg_rect.top - legend_drag_offset.y
|
|
1468
|
+
|
|
1469
|
+
// Get actual legend dimensions for accurate bounds checking using the bound element reference
|
|
1470
|
+
const { width: legend_width, height: legend_height } = legend_element
|
|
1471
|
+
.getBoundingClientRect()
|
|
1472
|
+
|
|
1473
|
+
// Constrain to plot bounds using measured legend size
|
|
1474
|
+
const constrained_x = Math.max(0, Math.min(width - legend_width, new_x))
|
|
1475
|
+
const constrained_y = Math.max(0, Math.min(height - legend_height, new_y))
|
|
1476
|
+
|
|
1477
|
+
legend_manual_position = { x: constrained_x, y: constrained_y }
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function get_screen_coords(point: Point, data_series?: DataSeries): [number, number] {
|
|
1481
|
+
// convert data coordinates to potentially non-finite screen coordinates
|
|
1482
|
+
const use_x2 = data_series?.x_axis === `x2`
|
|
1483
|
+
const active_x_scale = use_x2 ? x2_scale_fn : x_scale_fn
|
|
1484
|
+
const active_is_time_x = use_x2 ? is_time_x2 : is_time_x
|
|
1485
|
+
const screen_x = active_is_time_x
|
|
1486
|
+
? active_x_scale(new Date(point.x))
|
|
1487
|
+
: active_x_scale(point.x)
|
|
1488
|
+
|
|
1489
|
+
const y_val = point.y
|
|
1490
|
+
// Determine which y-scale to use based on series y_axis property
|
|
1491
|
+
const use_y2 = data_series?.y_axis === `y2`
|
|
1492
|
+
const y_scale = use_y2 ? y2_scale_fn : y_scale_fn
|
|
1493
|
+
const y_scale_type = use_y2
|
|
1494
|
+
? get_scale_type_name(final_y2_axis.scale_type)
|
|
1495
|
+
: get_scale_type_name(final_y_axis.scale_type)
|
|
1496
|
+
// Only log scale needs domain clamping; linear and arcsinh can handle any value
|
|
1497
|
+
const min_domain_y = y_scale_type === `log` ? y_scale.domain()[0] : -Infinity
|
|
1498
|
+
const safe_y_val = y_scale_type === `log` ? Math.max(y_val, min_domain_y) : y_val
|
|
1499
|
+
const screen_y = y_scale(safe_y_val) // This might be non-finite
|
|
1500
|
+
|
|
1501
|
+
return [screen_x, screen_y]
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Helper function to construct ScatterHandlerProps synchronously from InternalPoint
|
|
1505
|
+
function construct_handler_props(
|
|
1506
|
+
point: InternalPoint<Metadata>,
|
|
1507
|
+
): ScatterHandlerProps<Metadata> | null {
|
|
1508
|
+
const hovered_series = series_with_ids[point.series_idx]
|
|
1509
|
+
if (!hovered_series) return null
|
|
1510
|
+
const { x, y, color_value, metadata, series_idx } = point
|
|
1511
|
+
const handler_use_x2 = hovered_series.x_axis === `x2`
|
|
1512
|
+
const handler_x_scale = handler_use_x2 ? x2_scale_fn : x_scale_fn
|
|
1513
|
+
const handler_is_time_x = handler_use_x2 ? is_time_x2 : is_time_x
|
|
1514
|
+
const cx = handler_is_time_x ? handler_x_scale(new Date(x)) : handler_x_scale(x)
|
|
1515
|
+
const cy = (hovered_series.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(y)
|
|
1516
|
+
const active_x_config = handler_use_x2 ? final_x2_axis : final_x_axis
|
|
1517
|
+
const active_y_config = hovered_series.y_axis === `y2`
|
|
1518
|
+
? final_y2_axis
|
|
1519
|
+
: final_y_axis
|
|
1520
|
+
const coords = {
|
|
1521
|
+
x,
|
|
1522
|
+
y,
|
|
1523
|
+
cx,
|
|
1524
|
+
cy,
|
|
1525
|
+
x_axis: active_x_config,
|
|
1526
|
+
x2_axis: final_x2_axis,
|
|
1527
|
+
y_axis: active_y_config,
|
|
1528
|
+
y2_axis: final_y2_axis,
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
...coords,
|
|
1532
|
+
fullscreen,
|
|
1533
|
+
metadata,
|
|
1534
|
+
label: hovered_series.label ?? null,
|
|
1535
|
+
series_idx,
|
|
1536
|
+
x_formatted: format_value(x, active_x_config.format || `.3~s`),
|
|
1537
|
+
y_formatted: format_value(y, active_y_config.format || `.3~s`),
|
|
1538
|
+
color_value: color_value ?? null,
|
|
1539
|
+
colorbar: {
|
|
1540
|
+
value: color_value ?? null,
|
|
1541
|
+
title: color_bar?.title ?? null,
|
|
1542
|
+
scale: color_scale,
|
|
1543
|
+
tick_format: color_bar?.tick_format ?? null,
|
|
1544
|
+
},
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Derive handler props from hovered point for both tooltip and event handlers
|
|
1549
|
+
let handler_props = $derived.by((): ScatterHandlerProps<Metadata> | null => {
|
|
1550
|
+
if (!tooltip_point) return null
|
|
1551
|
+
return construct_handler_props(tooltip_point)
|
|
1552
|
+
})
|
|
1553
|
+
|
|
1554
|
+
let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1)
|
|
1555
|
+
|
|
1556
|
+
// Precompute non-click event names from point_events so we don't rebuild
|
|
1557
|
+
// the entries array on every point render.
|
|
1558
|
+
let point_event_names = $derived(
|
|
1559
|
+
point_events
|
|
1560
|
+
? Object.keys(point_events).filter((name) => name !== `onclick`)
|
|
1561
|
+
: [],
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
// Set theme-aware background when entering fullscreen
|
|
1565
|
+
$effect(() => {
|
|
1566
|
+
set_fullscreen_bg(wrapper, fullscreen, `--scatter-fullscreen-bg`)
|
|
1567
|
+
})
|
|
1568
|
+
|
|
1569
|
+
// State accessors for shared axis change handler
|
|
1570
|
+
const axis_state: AxisChangeState<DataSeries<Metadata>> = {
|
|
1571
|
+
get_axis: (axis) => {
|
|
1572
|
+
if (axis === `x`) return x_axis
|
|
1573
|
+
if (axis === `x2`) return x2_axis
|
|
1574
|
+
if (axis === `y`) return y_axis
|
|
1575
|
+
return y2_axis
|
|
1576
|
+
},
|
|
1577
|
+
set_axis: (axis, config) => {
|
|
1578
|
+
// Spread into existing state to preserve merged type structure
|
|
1579
|
+
if (axis === `x`) x_axis = { ...x_axis, ...config }
|
|
1580
|
+
else if (axis === `x2`) x2_axis = { ...x2_axis, ...config }
|
|
1581
|
+
else if (axis === `y`) y_axis = { ...y_axis, ...config }
|
|
1582
|
+
else y2_axis = { ...y2_axis, ...config }
|
|
1583
|
+
},
|
|
1584
|
+
get_series: () => series,
|
|
1585
|
+
set_series: (new_series) => (series = new_series),
|
|
1586
|
+
get_loading: () => axis_loading,
|
|
1587
|
+
set_loading: (axis) => (axis_loading = axis),
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Shared handler + one-shot auto-load bound to this component's state
|
|
1591
|
+
const { handle_axis_change, try_auto_load } = create_axis_loader(
|
|
1592
|
+
axis_state,
|
|
1593
|
+
() => ({ data_loader, on_axis_change, on_error }),
|
|
1594
|
+
)
|
|
1595
|
+
$effect(try_auto_load)
|
|
1596
|
+
</script>
|
|
1597
|
+
|
|
1598
|
+
{#snippet fill_regions_layer(fills: typeof computed_fills)}
|
|
1599
|
+
{#each fills as fill (fill.hover_key)}
|
|
1600
|
+
{#each fill.path_segments as
|
|
1601
|
+
path_d,
|
|
1602
|
+
segment_idx
|
|
1603
|
+
(`${fill.id ?? fill.idx}-${segment_idx}`)
|
|
1604
|
+
}
|
|
1605
|
+
<FillArea
|
|
1606
|
+
region={fill}
|
|
1607
|
+
region_idx={fill.idx}
|
|
1608
|
+
path={path_d}
|
|
1609
|
+
{clip_path_id}
|
|
1610
|
+
{x_scale_fn}
|
|
1611
|
+
{y_scale_fn}
|
|
1612
|
+
is_hovered={hovered_fill_key === fill.hover_key}
|
|
1613
|
+
on_click={(event: FillHandlerEvent) => {
|
|
1614
|
+
fill.on_click?.(event)
|
|
1615
|
+
on_fill_click?.(event)
|
|
1616
|
+
}}
|
|
1617
|
+
on_hover={(event: FillHandlerEvent | null) => {
|
|
1618
|
+
hovered_fill_key = event ? fill.hover_key : null
|
|
1619
|
+
fill.on_hover?.(event)
|
|
1620
|
+
on_fill_hover?.(event)
|
|
1621
|
+
}}
|
|
1622
|
+
/>
|
|
1623
|
+
{/each}
|
|
1624
|
+
{/each}
|
|
1625
|
+
{/snippet}
|
|
1626
|
+
|
|
1627
|
+
{#snippet ref_lines_layer(lines: IndexedRefLine[])}
|
|
1628
|
+
{#each lines as line (line.id ?? line.idx)}
|
|
1629
|
+
<ReferenceLine
|
|
1630
|
+
ref_line={line}
|
|
1631
|
+
line_idx={line.idx}
|
|
1632
|
+
x_min={line.x_axis === `x2` ? x2_min : x_min}
|
|
1633
|
+
x_max={line.x_axis === `x2` ? x2_max : x_max}
|
|
1634
|
+
y_min={line.y_axis === `y2` ? y2_min : y_min}
|
|
1635
|
+
y_max={line.y_axis === `y2` ? y2_max : y_max}
|
|
1636
|
+
x_scale={x_scale_fn}
|
|
1637
|
+
x2_scale={x2_scale_fn}
|
|
1638
|
+
y_scale={y_scale_fn}
|
|
1639
|
+
y2_scale={y2_scale_fn}
|
|
1640
|
+
{clip_path_id}
|
|
1641
|
+
hovered_line_idx={hovered_ref_line_idx}
|
|
1642
|
+
on_click={(event: RefLineEvent) => {
|
|
1643
|
+
line.on_click?.(event)
|
|
1644
|
+
on_ref_line_click?.(event)
|
|
1645
|
+
}}
|
|
1646
|
+
on_hover={(event: RefLineEvent | null) => {
|
|
1647
|
+
hovered_ref_line_idx = event?.line_idx ?? null
|
|
1648
|
+
line.on_hover?.(event)
|
|
1649
|
+
on_ref_line_hover?.(event)
|
|
1650
|
+
}}
|
|
1651
|
+
/>
|
|
1652
|
+
{/each}
|
|
1653
|
+
{/snippet}
|
|
1654
|
+
|
|
1655
|
+
<svelte:window
|
|
1656
|
+
onkeydown={(evt) => {
|
|
1657
|
+
if (evt.key === `Escape` && fullscreen) {
|
|
1658
|
+
evt.preventDefault()
|
|
1659
|
+
fullscreen = false
|
|
1660
|
+
}
|
|
1661
|
+
if (evt.key === `Shift`) shift_held = true
|
|
1662
|
+
}}
|
|
1663
|
+
onkeyup={(evt) => {
|
|
1664
|
+
if (evt.key === `Shift`) shift_held = false
|
|
1665
|
+
}}
|
|
1666
|
+
/>
|
|
1667
|
+
|
|
1668
|
+
<div
|
|
1669
|
+
bind:this={wrapper}
|
|
1670
|
+
bind:clientWidth={width}
|
|
1671
|
+
bind:clientHeight={height}
|
|
1672
|
+
{...rest}
|
|
1673
|
+
class="scatter {rest.class ?? ``}"
|
|
1674
|
+
class:fullscreen
|
|
1675
|
+
>
|
|
1676
|
+
{#if width && height}
|
|
1677
|
+
<div class="header-controls">
|
|
1678
|
+
{@render header_controls?.({ height, width, fullscreen })}
|
|
1679
|
+
{#if fullscreen_toggle}
|
|
1680
|
+
<FullscreenToggle bind:fullscreen />
|
|
1681
|
+
{/if}
|
|
1682
|
+
</div>
|
|
1683
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
1684
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
1685
|
+
<svg
|
|
1686
|
+
bind:this={svg_element}
|
|
1687
|
+
role="application"
|
|
1688
|
+
aria-label={rest[`aria-label`] ??
|
|
1689
|
+
([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
|
|
1690
|
+
`Scatter plot`)}
|
|
1691
|
+
tabindex="0"
|
|
1692
|
+
onfocusin={() => (is_focused = true)}
|
|
1693
|
+
onfocusout={() => (is_focused = false)}
|
|
1694
|
+
onmouseenter={() => (hovered = true)}
|
|
1695
|
+
onmousedown={handle_mouse_down}
|
|
1696
|
+
onmousemove={(evt: MouseEvent) => {
|
|
1697
|
+
// Only find closest point if not actively dragging
|
|
1698
|
+
if (!drag_start_coords && !pan_drag_state) on_mouse_move(evt)
|
|
1699
|
+
}}
|
|
1700
|
+
onmouseleave={() => {
|
|
1701
|
+
hovered = false
|
|
1702
|
+
tooltip_point = null
|
|
1703
|
+
on_point_hover?.(null)
|
|
1704
|
+
}}
|
|
1705
|
+
ondblclick={() => {
|
|
1706
|
+
// Reset to current auto ranges (not stale initial_*_range which may have expanded)
|
|
1707
|
+
// This ensures lazy expansion restarts fresh from current data bounds
|
|
1708
|
+
initial_x_range = [...auto_x_range] as [number, number]
|
|
1709
|
+
initial_x2_range = [...auto_x2_range] as [number, number]
|
|
1710
|
+
initial_y_range = [...auto_y_range] as [number, number]
|
|
1711
|
+
initial_y2_range = [...auto_y2_range] as [number, number]
|
|
1712
|
+
zoom_x_range = [...auto_x_range] as [number, number]
|
|
1713
|
+
zoom_x2_range = [...auto_x2_range] as [number, number]
|
|
1714
|
+
zoom_y_range = [...auto_y_range] as [number, number]
|
|
1715
|
+
zoom_y2_range = get_synced_y2(auto_y_range, [...auto_y2_range] as Vec2)
|
|
1716
|
+
// Also reset axis props so future data changes recalculate auto ranges
|
|
1717
|
+
x_axis = { ...x_axis, range: [null, null] }
|
|
1718
|
+
x2_axis = { ...x2_axis, range: [null, null] }
|
|
1719
|
+
y_axis = { ...y_axis, range: [null, null] }
|
|
1720
|
+
y2_axis = { ...y2_axis, range: [null, null] }
|
|
1721
|
+
}}
|
|
1722
|
+
onwheel={handle_wheel}
|
|
1723
|
+
ontouchstart={handle_touch_start}
|
|
1724
|
+
ontouchmove={handle_touch_move}
|
|
1725
|
+
ontouchend={handle_touch_end}
|
|
1726
|
+
ontouchcancel={handle_touch_end}
|
|
1727
|
+
style:cursor={pan_drag_state
|
|
1728
|
+
? `grabbing`
|
|
1729
|
+
: shift_held && pan?.enabled !== false
|
|
1730
|
+
? `grab`
|
|
1731
|
+
: `crosshair`}
|
|
1732
|
+
>
|
|
1733
|
+
{@render user_content?.({
|
|
1734
|
+
height,
|
|
1735
|
+
width,
|
|
1736
|
+
x_scale_fn,
|
|
1737
|
+
x2_scale_fn,
|
|
1738
|
+
y_scale_fn,
|
|
1739
|
+
y2_scale_fn,
|
|
1740
|
+
pad,
|
|
1741
|
+
x_range: [x_min, x_max],
|
|
1742
|
+
x2_range: [x2_min, x2_max],
|
|
1743
|
+
y_range: [y_min, y_max],
|
|
1744
|
+
y2_range: [y2_min, y2_max],
|
|
1745
|
+
fullscreen,
|
|
1746
|
+
})}
|
|
1747
|
+
|
|
1748
|
+
<!-- Fill regions: below grid -->
|
|
1749
|
+
{@render fill_regions_layer(fills_by_z.below_grid)}
|
|
1750
|
+
<!-- Reference lines: below grid -->
|
|
1751
|
+
{@render ref_lines_layer(ref_lines_by_z.below_grid)}
|
|
1752
|
+
|
|
1753
|
+
<PlotAxis
|
|
1754
|
+
side="x"
|
|
1755
|
+
ticks={x_tick_values}
|
|
1756
|
+
place={(tick) => (is_time_x ? x_scale_fn(new Date(tick)) : x_scale_fn(tick))}
|
|
1757
|
+
axis={final_x_axis}
|
|
1758
|
+
{pad}
|
|
1759
|
+
{width}
|
|
1760
|
+
{height}
|
|
1761
|
+
show_grid={final_display.x_grid}
|
|
1762
|
+
show_baseline={false}
|
|
1763
|
+
domain={[x_min, x_max]}
|
|
1764
|
+
tick_label={(tick) => get_tick_label(tick, final_x_axis.ticks)}
|
|
1765
|
+
label_x={width / 2 + (final_x_axis.label_shift?.x ?? 0)}
|
|
1766
|
+
label_y={height - pad.b - (final_x_axis.label_shift?.y ?? -40)}
|
|
1767
|
+
axis_loading={axis_loading === `x`}
|
|
1768
|
+
on_axis_change={(key) => handle_axis_change(`x`, key)}
|
|
1769
|
+
/>
|
|
1770
|
+
|
|
1771
|
+
<!-- Current frame indicator -->
|
|
1772
|
+
{#if current_x_value != null}
|
|
1773
|
+
{@const current_pos_raw = is_time_x
|
|
1774
|
+
? x_scale_fn(new Date(current_x_value))
|
|
1775
|
+
: x_scale_fn(current_x_value)}
|
|
1776
|
+
{#if isFinite(current_pos_raw)}
|
|
1777
|
+
{@const current_pos = current_pos_raw}
|
|
1778
|
+
{#if current_pos >= pad.l && current_pos <= width - pad.r}
|
|
1779
|
+
{@const active_tick_height = 7}
|
|
1780
|
+
<rect
|
|
1781
|
+
x={current_pos - 1.5}
|
|
1782
|
+
y={height - pad.b - active_tick_height / 2}
|
|
1783
|
+
width="3"
|
|
1784
|
+
height={active_tick_height}
|
|
1785
|
+
fill="var(--scatter-current-frame-color, #ff6b35)"
|
|
1786
|
+
stroke="white"
|
|
1787
|
+
stroke-width="1"
|
|
1788
|
+
class="current-frame-indicator"
|
|
1789
|
+
/>
|
|
1790
|
+
{/if}
|
|
1791
|
+
{/if}
|
|
1792
|
+
{/if}
|
|
1793
|
+
|
|
1794
|
+
<PlotAxis
|
|
1795
|
+
side="y"
|
|
1796
|
+
ticks={y_tick_values}
|
|
1797
|
+
place={y_scale_fn}
|
|
1798
|
+
axis={final_y_axis}
|
|
1799
|
+
{pad}
|
|
1800
|
+
{width}
|
|
1801
|
+
{height}
|
|
1802
|
+
show_grid={final_display.y_grid}
|
|
1803
|
+
show_baseline={false}
|
|
1804
|
+
domain={[y_min, y_max]}
|
|
1805
|
+
unit_on_first_tick
|
|
1806
|
+
tick_label={(tick) => get_tick_label(tick, final_y_axis.ticks)}
|
|
1807
|
+
label_x={Math.max(
|
|
1808
|
+
12,
|
|
1809
|
+
pad.l - (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
|
|
1810
|
+
LABEL_GAP_DEFAULT,
|
|
1811
|
+
) + (final_y_axis.label_shift?.x ?? 0)}
|
|
1812
|
+
label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y_axis.label_shift?.y ?? 0)}
|
|
1813
|
+
axis_loading={axis_loading === `y`}
|
|
1814
|
+
on_axis_change={(key) => handle_axis_change(`y`, key)}
|
|
1815
|
+
/>
|
|
1816
|
+
|
|
1817
|
+
<!-- Y2-axis (Right) -->
|
|
1818
|
+
{#if y2_points.length > 0}
|
|
1819
|
+
<PlotAxis
|
|
1820
|
+
side="y2"
|
|
1821
|
+
ticks={y2_tick_values}
|
|
1822
|
+
place={y2_scale_fn}
|
|
1823
|
+
axis={final_y2_axis}
|
|
1824
|
+
{pad}
|
|
1825
|
+
{width}
|
|
1826
|
+
{height}
|
|
1827
|
+
show_grid={final_display.y2_grid}
|
|
1828
|
+
show_baseline={false}
|
|
1829
|
+
domain={[y2_min, y2_max]}
|
|
1830
|
+
unit_on_first_tick
|
|
1831
|
+
tick_label={(tick) => get_tick_label(tick, final_y2_axis.ticks)}
|
|
1832
|
+
label_x={y2_axis_label_x(final_y2_axis, width, pad.r, tick_label_widths.y2_max)}
|
|
1833
|
+
label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y2_axis.label_shift?.y ?? 0)}
|
|
1834
|
+
axis_loading={axis_loading === `y2`}
|
|
1835
|
+
on_axis_change={(key) => handle_axis_change(`y2`, key)}
|
|
1836
|
+
/>
|
|
1837
|
+
{/if}
|
|
1838
|
+
|
|
1839
|
+
<!-- X2-axis (Top) -->
|
|
1840
|
+
{#if x2_points.length > 0}
|
|
1841
|
+
<PlotAxis
|
|
1842
|
+
side="x2"
|
|
1843
|
+
ticks={x2_tick_values}
|
|
1844
|
+
place={(tick) => (is_time_x2 ? x2_scale_fn(new Date(tick)) : x2_scale_fn(tick))}
|
|
1845
|
+
axis={final_x2_axis}
|
|
1846
|
+
{pad}
|
|
1847
|
+
{width}
|
|
1848
|
+
{height}
|
|
1849
|
+
show_grid={final_display.x2_grid}
|
|
1850
|
+
show_baseline={false}
|
|
1851
|
+
domain={[x2_min, x2_max]}
|
|
1852
|
+
tick_label={(tick) => get_tick_label(tick, final_x2_axis.ticks)}
|
|
1853
|
+
label_x={width / 2 + (final_x2_axis.label_shift?.x ?? 0)}
|
|
1854
|
+
label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? 40))}
|
|
1855
|
+
axis_loading={axis_loading === `x2`}
|
|
1856
|
+
on_axis_change={(key) => handle_axis_change(`x2`, key)}
|
|
1857
|
+
/>
|
|
1858
|
+
{/if}
|
|
1859
|
+
|
|
1860
|
+
<!-- Tooltip rendered inside overlay (moved outside SVG for stacking above colorbar) -->
|
|
1861
|
+
|
|
1862
|
+
<ZoomRect start={drag_start_coords} current={drag_current_coords} />
|
|
1863
|
+
|
|
1864
|
+
<ZeroLines
|
|
1865
|
+
display={final_display}
|
|
1866
|
+
{x_scale_fn}
|
|
1867
|
+
{x2_scale_fn}
|
|
1868
|
+
{y_scale_fn}
|
|
1869
|
+
{y2_scale_fn}
|
|
1870
|
+
x_range={zoom_x_range}
|
|
1871
|
+
x2_range={zoom_x2_range}
|
|
1872
|
+
y_range={zoom_y_range}
|
|
1873
|
+
y2_range={zoom_y2_range}
|
|
1874
|
+
x_scale_type={final_x_axis.scale_type}
|
|
1875
|
+
x2_scale_type={final_x2_axis.scale_type}
|
|
1876
|
+
y_scale_type={final_y_axis.scale_type}
|
|
1877
|
+
y2_scale_type={final_y2_axis.scale_type}
|
|
1878
|
+
x_is_time={is_time_x}
|
|
1879
|
+
x2_is_time={is_time_x2}
|
|
1880
|
+
has_x2={x2_points.length > 0}
|
|
1881
|
+
has_y2={y2_points.length > 0}
|
|
1882
|
+
{width}
|
|
1883
|
+
{height}
|
|
1884
|
+
{pad}
|
|
1885
|
+
/>
|
|
1886
|
+
|
|
1887
|
+
<defs>
|
|
1888
|
+
<clipPath id={clip_path_id}>
|
|
1889
|
+
<rect
|
|
1890
|
+
x={clip_area.x}
|
|
1891
|
+
y={clip_area.y}
|
|
1892
|
+
width={clip_area.width}
|
|
1893
|
+
height={clip_area.height}
|
|
1894
|
+
/>
|
|
1895
|
+
</clipPath>
|
|
1896
|
+
</defs>
|
|
1897
|
+
|
|
1898
|
+
<!-- Fill regions: below lines (default z-index) -->
|
|
1899
|
+
{@render fill_regions_layer(fills_by_z.below_lines)}
|
|
1900
|
+
<!-- Reference lines: below lines (default z-index) -->
|
|
1901
|
+
{@render ref_lines_layer(ref_lines_by_z.below_lines)}
|
|
1902
|
+
|
|
1903
|
+
<!-- Lines -->
|
|
1904
|
+
{#if styles.show_lines}
|
|
1905
|
+
{#each filtered_series ?? [] as series_data (series_data._id)}
|
|
1906
|
+
{@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
|
|
1907
|
+
{@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
|
|
1908
|
+
<g
|
|
1909
|
+
data-series-id={series_data._id}
|
|
1910
|
+
clip-path="url(#{clip_path_id})"
|
|
1911
|
+
opacity={hovered_legend_series_idx !== null &&
|
|
1912
|
+
hovered_legend_series_idx !== series_data.orig_series_idx
|
|
1913
|
+
? 0.25
|
|
1914
|
+
: 1}
|
|
1915
|
+
>
|
|
1916
|
+
{#if series_markers?.includes(`line`)}
|
|
1917
|
+
{@const all_line_points = series_data.x.map((x, idx) => ({
|
|
1918
|
+
x,
|
|
1919
|
+
y: series_data.y[idx],
|
|
1920
|
+
}))}
|
|
1921
|
+
{@const finite_screen_points = all_line_points
|
|
1922
|
+
.map((point) => get_screen_coords(point, series_data))
|
|
1923
|
+
.filter(([sx, sy]) => isFinite(sx) && isFinite(sy))}
|
|
1924
|
+
{@const apply_line_controls = controls.show &&
|
|
1925
|
+
(!has_multiple_series ||
|
|
1926
|
+
series_data._id === series_with_ids[selected_series_idx]?._id)}
|
|
1927
|
+
{@const ls = series_data.line_style}
|
|
1928
|
+
{@const tc = (key: string) => apply_line_controls && touched.has(key)}
|
|
1929
|
+
{@const color_fallback = ls?.stroke ??
|
|
1930
|
+
(Array.isArray(series_data.point_style)
|
|
1931
|
+
? series_data.point_style[0]?.fill
|
|
1932
|
+
: series_data.point_style?.fill) ??
|
|
1933
|
+
(series_data.color_values?.[0] != null
|
|
1934
|
+
? color_scale_fn(series_data.color_values[0])
|
|
1935
|
+
: series_default_color)}
|
|
1936
|
+
<Line
|
|
1937
|
+
points={finite_screen_points}
|
|
1938
|
+
origin={[
|
|
1939
|
+
is_time_x ? x_scale_fn(new Date(x_min)) : x_scale_fn(x_min),
|
|
1940
|
+
series_data.y_axis === `y2` ? y2_scale_fn(y2_min) : y_scale_fn(y_min),
|
|
1941
|
+
]}
|
|
1942
|
+
line_color={(tc(`line.color`) ? styles.line?.color : null) ?? color_fallback}
|
|
1943
|
+
line_width={(tc(`line.width`) ? styles.line?.width : null) ?? ls?.stroke_width ?? 2}
|
|
1944
|
+
line_dash={(tc(`line.dash`) ? styles.line?.dash : null) ?? ls?.line_dash}
|
|
1945
|
+
area_color="transparent"
|
|
1946
|
+
line_tween={effective_line_tween}
|
|
1947
|
+
/>
|
|
1948
|
+
{/if}
|
|
1949
|
+
</g>
|
|
1950
|
+
{/each}
|
|
1951
|
+
{/if}
|
|
1952
|
+
|
|
1953
|
+
<!-- Fill regions: below points -->
|
|
1954
|
+
{@render fill_regions_layer(fills_by_z.below_points)}
|
|
1955
|
+
<!-- Reference lines: below points -->
|
|
1956
|
+
{@render ref_lines_layer(ref_lines_by_z.below_points)}
|
|
1957
|
+
|
|
1958
|
+
<!-- Points -->
|
|
1959
|
+
{#if styles.show_points}
|
|
1960
|
+
{#each filtered_series ?? [] as series_data (series_data._id)}
|
|
1961
|
+
{@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
|
|
1962
|
+
{@const series_default_color = get_series_color(series_data.orig_series_idx ?? 0)}
|
|
1963
|
+
{@const series_default_symbol = get_series_symbol(series_data.orig_series_idx ?? 0)}
|
|
1964
|
+
<g data-series-id={series_data._id}>
|
|
1965
|
+
{#if series_markers?.includes(`points`)}
|
|
1966
|
+
{#each series_data.filtered_data as
|
|
1967
|
+
point
|
|
1968
|
+
(`${point.series_idx}-${point.point_idx}`)
|
|
1969
|
+
}
|
|
1970
|
+
{@const label_id = `${point.series_idx}-${point.point_idx}`}
|
|
1971
|
+
{@const calculated_label_pos = label_positions[label_id]}
|
|
1972
|
+
{@const point_label = point.point_label ?? {}}
|
|
1973
|
+
{@const label_style = point_label.auto_placement &&
|
|
1974
|
+
actual_label_config.max_neighbors &&
|
|
1975
|
+
!calculated_label_pos
|
|
1976
|
+
? {}
|
|
1977
|
+
: point_label}
|
|
1978
|
+
{@const final_label = calculated_label_pos
|
|
1979
|
+
? {
|
|
1980
|
+
...label_style,
|
|
1981
|
+
offset: {
|
|
1982
|
+
x: calculated_label_pos.x -
|
|
1983
|
+
(is_time_x ? x_scale_fn(new Date(point.x)) : x_scale_fn(point.x)),
|
|
1984
|
+
y: calculated_label_pos.y - (series_data.y_axis === `y2`
|
|
1985
|
+
? y2_scale_fn(point.y)
|
|
1986
|
+
: y_scale_fn(point.y)),
|
|
1987
|
+
},
|
|
1988
|
+
}
|
|
1989
|
+
: label_style}
|
|
1990
|
+
{@const [raw_screen_x, raw_screen_y] = get_screen_coords(point, series_data)}
|
|
1991
|
+
{@const screen_x = isFinite(raw_screen_x) ? raw_screen_x : x_scale_fn.range()[0]}
|
|
1992
|
+
{@const screen_y = isFinite(raw_screen_y)
|
|
1993
|
+
? raw_screen_y
|
|
1994
|
+
: (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn).range()[0]}
|
|
1995
|
+
{@const apply_controls = controls.show &&
|
|
1996
|
+
(!has_multiple_series ||
|
|
1997
|
+
series_data._id === series_with_ids[selected_series_idx]?._id)}
|
|
1998
|
+
{@const pt = point.point_style}
|
|
1999
|
+
{@const tc = (key: string) => apply_controls && touched.has(key)}
|
|
2000
|
+
{@const computed_radius = point.size_value != null
|
|
2001
|
+
? size_scale_fn(point.size_value)
|
|
2002
|
+
: (tc(`point.size`) ? styles.point?.size : null) ?? pt?.radius ?? 4}
|
|
2003
|
+
<ScatterPoint
|
|
2004
|
+
x={screen_x}
|
|
2005
|
+
y={screen_y}
|
|
2006
|
+
is_dimmed={hovered_legend_series_idx !== null &&
|
|
2007
|
+
hovered_legend_series_idx !== point.series_idx}
|
|
2008
|
+
is_hovered={tooltip_point?.series_idx === point.series_idx &&
|
|
2009
|
+
tooltip_point?.point_idx === point.point_idx}
|
|
2010
|
+
is_selected={selected_point?.series_idx === point.series_idx &&
|
|
2011
|
+
selected_point?.point_idx === point.point_idx}
|
|
2012
|
+
leader_line_threshold={actual_label_config.leader_line_threshold}
|
|
2013
|
+
style={{
|
|
2014
|
+
symbol_type: pt?.symbol_type ?? series_default_symbol,
|
|
2015
|
+
...pt,
|
|
2016
|
+
radius: computed_radius,
|
|
2017
|
+
stroke_width:
|
|
2018
|
+
(tc(`point.stroke_width`) ? styles.point?.stroke_width : null) ??
|
|
2019
|
+
pt?.stroke_width ?? 1,
|
|
2020
|
+
stroke:
|
|
2021
|
+
(tc(`point.stroke_color`) ? styles.point?.stroke_color : null) ??
|
|
2022
|
+
pt?.stroke ?? `#000`,
|
|
2023
|
+
stroke_opacity:
|
|
2024
|
+
(tc(`point.stroke_opacity`) ? styles.point?.stroke_opacity : null) ??
|
|
2025
|
+
pt?.stroke_opacity ?? 1,
|
|
2026
|
+
fill_opacity: (tc(`point.opacity`) ? styles.point?.opacity : null) ??
|
|
2027
|
+
pt?.fill_opacity ?? 1,
|
|
2028
|
+
cursor: (on_point_click || point_events?.onclick)
|
|
2029
|
+
? `pointer`
|
|
2030
|
+
: undefined,
|
|
2031
|
+
}}
|
|
2032
|
+
hover={point.point_hover ?? {}}
|
|
2033
|
+
label={final_label}
|
|
2034
|
+
offset={point.point_offset ?? { x: 0, y: 0 }}
|
|
2035
|
+
{point_tween}
|
|
2036
|
+
origin={{ x: plot_center_x, y: plot_center_y }}
|
|
2037
|
+
--point-fill-color={point.color_value != null
|
|
2038
|
+
? color_scale_fn(point.color_value)
|
|
2039
|
+
: (tc(`point.color`) ? styles.point?.color : null) ?? pt?.fill ??
|
|
2040
|
+
series_default_color}
|
|
2041
|
+
{...point_events &&
|
|
2042
|
+
Object.fromEntries(
|
|
2043
|
+
point_event_names.map((name) => [name, (event: Event) =>
|
|
2044
|
+
point_events?.[name]?.({ point, event })]
|
|
2045
|
+
),
|
|
2046
|
+
)}
|
|
2047
|
+
onclick={(event: MouseEvent) => {
|
|
2048
|
+
// Call user-provided onclick handler first if it exists
|
|
2049
|
+
point_events?.onclick?.({ point, event })
|
|
2050
|
+
// then handle internal logic
|
|
2051
|
+
const props = construct_handler_props(point)
|
|
2052
|
+
tooltip_point = point
|
|
2053
|
+
if (props) on_point_click?.({ ...props, event, point })
|
|
2054
|
+
}}
|
|
2055
|
+
/>
|
|
2056
|
+
{/each}
|
|
2057
|
+
{/if}
|
|
2058
|
+
</g>
|
|
2059
|
+
{/each}
|
|
2060
|
+
{/if}
|
|
2061
|
+
|
|
2062
|
+
<!-- Fill regions: above all -->
|
|
2063
|
+
{@render fill_regions_layer(fills_by_z.above_all)}
|
|
2064
|
+
<!-- Reference lines: above all -->
|
|
2065
|
+
{@render ref_lines_layer(ref_lines_by_z.above_all)}
|
|
2066
|
+
</svg>
|
|
2067
|
+
|
|
2068
|
+
<!-- Tooltip overlay above all plot overlays (legend, colorbar) -->
|
|
2069
|
+
{#if handler_props && hovered && tooltip_point}
|
|
2070
|
+
{@const { point_label, series_idx } = tooltip_point}
|
|
2071
|
+
{@const tooltip_bg_color = pick_tooltip_bg(
|
|
2072
|
+
tooltip_point,
|
|
2073
|
+
series_with_ids[series_idx],
|
|
2074
|
+
color_scale_fn,
|
|
2075
|
+
)}
|
|
2076
|
+
<PlotTooltip
|
|
2077
|
+
x={handler_props.cx}
|
|
2078
|
+
y={handler_props.cy}
|
|
2079
|
+
offset={{ x: 10, y: 5 }}
|
|
2080
|
+
constrain_to={{ width, height }}
|
|
2081
|
+
fallback_size={{ width: 120, height: 50 }}
|
|
2082
|
+
bg_color={tooltip_bg_color}
|
|
2083
|
+
>
|
|
2084
|
+
{#if tooltip}
|
|
2085
|
+
{@render tooltip(handler_props)}
|
|
2086
|
+
{:else}
|
|
2087
|
+
{@const hp = handler_props}
|
|
2088
|
+
{#if has_multiple_series && hp.label}<strong>{hp.label}</strong><br />{/if}
|
|
2089
|
+
{@html sanitize_html(point_label?.text ? `${point_label.text}<br />` : ``)}
|
|
2090
|
+
{@html sanitize_html(hp.x_axis.label || `x`)}: {hp.x_formatted}<br />
|
|
2091
|
+
{@html sanitize_html(hp.y_axis.label || `y`)}: {hp.y_formatted}
|
|
2092
|
+
{#if hp.colorbar?.value != null}
|
|
2093
|
+
<br />{@html sanitize_html(hp.colorbar.title || `Color`)}: {
|
|
2094
|
+
format_value(hp.colorbar.value, hp.colorbar.tick_format || `.3~g`)
|
|
2095
|
+
}
|
|
2096
|
+
{/if}
|
|
2097
|
+
{/if}
|
|
2098
|
+
</PlotTooltip>
|
|
2099
|
+
{/if}
|
|
2100
|
+
|
|
2101
|
+
<!-- Control Pane -->
|
|
2102
|
+
{#if controls.show}
|
|
2103
|
+
<ScatterPlotControls
|
|
2104
|
+
toggle_props={{
|
|
2105
|
+
...controls.toggle_props,
|
|
2106
|
+
style:
|
|
2107
|
+
`--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); top: var(--ctrl-btn-top, 5pt); ${
|
|
2108
|
+
controls.toggle_props?.style ?? ``
|
|
2109
|
+
}`,
|
|
2110
|
+
}}
|
|
2111
|
+
pane_props={controls.pane_props}
|
|
2112
|
+
bind:x_axis
|
|
2113
|
+
bind:x2_axis
|
|
2114
|
+
bind:y_axis
|
|
2115
|
+
bind:y2_axis
|
|
2116
|
+
bind:display
|
|
2117
|
+
bind:styles
|
|
2118
|
+
{auto_x_range}
|
|
2119
|
+
{auto_x2_range}
|
|
2120
|
+
{auto_y_range}
|
|
2121
|
+
{auto_y2_range}
|
|
2122
|
+
bind:selected_series_idx
|
|
2123
|
+
series={series_with_ids}
|
|
2124
|
+
has_x2_points={x2_points.length > 0}
|
|
2125
|
+
has_y2_points={y2_points.length > 0}
|
|
2126
|
+
children={controls_extra}
|
|
2127
|
+
on_touch={(key: string) => touched.add(key)}
|
|
2128
|
+
/>
|
|
2129
|
+
{/if}
|
|
2130
|
+
|
|
2131
|
+
<!-- Color Bar -->
|
|
2132
|
+
{#if color_bar && all_color_values.length > 0 && color_bar_placement}
|
|
2133
|
+
{@const color_domain = [
|
|
2134
|
+
(typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
|
|
2135
|
+
auto_color_range[0],
|
|
2136
|
+
(typeof color_scale === `string` ? undefined : color_scale.value_range)?.[1] ??
|
|
2137
|
+
auto_color_range[1],
|
|
2138
|
+
] as Vec2}
|
|
2139
|
+
<div
|
|
2140
|
+
bind:this={colorbar_element}
|
|
2141
|
+
onmouseenter={() => colorbar_hover.set_locked(true)}
|
|
2142
|
+
onmouseleave={() => colorbar_hover.set_locked(false)}
|
|
2143
|
+
class="colorbar-wrapper"
|
|
2144
|
+
role="img"
|
|
2145
|
+
aria-label="Color scale legend"
|
|
2146
|
+
style={`${
|
|
2147
|
+
// explicit wrapper_style or auto-outside places the colorbar; else auto-placement coords
|
|
2148
|
+
effective_cbar_wrapper_style ??
|
|
2149
|
+
`position: absolute; left: ${tweened_colorbar_coords.current.x}px; top: ${tweened_colorbar_coords.current.y}px`}; pointer-events: auto;`}
|
|
2150
|
+
>
|
|
2151
|
+
<ColorBar
|
|
2152
|
+
tick_labels={4}
|
|
2153
|
+
tick_side="primary"
|
|
2154
|
+
{color_scale_fn}
|
|
2155
|
+
color_scale_domain={color_domain}
|
|
2156
|
+
scale_type={typeof color_scale === `string` ? undefined : color_scale.type}
|
|
2157
|
+
range={color_domain?.every((val) => val != null) ? color_domain : undefined}
|
|
2158
|
+
bar_style="width: 220px; height: 16px; {color_bar?.style ?? ``}"
|
|
2159
|
+
{...color_bar}
|
|
2160
|
+
wrapper_style={effective_cbar_wrapper_style ? `height: 100%; width: 100%;` : ``}
|
|
2161
|
+
/>
|
|
2162
|
+
</div>
|
|
2163
|
+
{/if}
|
|
2164
|
+
|
|
2165
|
+
<!-- Legend -->
|
|
2166
|
+
<!-- Only render if multiple series or if legend prop was explicitly provided by user (even if empty object) -->
|
|
2167
|
+
{#if legend != null && legend_data.length > 0 &&
|
|
2168
|
+
(legend_data.length > 1 || Object.keys(legend ?? {}).length > 0)}
|
|
2169
|
+
{@const default_x = pad.l + 10}
|
|
2170
|
+
{@const default_y = pad.t + 10}
|
|
2171
|
+
{@const current_x = legend_is_dragging && legend_manual_position
|
|
2172
|
+
? legend_manual_position.x
|
|
2173
|
+
: legend_auto_outside
|
|
2174
|
+
? legend_outside_x
|
|
2175
|
+
: legend_placement
|
|
2176
|
+
? tweened_legend_coords.current.x
|
|
2177
|
+
: default_x}
|
|
2178
|
+
{@const current_y = legend_is_dragging && legend_manual_position
|
|
2179
|
+
? legend_manual_position.y
|
|
2180
|
+
: legend_auto_outside
|
|
2181
|
+
? legend_outside_y
|
|
2182
|
+
: legend_placement
|
|
2183
|
+
? tweened_legend_coords.current.y
|
|
2184
|
+
: default_y}
|
|
2185
|
+
<PlotLegend
|
|
2186
|
+
bind:root_element={legend_element}
|
|
2187
|
+
series_data={legend_data}
|
|
2188
|
+
on_drag_start={handle_legend_drag_start}
|
|
2189
|
+
on_drag={handle_legend_drag}
|
|
2190
|
+
on_drag_end={() => (legend_is_dragging = false)}
|
|
2191
|
+
on_hover_change={legend_hover.set_locked}
|
|
2192
|
+
on_item_hover={(item) => {
|
|
2193
|
+
if (item?.item_type === `fill`) {
|
|
2194
|
+
// highlight the matching fill in the plot (same state plot fill-hover uses), but skip
|
|
2195
|
+
// hidden fills since they render nothing and would mark the legend item active for naught
|
|
2196
|
+
const fill = computed_fills.find((entry) => entry.idx === item.fill_idx)
|
|
2197
|
+
hovered_fill_key = fill && fill.visible !== false ? fill.hover_key : null
|
|
2198
|
+
hovered_legend_series_idx = null
|
|
2199
|
+
} else {
|
|
2200
|
+
hovered_legend_series_idx = item != null && item.series_idx >= 0
|
|
2201
|
+
? item.series_idx
|
|
2202
|
+
: null
|
|
2203
|
+
hovered_fill_key = null
|
|
2204
|
+
}
|
|
2205
|
+
}}
|
|
2206
|
+
active_series_idx={tooltip_point?.series_idx ?? hovered_legend_series_idx}
|
|
2207
|
+
active_fill_idx={computed_fills.find((fill) => fill.hover_key === hovered_fill_key)?.idx ??
|
|
2208
|
+
null}
|
|
2209
|
+
draggable={legend?.draggable ?? true}
|
|
2210
|
+
{...legend}
|
|
2211
|
+
on_toggle={legend?.on_toggle ?? legend_vis.on_toggle}
|
|
2212
|
+
on_double_click={legend?.on_double_click ?? legend_vis.on_double_click}
|
|
2213
|
+
on_group_toggle={legend?.on_group_toggle ?? legend_vis.on_group_toggle}
|
|
2214
|
+
on_fill_toggle={(source_type: `fill_region` | `error_band`, source_idx: number) => {
|
|
2215
|
+
// Only fill_regions can be toggled (error_bands are not bindable)
|
|
2216
|
+
if (source_type === `fill_region`) {
|
|
2217
|
+
fill_regions = fill_regions.map((region, idx) =>
|
|
2218
|
+
idx === source_idx
|
|
2219
|
+
? { ...region, visible: !(region.visible !== false) }
|
|
2220
|
+
: region
|
|
2221
|
+
)
|
|
2222
|
+
}
|
|
2223
|
+
}}
|
|
2224
|
+
on_fill_double_click={(
|
|
2225
|
+
source_type: `fill_region` | `error_band`,
|
|
2226
|
+
source_idx: number,
|
|
2227
|
+
) => {
|
|
2228
|
+
// Only fill_regions can be toggled (error_bands are not bindable)
|
|
2229
|
+
if (source_type !== `fill_region`) return
|
|
2230
|
+
// Toggle: if only this fill is visible, show all; otherwise show only this one
|
|
2231
|
+
const visible_count = fill_regions.filter((region) => region.visible !== false).length
|
|
2232
|
+
const this_visible = fill_regions[source_idx]?.visible !== false
|
|
2233
|
+
if (visible_count === 1 && this_visible) {
|
|
2234
|
+
// Show all fills
|
|
2235
|
+
fill_regions = fill_regions.map((region) => ({ ...region, visible: true }))
|
|
2236
|
+
} else {
|
|
2237
|
+
// Show only this fill
|
|
2238
|
+
fill_regions = fill_regions.map((region, idx) => ({
|
|
2239
|
+
...region,
|
|
2240
|
+
visible: idx === source_idx,
|
|
2241
|
+
}))
|
|
2242
|
+
}
|
|
2243
|
+
}}
|
|
2244
|
+
style={`
|
|
2245
|
+
position: absolute;
|
|
2246
|
+
left: ${current_x}px;
|
|
2247
|
+
top: ${current_y}px;
|
|
2248
|
+
pointer-events: auto;
|
|
2249
|
+
${legend?.style ?? ``}
|
|
2250
|
+
`}
|
|
2251
|
+
/>
|
|
2252
|
+
{/if}
|
|
2253
|
+
{/if}
|
|
2254
|
+
|
|
2255
|
+
<!-- User-provided children (e.g. for custom absolutely-positioned overlays) -->
|
|
2256
|
+
{@render children?.({ height, width, fullscreen })}
|
|
2257
|
+
</div>
|
|
2258
|
+
|
|
2259
|
+
<style>
|
|
2260
|
+
div.scatter {
|
|
2261
|
+
position: relative; /* Needed for absolute positioning of children like ColorBar */
|
|
2262
|
+
width: var(--scatter-width, 100%);
|
|
2263
|
+
height: var(--scatter-height, auto);
|
|
2264
|
+
min-height: var(--scatter-min-height, 350px);
|
|
2265
|
+
container-type: size; /* enable cqh for panes */
|
|
2266
|
+
container-name: scatter-plot;
|
|
2267
|
+
z-index: var(--scatter-z-index);
|
|
2268
|
+
flex: var(--scatter-flex, 1); /* Allow filling available space in flex containers */
|
|
2269
|
+
display: var(--scatter-display, flex);
|
|
2270
|
+
flex-direction: column;
|
|
2271
|
+
background: var(--scatter-bg, var(--plot-bg));
|
|
2272
|
+
border-radius: var(--scatter-border-radius, var(--border-radius, 3pt));
|
|
2273
|
+
}
|
|
2274
|
+
div.scatter.fullscreen {
|
|
2275
|
+
position: fixed;
|
|
2276
|
+
top: 0;
|
|
2277
|
+
left: 0;
|
|
2278
|
+
width: 100vw !important;
|
|
2279
|
+
height: 100vh !important;
|
|
2280
|
+
/* Must be higher than Structure.svelte's --struct-buttons-z-index. */
|
|
2281
|
+
z-index: var(--scatter-fullscreen-z-index, var(--z-index-overlay-nav, 100000001));
|
|
2282
|
+
margin: 0;
|
|
2283
|
+
border-radius: 0;
|
|
2284
|
+
background: var(--scatter-fullscreen-bg, var(--scatter-bg, var(--plot-bg)));
|
|
2285
|
+
max-height: none !important;
|
|
2286
|
+
overflow: hidden;
|
|
2287
|
+
/* border-top (not padding-top): bind:clientHeight includes padding but excludes
|
|
2288
|
+
borders - padding made the chart overflow + clip its bottom 2em (x-axis title) */
|
|
2289
|
+
border-top: var(--plot-fullscreen-padding-top, 2em) solid
|
|
2290
|
+
var(--scatter-fullscreen-bg, var(--scatter-bg, var(--plot-bg, transparent)));
|
|
2291
|
+
box-sizing: border-box;
|
|
2292
|
+
}
|
|
2293
|
+
/* Center the colorbar within its wrapper when shorter than it (e.g. capped by --cbar-max-height
|
|
2294
|
+
in fullscreen). Users can override via wrapper_style (inline wins). */
|
|
2295
|
+
.colorbar-wrapper {
|
|
2296
|
+
display: flex;
|
|
2297
|
+
align-items: center;
|
|
2298
|
+
justify-content: center;
|
|
2299
|
+
}
|
|
2300
|
+
.header-controls {
|
|
2301
|
+
position: absolute;
|
|
2302
|
+
top: var(--ctrl-btn-top, 5pt);
|
|
2303
|
+
right: var(--fullscreen-btn-right, 4px);
|
|
2304
|
+
z-index: var(--fullscreen-btn-z-index, 10);
|
|
2305
|
+
display: flex;
|
|
2306
|
+
align-items: center;
|
|
2307
|
+
gap: 8px;
|
|
2308
|
+
}
|
|
2309
|
+
.header-controls :global(.fullscreen-toggle) {
|
|
2310
|
+
position: static; /* Override absolute positioning since container handles it */
|
|
2311
|
+
opacity: 1; /* Always visible when inside header-controls, container controls visibility */
|
|
2312
|
+
}
|
|
2313
|
+
/* Hide controls and fullscreen toggles by default, show on hover */
|
|
2314
|
+
div.scatter :global(.pane-toggle),
|
|
2315
|
+
div.scatter .header-controls {
|
|
2316
|
+
opacity: 0;
|
|
2317
|
+
transition: opacity 0.2s, background-color 0.2s;
|
|
2318
|
+
}
|
|
2319
|
+
div.scatter:hover :global(.pane-toggle),
|
|
2320
|
+
div.scatter:hover .header-controls,
|
|
2321
|
+
div.scatter :global(.pane-toggle:focus-visible),
|
|
2322
|
+
div.scatter :global(.pane-toggle[aria-expanded='true']),
|
|
2323
|
+
div.scatter .header-controls:focus-within {
|
|
2324
|
+
opacity: 1;
|
|
2325
|
+
}
|
|
2326
|
+
svg {
|
|
2327
|
+
width: var(--scatter-svg-width, 100%);
|
|
2328
|
+
height: var(--scatter-svg-height, 100%);
|
|
2329
|
+
flex: var(--scatter-svg-flex, 1);
|
|
2330
|
+
overflow: var(--scatter-svg-overflow, visible);
|
|
2331
|
+
fill: var(--text-color);
|
|
2332
|
+
font-weight: var(--scatter-font-weight);
|
|
2333
|
+
font-size: var(--scatter-font-size);
|
|
2334
|
+
}
|
|
2335
|
+
.scatter :global(.axis-label) {
|
|
2336
|
+
text-align: center;
|
|
2337
|
+
width: 100%;
|
|
2338
|
+
height: 100%;
|
|
2339
|
+
font-size: var(--scatter-font-size, inherit);
|
|
2340
|
+
font-weight: var(--scatter-font-weight, normal);
|
|
2341
|
+
color: var(--text-color);
|
|
2342
|
+
white-space: nowrap;
|
|
2343
|
+
/* Use line-height to center text vertically without flexbox */
|
|
2344
|
+
line-height: var(
|
|
2345
|
+
--scatter-axis-label-line-height,
|
|
2346
|
+
20px
|
|
2347
|
+
); /* Match foreignObject height */
|
|
2348
|
+
display: block;
|
|
2349
|
+
}
|
|
2350
|
+
.current-frame-indicator {
|
|
2351
|
+
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2));
|
|
2352
|
+
transition: opacity 0.2s ease;
|
|
2353
|
+
}
|
|
2354
|
+
.current-frame-indicator:hover {
|
|
2355
|
+
opacity: 0.8;
|
|
2356
|
+
}
|
|
2357
|
+
</style>
|