matterviz 0.3.1 → 0.3.3
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 +10 -2
- package/dist/FilePicker.svelte +154 -96
- package/dist/Icon.svelte +20 -14
- package/dist/MillerIndexInput.svelte +27 -21
- package/dist/api/optimade.js +6 -6
- package/dist/app.css +216 -178
- package/dist/brillouin/BrillouinZone.svelte +299 -198
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
- package/dist/brillouin/BrillouinZoneExportPane.svelte +74 -55
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
- package/dist/brillouin/BrillouinZoneScene.svelte +277 -165
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
- package/dist/brillouin/compute.js +11 -6
- package/dist/chempot-diagram/ChemPotDiagram.svelte +327 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +847 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3194 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +11 -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 +77 -0
- package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
- package/dist/chempot-diagram/chempot-worker.js +11 -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 +812 -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 +36 -0
- package/dist/chempot-diagram/types.d.ts +86 -0
- package/dist/chempot-diagram/types.js +28 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +9 -3
- package/dist/composition/BarChart.svelte +141 -77
- package/dist/composition/BubbleChart.svelte +107 -52
- package/dist/composition/Composition.svelte +100 -79
- package/dist/composition/Formula.svelte +108 -62
- package/dist/composition/FormulaFilter.svelte +973 -353
- package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
- package/dist/composition/PieChart.svelte +199 -99
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/composition/format.d.ts +5 -0
- package/dist/composition/format.js +20 -3
- package/dist/composition/parse.js +14 -9
- package/dist/convex-hull/ConvexHull.svelte +93 -38
- package/dist/convex-hull/ConvexHull2D.svelte +551 -393
- package/dist/convex-hull/ConvexHull3D.svelte +1303 -825
- package/dist/convex-hull/ConvexHull4D.svelte +1012 -686
- package/dist/convex-hull/ConvexHullControls.svelte +115 -28
- package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
- package/dist/convex-hull/ConvexHullStats.svelte +821 -249
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +41 -16
- package/dist/convex-hull/GasPressureControls.svelte +104 -61
- package/dist/convex-hull/StructurePopup.svelte +25 -4
- package/dist/convex-hull/TemperatureSlider.svelte +45 -25
- package/dist/convex-hull/barycentric-coords.js +13 -7
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +40 -0
- package/dist/convex-hull/gas-thermodynamics.js +17 -12
- package/dist/convex-hull/helpers.d.ts +10 -1
- package/dist/convex-hull/helpers.js +79 -38
- package/dist/convex-hull/index.d.ts +1 -0
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +163 -69
- package/dist/convex-hull/types.d.ts +12 -12
- package/dist/convex-hull/types.js +0 -12
- package/dist/coordination/CoordinationBarPlot.svelte +232 -176
- package/dist/element/BohrAtom.svelte +56 -13
- package/dist/element/ElementHeading.svelte +7 -2
- package/dist/element/ElementPhoto.svelte +15 -9
- package/dist/element/ElementStats.svelte +10 -4
- package/dist/element/ElementTile.svelte +137 -73
- package/dist/element/Nucleus.svelte +39 -11
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/feedback/ClickFeedback.svelte +16 -5
- package/dist/feedback/DragOverlay.svelte +10 -2
- package/dist/feedback/Spinner.svelte +4 -2
- package/dist/feedback/StatusMessage.svelte +8 -2
- package/dist/fermi-surface/FermiSlice.svelte +118 -88
- package/dist/fermi-surface/FermiSurface.svelte +336 -239
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
- package/dist/fermi-surface/FermiSurfaceScene.svelte +536 -343
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
- package/dist/fermi-surface/compute.js +16 -20
- package/dist/fermi-surface/parse.js +37 -33
- package/dist/fermi-surface/symmetry.js +2 -7
- package/dist/fermi-surface/types.d.ts +3 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1527 -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 +111 -0
- package/dist/icons.js +158 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.d.ts +3 -0
- package/dist/io/export.js +138 -140
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/is-binary.js +2 -3
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +117 -0
- package/dist/isosurface/Isosurface.svelte +220 -110
- package/dist/isosurface/IsosurfaceControls.svelte +65 -28
- package/dist/isosurface/parse.js +104 -56
- package/dist/isosurface/slice.d.ts +2 -1
- package/dist/isosurface/slice.js +8 -13
- package/dist/isosurface/types.d.ts +14 -1
- package/dist/isosurface/types.js +152 -5
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +12 -8
- package/dist/layout/FullscreenToggle.svelte +11 -2
- package/dist/layout/InfoCard.svelte +38 -6
- package/dist/layout/InfoTag.svelte +125 -94
- package/dist/layout/PropertyFilter.svelte +82 -37
- package/dist/layout/SettingsSection.svelte +85 -55
- package/dist/layout/SubpageGrid.svelte +82 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +266 -223
- package/dist/layout/json-tree/JsonTree.svelte +516 -429
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +281 -173
- package/dist/layout/json-tree/types.d.ts +10 -2
- package/dist/layout/json-tree/utils.d.ts +2 -0
- package/dist/layout/json-tree/utils.js +37 -2
- package/dist/marching-cubes.js +25 -2
- package/dist/math.d.ts +20 -17
- package/dist/math.js +474 -57
- package/dist/overlays/ContextMenu.svelte +66 -40
- package/dist/overlays/DraggablePane.svelte +331 -154
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/periodic-table/PeriodicTable.svelte +278 -145
- package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
- package/dist/periodic-table/PropertySelect.svelte +25 -7
- package/dist/periodic-table/TableInset.svelte +8 -3
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +559 -267
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +131 -51
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +160 -110
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +8 -1
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +217 -86
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
- package/dist/phase-diagram/build-diagram.js +9 -9
- package/dist/phase-diagram/colors.js +1 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/parse.js +10 -9
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +869 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +8 -4
- package/dist/phase-diagram/utils.js +219 -74
- package/dist/plot/AxisLabel.svelte +51 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +1461 -768
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +33 -6
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +533 -383
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ColorScaleSelect.svelte +28 -7
- package/dist/plot/ElementScatter.svelte +38 -16
- package/dist/plot/FillArea.svelte +152 -92
- package/dist/plot/Histogram.svelte +1162 -709
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte +81 -18
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte +34 -11
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/Line.svelte +63 -28
- package/dist/plot/PlotControls.svelte +221 -96
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +174 -91
- package/dist/plot/PlotTooltip.svelte +45 -6
- package/dist/plot/PortalSelect.svelte +175 -146
- package/dist/plot/ReferenceLine.svelte +77 -22
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferenceLine3D.svelte +132 -107
- package/dist/plot/ReferencePlane.svelte +146 -123
- package/dist/plot/ScatterPlot.svelte +1880 -1156
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte +256 -131
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +300 -297
- package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
- package/dist/plot/ScatterPlot3DScene.svelte +608 -406
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +150 -70
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ScatterPoint.svelte +98 -26
- package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
- package/dist/plot/SpacegroupBarPlot.svelte +142 -85
- package/dist/plot/Surface3D.svelte +159 -108
- package/dist/plot/ZeroLines.svelte +96 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +23 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/axis-utils.js +1 -3
- package/dist/plot/data-cleaning.js +12 -28
- package/dist/plot/data-transform.js +2 -1
- package/dist/plot/fill-utils.js +2 -0
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +2 -3
- package/dist/plot/layout.d.ts +11 -2
- package/dist/plot/layout.js +44 -17
- package/dist/plot/reference-line.d.ts +5 -22
- package/dist/plot/reference-line.js +12 -84
- package/dist/plot/scales.js +24 -36
- package/dist/plot/types.d.ts +53 -40
- package/dist/plot/types.js +12 -7
- package/dist/plot/utils/label-placement.d.ts +32 -15
- package/dist/plot/utils/label-placement.js +227 -63
- package/dist/plot/utils/series-visibility.js +2 -3
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +173 -132
- package/dist/rdf/calc-rdf.js +4 -5
- package/dist/sanitize.d.ts +4 -0
- package/dist/sanitize.js +107 -0
- package/dist/settings.d.ts +21 -6
- package/dist/settings.js +63 -19
- package/dist/spectral/Bands.svelte +963 -412
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/BandsAndDos.svelte +90 -49
- package/dist/spectral/BrillouinBandsDos.svelte +151 -93
- package/dist/spectral/Dos.svelte +389 -258
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +119 -51
- package/dist/spectral/types.d.ts +2 -0
- package/dist/state.svelte.d.ts +1 -1
- package/dist/state.svelte.js +3 -2
- package/dist/structure/Arrow.svelte +59 -20
- package/dist/structure/AtomLegend.svelte +231 -129
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Bond.svelte +73 -47
- package/dist/structure/CanvasTooltip.svelte +10 -2
- package/dist/structure/CellSelect.svelte +148 -51
- package/dist/structure/Cylinder.svelte +33 -17
- package/dist/structure/Lattice.svelte +88 -33
- package/dist/structure/Structure.svelte +1077 -821
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureControls.svelte +373 -139
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +124 -89
- package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +304 -231
- package/dist/structure/StructureScene.svelte +919 -445
- package/dist/structure/StructureScene.svelte.d.ts +16 -7
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +42 -29
- package/dist/structure/bonding.js +6 -7
- package/dist/structure/export.js +22 -34
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +2 -3
- package/dist/structure/index.d.ts +16 -0
- package/dist/structure/index.js +88 -6
- package/dist/structure/measure.d.ts +2 -2
- package/dist/structure/measure.js +4 -44
- package/dist/structure/parse.js +130 -155
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +99 -0
- package/dist/structure/pbc.d.ts +1 -0
- package/dist/structure/pbc.js +16 -6
- package/dist/structure/supercell.d.ts +2 -2
- package/dist/structure/supercell.js +12 -22
- package/dist/structure/validation.js +5 -3
- package/dist/symmetry/SymmetryStats.svelte +94 -37
- package/dist/symmetry/WyckoffTable.svelte +42 -14
- package/dist/symmetry/cell-transform.js +5 -3
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +87 -21
- package/dist/symmetry/spacegroups.js +148 -148
- package/dist/table/HeatmapTable.svelte +1112 -516
- package/dist/table/HeatmapTable.svelte.d.ts +12 -1
- package/dist/table/ToggleMenu.svelte +125 -90
- package/dist/table/index.d.ts +2 -0
- package/dist/table/index.js +2 -4
- package/dist/theme/ThemeControl.svelte +21 -12
- package/dist/time.js +4 -1
- package/dist/tooltip/TooltipContent.svelte +33 -8
- package/dist/trajectory/Trajectory.svelte +889 -687
- package/dist/trajectory/TrajectoryError.svelte +14 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +148 -90
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +13 -31
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +332 -0
- package/dist/trajectory/helpers.d.ts +14 -0
- package/dist/trajectory/helpers.js +172 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +23 -14
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +77 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +129 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +299 -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 +68 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +110 -0
- package/dist/trajectory/plotting.js +13 -8
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +17 -0
- package/dist/xrd/XrdPlot.svelte +337 -245
- package/dist/xrd/broadening.js +14 -9
- package/dist/xrd/calc-xrd.js +12 -19
- package/dist/xrd/parse.d.ts +1 -1
- package/dist/xrd/parse.js +17 -17
- package/package.json +103 -101
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
- /package/dist/theme/{themes.js → themes.mjs} +0 -0
|
@@ -1,567 +1,1024 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { format_value } from '../labels'
|
|
3
|
+
import { FullscreenToggle, set_fullscreen_bg } from '../layout'
|
|
4
|
+
import type {
|
|
5
|
+
AxisLoadError,
|
|
6
|
+
BarStyle,
|
|
7
|
+
DataLoaderFn,
|
|
8
|
+
HistogramHandlerProps,
|
|
9
|
+
PanConfig,
|
|
10
|
+
RefLine,
|
|
11
|
+
RefLineEvent,
|
|
12
|
+
} from './'
|
|
13
|
+
import {
|
|
14
|
+
AxisLabel,
|
|
15
|
+
compute_element_placement,
|
|
16
|
+
HistogramControls,
|
|
17
|
+
PlotLegend,
|
|
18
|
+
ReferenceLine,
|
|
19
|
+
} from './'
|
|
20
|
+
import type { AxisChangeState } from './axis-utils'
|
|
21
|
+
import { create_axis_change_handler } from './axis-utils'
|
|
22
|
+
import { extract_series_color, prepare_legend_data } from './data-transform'
|
|
23
|
+
import { AXIS_DEFAULTS } from './defaults'
|
|
24
|
+
import {
|
|
25
|
+
create_dimension_tracker,
|
|
26
|
+
create_hover_lock,
|
|
27
|
+
} from './hover-lock.svelte'
|
|
28
|
+
import {
|
|
29
|
+
get_relative_coords,
|
|
30
|
+
pan_range,
|
|
31
|
+
PINCH_ZOOM_THRESHOLD,
|
|
32
|
+
pixels_to_data_delta,
|
|
33
|
+
} from './interactions'
|
|
34
|
+
import {
|
|
35
|
+
calc_auto_padding,
|
|
36
|
+
constrain_tooltip_position,
|
|
37
|
+
filter_padding,
|
|
38
|
+
LABEL_GAP_DEFAULT,
|
|
39
|
+
measure_max_tick_width,
|
|
40
|
+
} from './layout'
|
|
41
|
+
import type { IndexedRefLine } from './reference-line'
|
|
42
|
+
import { group_ref_lines_by_z, index_ref_lines } from './reference-line'
|
|
43
|
+
import {
|
|
44
|
+
create_scale,
|
|
45
|
+
generate_ticks,
|
|
46
|
+
get_nice_data_range,
|
|
47
|
+
get_tick_label,
|
|
48
|
+
} from './scales'
|
|
49
|
+
import type {
|
|
50
|
+
BasePlotProps,
|
|
51
|
+
DataSeries,
|
|
52
|
+
InitialRanges,
|
|
53
|
+
LegendConfig,
|
|
54
|
+
PlotConfig,
|
|
55
|
+
ScaleType,
|
|
56
|
+
} from './types'
|
|
57
|
+
import { get_scale_type_name } from './types'
|
|
58
|
+
import ZeroLines from './ZeroLines.svelte'
|
|
59
|
+
import ZoomRect from './ZoomRect.svelte'
|
|
60
|
+
import { DEFAULTS } from '../settings'
|
|
61
|
+
import { bin, max } from 'd3-array'
|
|
62
|
+
import type { Snippet } from 'svelte'
|
|
63
|
+
import { untrack } from 'svelte'
|
|
64
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
65
|
+
import { Tween } from 'svelte/motion'
|
|
66
|
+
import type { Vec2 } from '../math'
|
|
67
|
+
import PlotTooltip from './PlotTooltip.svelte'
|
|
68
|
+
import { bar_path } from './svg'
|
|
69
|
+
|
|
70
|
+
let {
|
|
71
|
+
series = $bindable([]),
|
|
72
|
+
x_axis: x_axis_init = {},
|
|
73
|
+
x2_axis: x2_axis_init = {},
|
|
74
|
+
y_axis: y_axis_init = {},
|
|
75
|
+
y2_axis: y2_axis_init = {},
|
|
76
|
+
display: display_init = DEFAULTS.histogram.display,
|
|
77
|
+
x_range = [null, null],
|
|
78
|
+
x2_range = [null, null],
|
|
79
|
+
y_range = [null, null],
|
|
80
|
+
y2_range = [null, null],
|
|
81
|
+
range_padding = 0.05,
|
|
82
|
+
padding = { t: 20, b: 60, l: 60, r: 20 },
|
|
83
|
+
bins = $bindable(100),
|
|
84
|
+
show_legend = $bindable(true),
|
|
85
|
+
legend = {},
|
|
86
|
+
bar: bar_init = {},
|
|
87
|
+
selected_property = $bindable(``),
|
|
88
|
+
mode = $bindable(`single`),
|
|
89
|
+
tooltip,
|
|
90
|
+
hovered = $bindable(false),
|
|
91
|
+
change = () => {},
|
|
92
|
+
on_bar_click,
|
|
93
|
+
on_bar_hover,
|
|
94
|
+
ref_lines = $bindable([]),
|
|
95
|
+
on_ref_line_click,
|
|
96
|
+
on_ref_line_hover,
|
|
97
|
+
show_controls = $bindable(true),
|
|
98
|
+
controls_open = $bindable(false),
|
|
99
|
+
on_series_toggle = () => {},
|
|
100
|
+
controls_toggle_props,
|
|
101
|
+
controls_pane_props,
|
|
102
|
+
fullscreen = $bindable(false),
|
|
103
|
+
fullscreen_toggle = true,
|
|
104
|
+
children,
|
|
105
|
+
header_controls,
|
|
106
|
+
controls_extra,
|
|
107
|
+
data_loader,
|
|
108
|
+
on_axis_change,
|
|
109
|
+
on_error,
|
|
110
|
+
pan = {},
|
|
111
|
+
...rest
|
|
112
|
+
}: HTMLAttributes<HTMLDivElement> & BasePlotProps & PlotConfig & {
|
|
113
|
+
series: DataSeries[]
|
|
114
|
+
// Component-specific props
|
|
115
|
+
bins?: number
|
|
116
|
+
show_legend?: boolean
|
|
117
|
+
legend?: LegendConfig | null
|
|
118
|
+
bar?: BarStyle
|
|
119
|
+
selected_property?: string
|
|
120
|
+
mode?: `single` | `overlay`
|
|
121
|
+
tooltip?: Snippet<[HistogramHandlerProps]>
|
|
122
|
+
header_controls?: Snippet<
|
|
123
|
+
[{ height: number; width: number; fullscreen: boolean }]
|
|
124
|
+
>
|
|
125
|
+
controls_extra?: Snippet<[Required<PlotConfig>]>
|
|
126
|
+
change?: (data: { value: number; count: number; property: string } | null) => void
|
|
127
|
+
on_bar_click?: (
|
|
128
|
+
data: {
|
|
129
|
+
value: number
|
|
130
|
+
count: number
|
|
131
|
+
property: string
|
|
132
|
+
event: MouseEvent | KeyboardEvent
|
|
133
|
+
},
|
|
134
|
+
) => void
|
|
135
|
+
on_bar_hover?: (
|
|
136
|
+
data:
|
|
137
|
+
| { value: number; count: number; property: string; event: MouseEvent }
|
|
138
|
+
| null,
|
|
139
|
+
) => void
|
|
140
|
+
ref_lines?: RefLine[]
|
|
141
|
+
on_ref_line_click?: (event: RefLineEvent) => void
|
|
142
|
+
on_ref_line_hover?: (event: RefLineEvent | null) => void
|
|
143
|
+
on_series_toggle?: (series_idx: number) => void
|
|
144
|
+
// Interactive axis props
|
|
145
|
+
data_loader?: DataLoaderFn
|
|
146
|
+
on_axis_change?: (
|
|
147
|
+
axis: `x` | `x2` | `y` | `y2`,
|
|
148
|
+
key: string,
|
|
149
|
+
new_series: DataSeries[],
|
|
150
|
+
) => void
|
|
151
|
+
on_error?: (error: AxisLoadError) => void
|
|
152
|
+
pan?: PanConfig
|
|
153
|
+
} = $props()
|
|
154
|
+
|
|
155
|
+
// Local state for controls (initialized from props, owned by this component)
|
|
156
|
+
// Include key AXIS_DEFAULTS props (range, ticks, scale_type) that PlotControls needs
|
|
157
|
+
// Using $state because these have bindings in HistogramControls/PlotControls
|
|
158
|
+
// untrack() explicitly captures initial prop values (intentional - props provide initial config)
|
|
159
|
+
const { format: _, ...axis_state_defaults } = AXIS_DEFAULTS // Exclude format (has component-specific default)
|
|
160
|
+
let bar = $state(untrack(() => ({ ...DEFAULTS.histogram.bar, ...bar_init })))
|
|
161
|
+
let x_axis = $state(untrack(() => ({ ...axis_state_defaults, ...x_axis_init })))
|
|
162
|
+
// x2-axis needs different default label_shift for top-side positioning
|
|
163
|
+
let x2_axis = $state(untrack(() => ({
|
|
164
|
+
...axis_state_defaults,
|
|
165
|
+
label_shift: { x: 0, y: 40 },
|
|
166
|
+
...x2_axis_init,
|
|
167
|
+
})))
|
|
168
|
+
let y_axis = $state(untrack(() => ({ ...axis_state_defaults, ...y_axis_init })))
|
|
169
|
+
// y2-axis needs different default label_shift for right-side positioning
|
|
170
|
+
let y2_axis = $state(untrack(() => ({
|
|
30
171
|
...axis_state_defaults,
|
|
31
172
|
label_shift: { x: 0, y: 60 },
|
|
32
173
|
...y2_axis_init,
|
|
33
|
-
})))
|
|
34
|
-
let display = $state(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
let
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
let
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
let
|
|
75
|
-
|
|
76
|
-
|
|
174
|
+
})))
|
|
175
|
+
let display = $state(
|
|
176
|
+
untrack(() => ({ ...DEFAULTS.histogram.display, ...display_init })),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// Merge component-specific defaults with local state (format comes from here, not AXIS_DEFAULTS)
|
|
180
|
+
const final_x_axis = $derived({ label: `Value`, format: `.2~s`, ...x_axis })
|
|
181
|
+
const final_x2_axis = $derived({ label: `Value`, format: `.2~s`, ...x2_axis })
|
|
182
|
+
const final_y_axis = $derived({ label: `Count`, format: `d`, ...y_axis })
|
|
183
|
+
const final_bar = $derived({ ...DEFAULTS.histogram.bar, ...bar })
|
|
184
|
+
const final_y2_axis = $derived({ label: `Count`, format: `d`, ...y2_axis })
|
|
185
|
+
|
|
186
|
+
// Core state
|
|
187
|
+
let [width, height] = $state([0, 0])
|
|
188
|
+
let wrapper: HTMLDivElement | undefined = $state()
|
|
189
|
+
let svg_element: SVGElement | null = $state(null)
|
|
190
|
+
let clip_path_id = `histogram-clip-${crypto?.randomUUID?.()}`
|
|
191
|
+
let hover_info = $state<HistogramHandlerProps | null>(null)
|
|
192
|
+
|
|
193
|
+
// Reference line hover state
|
|
194
|
+
let hovered_ref_line_idx = $state<number | null>(null)
|
|
195
|
+
|
|
196
|
+
// Interactive axis loading state
|
|
197
|
+
let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
|
|
198
|
+
|
|
199
|
+
// Compute ref_lines with index and group by z-index (using shared utilities)
|
|
200
|
+
let indexed_ref_lines = $derived(index_ref_lines(ref_lines))
|
|
201
|
+
let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines))
|
|
202
|
+
let tooltip_el = $state<HTMLDivElement | undefined>()
|
|
203
|
+
let drag_state = $state<{
|
|
204
|
+
start: { x: number; y: number } | null
|
|
205
|
+
current: { x: number; y: number } | null
|
|
206
|
+
bounds: DOMRect | null
|
|
207
|
+
}>({ start: null, current: null, bounds: null })
|
|
208
|
+
|
|
209
|
+
// Pan state
|
|
210
|
+
let is_focused = $state(false)
|
|
211
|
+
let shift_held = $state(false)
|
|
212
|
+
let pan_drag_state = $state<
|
|
213
|
+
InitialRanges & { start: { x: number; y: number } } | null
|
|
214
|
+
>(null)
|
|
215
|
+
let touch_state = $state<
|
|
216
|
+
InitialRanges & { start_touches: { x: number; y: number }[] } | null
|
|
217
|
+
>(null)
|
|
218
|
+
|
|
219
|
+
// Legend placement stability state
|
|
220
|
+
let legend_element = $state<HTMLDivElement | undefined>()
|
|
221
|
+
const legend_hover = create_hover_lock()
|
|
222
|
+
const dim_tracker = create_dimension_tracker()
|
|
223
|
+
let has_initial_legend_placement = $state(false)
|
|
224
|
+
|
|
225
|
+
// Clear pending hover lock timeout on unmount
|
|
226
|
+
$effect(() => () => legend_hover.cleanup())
|
|
227
|
+
|
|
228
|
+
// Derived data
|
|
229
|
+
let selected_series = $derived(
|
|
230
|
+
mode === `single` && selected_property
|
|
231
|
+
? series.filter((srs: DataSeries) =>
|
|
232
|
+
(srs.visible ?? true) && srs.label === selected_property
|
|
233
|
+
)
|
|
234
|
+
: series.filter((srs: DataSeries) => srs.visible ?? true),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
// Separate series by y-axis
|
|
238
|
+
let y1_series = $derived(
|
|
239
|
+
selected_series.filter((srs: DataSeries) => (srs.y_axis ?? `y1`) === `y1`),
|
|
240
|
+
)
|
|
241
|
+
let y2_series = $derived(
|
|
242
|
+
selected_series.filter((srs: DataSeries) => srs.y_axis === `y2`),
|
|
243
|
+
)
|
|
244
|
+
let x2_series = $derived(
|
|
245
|
+
selected_series.filter((srs: DataSeries) => srs.x_axis === `x2`),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
let auto_ranges = $derived.by(() => {
|
|
249
|
+
const all_values = selected_series.flatMap((srs: DataSeries) => srs.y)
|
|
250
|
+
const auto_x = get_nice_data_range(
|
|
251
|
+
all_values.map((val) => ({ x: val, y: 0 })),
|
|
252
|
+
({ x }) => x,
|
|
253
|
+
x_range,
|
|
254
|
+
final_x_axis.scale_type ?? `linear`,
|
|
255
|
+
range_padding,
|
|
256
|
+
false,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const x2_values = x2_series.flatMap((srs: DataSeries) => srs.y)
|
|
260
|
+
const auto_x2 = x2_values.length > 0
|
|
261
|
+
? get_nice_data_range(
|
|
262
|
+
x2_values.map((val) => ({ x: val, y: 0 })),
|
|
263
|
+
({ x }) => x,
|
|
264
|
+
x2_range,
|
|
265
|
+
final_x2_axis.scale_type ?? `linear`,
|
|
266
|
+
range_padding,
|
|
267
|
+
false,
|
|
268
|
+
)
|
|
269
|
+
: [0, 1] as Vec2
|
|
270
|
+
|
|
77
271
|
// Calculate y-range for a specific set of series
|
|
78
|
-
const calc_y_range = (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
272
|
+
const calc_y_range = (
|
|
273
|
+
series_list: typeof selected_series,
|
|
274
|
+
y_limit: typeof y_range,
|
|
275
|
+
scale_type: ScaleType,
|
|
276
|
+
): Vec2 => {
|
|
277
|
+
const type_name = get_scale_type_name(scale_type)
|
|
278
|
+
if (!series_list.length) {
|
|
279
|
+
const fallback = type_name === `log` ? 1 : 0
|
|
280
|
+
return [fallback, 1]
|
|
281
|
+
}
|
|
282
|
+
const hist = bin().domain([auto_x[0], auto_x[1]]).thresholds(bins)
|
|
283
|
+
const max_count = Math.max(
|
|
284
|
+
0,
|
|
285
|
+
...series_list.map((srs: DataSeries) =>
|
|
286
|
+
max(hist(srs.y), (data) => data.length) || 0
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
// If there's effectively no data, avoid log-range issues (counts can't be <= 0 on log)
|
|
291
|
+
if (max_count <= 0) {
|
|
292
|
+
const fallback = type_name === `log` ? 1 : 0
|
|
293
|
+
return [fallback, 1]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const [y0, y1] = get_nice_data_range(
|
|
297
|
+
[{ x: 0, y: 0 }, { x: max_count, y: 0 }],
|
|
298
|
+
({ x }) => x,
|
|
299
|
+
y_limit,
|
|
300
|
+
scale_type,
|
|
301
|
+
range_padding,
|
|
302
|
+
false,
|
|
303
|
+
)
|
|
304
|
+
// For log scale, minimum must be >= 1 (count can't be 0 on log)
|
|
305
|
+
// For linear/arcsinh, start from 0
|
|
306
|
+
const y_min = type_name === `log` ? Math.max(1, y0) : Math.max(0, y0)
|
|
307
|
+
return [y_min, y1]
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const y1_range = calc_y_range(
|
|
311
|
+
y1_series,
|
|
312
|
+
y_range,
|
|
313
|
+
final_y_axis.scale_type ?? `linear`,
|
|
314
|
+
)
|
|
315
|
+
const y2_auto_range = calc_y_range(
|
|
316
|
+
y2_series,
|
|
317
|
+
y2_range,
|
|
318
|
+
final_y2_axis.scale_type ?? `linear`,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return { x: auto_x, x2: auto_x2, y: y1_range, y2: y2_auto_range }
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// Initialize ranges
|
|
325
|
+
let ranges = $state({
|
|
103
326
|
initial: {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
327
|
+
x: [0, 1] as Vec2,
|
|
328
|
+
x2: [0, 1] as Vec2,
|
|
329
|
+
y: [0, 1] as Vec2,
|
|
330
|
+
y2: [0, 1] as Vec2,
|
|
107
331
|
},
|
|
108
332
|
current: {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
333
|
+
x: [0, 1] as Vec2,
|
|
334
|
+
x2: [0, 1] as Vec2,
|
|
335
|
+
y: [0, 1] as Vec2,
|
|
336
|
+
y2: [0, 1] as Vec2,
|
|
112
337
|
},
|
|
113
|
-
})
|
|
114
|
-
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
$effect(() => {
|
|
115
341
|
// Support one-sided range pinning: merge user range with auto range for null values
|
|
116
|
-
const new_x = final_x_axis.range
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
342
|
+
const new_x: [number, number] = final_x_axis.range
|
|
343
|
+
? [
|
|
344
|
+
final_x_axis.range[0] ?? auto_ranges.x[0],
|
|
345
|
+
final_x_axis.range[1] ?? auto_ranges.x[1],
|
|
346
|
+
]
|
|
347
|
+
: auto_ranges.x
|
|
348
|
+
const new_x2: [number, number] = final_x2_axis.range
|
|
349
|
+
? [
|
|
350
|
+
final_x2_axis.range[0] ?? auto_ranges.x2[0],
|
|
351
|
+
final_x2_axis.range[1] ?? auto_ranges.x2[1],
|
|
352
|
+
]
|
|
353
|
+
: auto_ranges.x2
|
|
354
|
+
const new_y: [number, number] = final_y_axis.range
|
|
355
|
+
? [
|
|
356
|
+
final_y_axis.range[0] ?? auto_ranges.y[0],
|
|
357
|
+
final_y_axis.range[1] ?? auto_ranges.y[1],
|
|
358
|
+
]
|
|
359
|
+
: auto_ranges.y
|
|
360
|
+
const new_y2: [number, number] = final_y2_axis.range
|
|
361
|
+
? [
|
|
362
|
+
final_y2_axis.range[0] ?? auto_ranges.y2[0],
|
|
363
|
+
final_y2_axis.range[1] ?? auto_ranges.y2[1],
|
|
364
|
+
]
|
|
365
|
+
: auto_ranges.y2
|
|
366
|
+
|
|
134
367
|
// Only update if the initial (data-driven) ranges changed, not when user pans
|
|
135
368
|
// Comparing against initial preserves user's pan/zoom state
|
|
136
369
|
const x_changed = new_x[0] !== ranges.initial.x[0] ||
|
|
137
|
-
|
|
370
|
+
new_x[1] !== ranges.initial.x[1]
|
|
371
|
+
const x2_changed = new_x2[0] !== ranges.initial.x2[0] ||
|
|
372
|
+
new_x2[1] !== ranges.initial.x2[1]
|
|
138
373
|
const y_changed = new_y[0] !== ranges.initial.y[0] ||
|
|
139
|
-
|
|
374
|
+
new_y[1] !== ranges.initial.y[1]
|
|
140
375
|
const y2_changed = new_y2[0] !== ranges.initial.y2[0] ||
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
if (y2_changed)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Layout: dynamic padding based on tick label widths
|
|
150
|
-
const default_padding = { t: 20, b: 60, l: 60, r: 20 }
|
|
151
|
-
let pad = $derived(filter_padding(padding, default_padding))
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
376
|
+
new_y2[1] !== ranges.initial.y2[1]
|
|
377
|
+
|
|
378
|
+
if (x_changed) [ranges.initial.x, ranges.current.x] = [new_x, new_x]
|
|
379
|
+
if (x2_changed) [ranges.initial.x2, ranges.current.x2] = [new_x2, new_x2]
|
|
380
|
+
if (y_changed) [ranges.initial.y, ranges.current.y] = [new_y, new_y]
|
|
381
|
+
if (y2_changed) [ranges.initial.y2, ranges.current.y2] = [new_y2, new_y2]
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
// Layout: dynamic padding based on tick label widths
|
|
385
|
+
const default_padding = { t: 20, b: 60, l: 60, r: 20 }
|
|
386
|
+
let pad = $derived(filter_padding(padding, default_padding))
|
|
387
|
+
|
|
388
|
+
// Update padding based on tick label widths (untrack breaks circular dependency)
|
|
389
|
+
$effect(() => {
|
|
390
|
+
const current_ticks_x2 = untrack(() => ticks.x2)
|
|
391
|
+
const current_ticks_y = untrack(() => ticks.y)
|
|
392
|
+
const current_ticks_y2 = untrack(() => ticks.y2)
|
|
393
|
+
|
|
156
394
|
const new_pad = width && height && current_ticks_y.length
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
395
|
+
? calc_auto_padding({
|
|
396
|
+
padding,
|
|
397
|
+
default_padding,
|
|
398
|
+
x2_axis: { ...final_x2_axis, tick_values: current_ticks_x2 },
|
|
399
|
+
y_axis: { ...final_y_axis, tick_values: current_ticks_y },
|
|
400
|
+
y2_axis: { ...final_y2_axis, tick_values: current_ticks_y2 },
|
|
401
|
+
})
|
|
402
|
+
: filter_padding(padding, default_padding)
|
|
403
|
+
|
|
164
404
|
// Add y2 axis label space (calc_auto_padding only accounts for tick labels)
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
405
|
+
if (
|
|
406
|
+
width && height && y2_series.length && current_ticks_y2.length &&
|
|
407
|
+
final_y2_axis.label
|
|
408
|
+
) {
|
|
409
|
+
const inside = final_y2_axis.tick?.label?.inside ?? false
|
|
410
|
+
// When ticks are inside, they don't contribute to padding
|
|
411
|
+
const tick_shift = inside ? 0 : (final_y2_axis.tick?.label?.shift?.x ?? 0) + 8
|
|
412
|
+
const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max
|
|
413
|
+
const label_thickness = Math.round(12 * 1.2)
|
|
414
|
+
new_pad.r = Math.max(
|
|
415
|
+
new_pad.r,
|
|
416
|
+
tick_width_contribution + LABEL_GAP_DEFAULT + tick_shift + label_thickness,
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Add x2 axis label space (mirroring y2 logic for top padding)
|
|
421
|
+
if (
|
|
422
|
+
width && height && x2_series.length && current_ticks_x2.length &&
|
|
423
|
+
final_x2_axis.label
|
|
424
|
+
) {
|
|
425
|
+
const inside = final_x2_axis.tick?.label?.inside ?? false
|
|
426
|
+
const tick_shift = inside
|
|
427
|
+
? 0
|
|
428
|
+
: Math.abs(final_x2_axis.tick?.label?.shift?.y ?? 0) + 8
|
|
429
|
+
const label_thickness = Math.round(12 * 1.2)
|
|
430
|
+
new_pad.t = Math.max(
|
|
431
|
+
new_pad.t,
|
|
432
|
+
tick_shift + LABEL_GAP_DEFAULT + label_thickness,
|
|
433
|
+
)
|
|
174
434
|
}
|
|
435
|
+
|
|
175
436
|
// Only update if padding actually changed
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
437
|
+
if (
|
|
438
|
+
pad.t !== new_pad.t || pad.b !== new_pad.b || pad.l !== new_pad.l ||
|
|
439
|
+
pad.r !== new_pad.r
|
|
440
|
+
) pad = new_pad
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// Scales and data
|
|
444
|
+
let scales = $derived({
|
|
445
|
+
x: create_scale(
|
|
446
|
+
final_x_axis.scale_type ?? `linear`,
|
|
447
|
+
ranges.current.x,
|
|
448
|
+
[pad.l, width - pad.r],
|
|
449
|
+
),
|
|
450
|
+
x2: create_scale(
|
|
451
|
+
final_x2_axis.scale_type ?? `linear`,
|
|
452
|
+
ranges.current.x2,
|
|
453
|
+
[pad.l, width - pad.r],
|
|
454
|
+
),
|
|
455
|
+
y: create_scale(
|
|
456
|
+
final_y_axis.scale_type ?? `linear`,
|
|
457
|
+
ranges.current.y,
|
|
458
|
+
[height - pad.b, pad.t],
|
|
459
|
+
),
|
|
460
|
+
y2: create_scale(
|
|
461
|
+
final_y2_axis.scale_type ?? `linear`,
|
|
462
|
+
ranges.current.y2,
|
|
463
|
+
[height - pad.b, pad.t],
|
|
464
|
+
),
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
let histogram_data = $derived.by(() => {
|
|
468
|
+
if (!selected_series.length || !width || !height) return []
|
|
189
469
|
const hist_generator = bin()
|
|
190
|
-
|
|
191
|
-
|
|
470
|
+
.domain([ranges.current.x[0], ranges.current.x[1]])
|
|
471
|
+
.thresholds(bins)
|
|
472
|
+
const x2_hist_generator = x2_series.length > 0
|
|
473
|
+
? bin().domain([ranges.current.x2[0], ranges.current.x2[1]]).thresholds(bins)
|
|
474
|
+
: null
|
|
192
475
|
return selected_series.map((series_data, series_idx) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
476
|
+
const use_x2 = series_data.x_axis === `x2`
|
|
477
|
+
const active_hist = use_x2 && x2_hist_generator
|
|
478
|
+
? x2_hist_generator
|
|
479
|
+
: hist_generator
|
|
480
|
+
const bins_arr = active_hist(series_data.y)
|
|
481
|
+
const use_y2 = series_data.y_axis === `y2`
|
|
482
|
+
return {
|
|
483
|
+
id: series_data.id ?? series_idx,
|
|
484
|
+
series_idx,
|
|
485
|
+
label: series_data.label || `Series ${series_idx + 1}`,
|
|
486
|
+
color: selected_series.length === 1
|
|
487
|
+
? final_bar.color
|
|
488
|
+
: extract_series_color(series_data),
|
|
489
|
+
bins: bins_arr,
|
|
490
|
+
max_count: max(bins_arr, (data) => data.length) || 0,
|
|
491
|
+
x_axis: series_data.x_axis,
|
|
492
|
+
y_axis: series_data.y_axis,
|
|
493
|
+
x_scale: use_x2 ? scales.x2 : scales.x,
|
|
494
|
+
y_scale: use_y2 ? scales.y2 : scales.y,
|
|
495
|
+
}
|
|
496
|
+
})
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
let ticks = $derived({
|
|
210
500
|
x: width && height
|
|
211
|
-
|
|
212
|
-
|
|
501
|
+
? generate_ticks(
|
|
502
|
+
ranges.current.x,
|
|
503
|
+
final_x_axis.scale_type ?? `linear`,
|
|
504
|
+
final_x_axis.ticks,
|
|
505
|
+
scales.x,
|
|
506
|
+
{ default_count: 8 },
|
|
507
|
+
)
|
|
508
|
+
: [],
|
|
509
|
+
x2: width && height && x2_series.length > 0
|
|
510
|
+
? generate_ticks(
|
|
511
|
+
ranges.current.x2,
|
|
512
|
+
final_x2_axis.scale_type ?? `linear`,
|
|
513
|
+
final_x2_axis.ticks,
|
|
514
|
+
scales.x2,
|
|
515
|
+
{ default_count: 8 },
|
|
516
|
+
)
|
|
517
|
+
: [],
|
|
213
518
|
y: width && height
|
|
214
|
-
|
|
215
|
-
|
|
519
|
+
? generate_ticks(
|
|
520
|
+
ranges.current.y,
|
|
521
|
+
final_y_axis.scale_type ?? `linear`,
|
|
522
|
+
final_y_axis.ticks,
|
|
523
|
+
scales.y,
|
|
524
|
+
{ default_count: 6 },
|
|
525
|
+
)
|
|
526
|
+
: [],
|
|
216
527
|
y2: width && height && y2_series.length > 0
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
528
|
+
? generate_ticks(
|
|
529
|
+
ranges.current.y2,
|
|
530
|
+
final_y2_axis.scale_type ?? `linear`,
|
|
531
|
+
final_y2_axis.ticks,
|
|
532
|
+
scales.y2,
|
|
533
|
+
{ default_count: 6 },
|
|
534
|
+
)
|
|
535
|
+
: [],
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
// Cache measured tick-label widths so expensive text measurement only runs
|
|
539
|
+
// when tick values/format change, not on every template rerender.
|
|
540
|
+
let tick_label_widths = $derived({
|
|
541
|
+
x2_max: measure_max_tick_width(ticks.x2, final_x2_axis.format ?? ``),
|
|
542
|
+
y_max: measure_max_tick_width(ticks.y, final_y_axis.format ?? ``),
|
|
543
|
+
y2_max: measure_max_tick_width(ticks.y2, final_y2_axis.format ?? ``),
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
let legend_data = $derived(prepare_legend_data(series))
|
|
547
|
+
|
|
548
|
+
// Collect histogram bar positions for legend placement
|
|
549
|
+
let hist_points_for_placement = $derived.by(() => {
|
|
550
|
+
if (!width || !height || !histogram_data.length) return []
|
|
551
|
+
|
|
552
|
+
const points: { x: number; y: number }[] = []
|
|
553
|
+
|
|
554
|
+
for (const { bins, x_scale, y_scale } of histogram_data) {
|
|
555
|
+
for (const bin of bins) {
|
|
556
|
+
if (bin.length > 0) {
|
|
557
|
+
const bar_x = x_scale(((bin.x0 ?? 0) + (bin.x1 ?? 0)) / 2)
|
|
558
|
+
const bar_y = y_scale(bin.length)
|
|
559
|
+
if (isFinite(bar_x) && isFinite(bar_y)) {
|
|
560
|
+
// Add multiple points for taller bars to increase their weight
|
|
561
|
+
// Cap to prevent O(N·count/10) blow-ups for large counts
|
|
562
|
+
const weight = Math.min(20, Math.ceil(bin.length / 10))
|
|
563
|
+
for (let idx = 0; idx < weight; idx++) points.push({ x: bar_x, y: bar_y })
|
|
564
|
+
}
|
|
239
565
|
}
|
|
566
|
+
}
|
|
240
567
|
}
|
|
241
|
-
return points
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
568
|
+
return points
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
// Calculate best legend placement using continuous grid sampling
|
|
572
|
+
let legend_placement = $derived.by(() => {
|
|
573
|
+
const should_place = show_legend && legend != null && series.length > 1
|
|
574
|
+
if (!should_place || !width || !height) return null
|
|
575
|
+
|
|
576
|
+
const plot_width = width - pad.l - pad.r
|
|
577
|
+
const plot_height = height - pad.t - pad.b
|
|
578
|
+
|
|
250
579
|
// Use measured size if available, otherwise estimate
|
|
251
580
|
const legend_size = legend_element
|
|
252
|
-
|
|
253
|
-
|
|
581
|
+
? { width: legend_element.offsetWidth, height: legend_element.offsetHeight }
|
|
582
|
+
: { width: 120, height: 60 }
|
|
583
|
+
|
|
254
584
|
const result = compute_element_placement({
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
585
|
+
plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
|
|
586
|
+
element_size: legend_size,
|
|
587
|
+
axis_clearance: legend?.axis_clearance,
|
|
588
|
+
exclude_rects: [],
|
|
589
|
+
points: hist_points_for_placement,
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
return result
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
// Tweened legend coordinates for smooth animation - create once, update target via effect
|
|
596
|
+
// untrack() explicitly captures initial tween config (intentional - config set once at mount)
|
|
597
|
+
const tweened_legend_coords = new Tween(
|
|
598
|
+
{ x: 0, y: 0 },
|
|
599
|
+
untrack(() => ({ duration: 400, ...(legend?.tween ?? {}) })),
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
// Update legend position with stability checks
|
|
603
|
+
$effect(() => {
|
|
604
|
+
if (!width || !height || !legend_placement) return
|
|
605
|
+
|
|
270
606
|
// Track dimensions for resize detection
|
|
271
|
-
const dims_changed = dim_tracker.has_changed(width, height)
|
|
272
|
-
if (dims_changed)
|
|
273
|
-
|
|
607
|
+
const dims_changed = dim_tracker.has_changed(width, height)
|
|
608
|
+
if (dims_changed) dim_tracker.update(width, height)
|
|
609
|
+
|
|
274
610
|
// Only update if: resize occurred, OR (not hover-locked AND (responsive OR not yet initially placed))
|
|
275
|
-
const is_responsive = legend?.responsive ?? false
|
|
611
|
+
const is_responsive = legend?.responsive ?? false
|
|
276
612
|
const should_update = dims_changed || (!legend_hover.is_locked.current &&
|
|
277
|
-
|
|
613
|
+
(is_responsive || !has_initial_legend_placement))
|
|
614
|
+
|
|
278
615
|
if (should_update) {
|
|
279
|
-
|
|
616
|
+
tweened_legend_coords.set(
|
|
617
|
+
{ x: legend_placement.x, y: legend_placement.y },
|
|
280
618
|
// Skip animation on initial placement to avoid jump from (0, 0)
|
|
281
|
-
has_initial_legend_placement ? undefined : { duration: 0 }
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
619
|
+
has_initial_legend_placement ? undefined : { duration: 0 },
|
|
620
|
+
)
|
|
621
|
+
// Only lock position after we have actual measured size
|
|
622
|
+
if (legend_element) {
|
|
623
|
+
has_initial_legend_placement = true
|
|
624
|
+
}
|
|
286
625
|
}
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const start_x = scales.x.invert(drag_state.start.x)
|
|
293
|
-
const end_x = scales.x.invert(drag_state.current.x)
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
const
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
// Event handlers
|
|
629
|
+
const handle_zoom = () => {
|
|
630
|
+
if (!drag_state.start || !drag_state.current) return
|
|
631
|
+
const start_x = scales.x.invert(drag_state.start.x)
|
|
632
|
+
const end_x = scales.x.invert(drag_state.current.x)
|
|
633
|
+
const start_x2 = scales.x2.invert(drag_state.start.x)
|
|
634
|
+
const end_x2 = scales.x2.invert(drag_state.current.x)
|
|
635
|
+
const start_y = scales.y.invert(drag_state.start.y)
|
|
636
|
+
const end_y = scales.y.invert(drag_state.current.y)
|
|
637
|
+
const start_y2 = scales.y2.invert(drag_state.start.y)
|
|
638
|
+
const end_y2 = scales.y2.invert(drag_state.current.y)
|
|
639
|
+
|
|
298
640
|
if (typeof start_x === `number` && typeof end_x === `number`) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
};
|
|
307
|
-
y_axis = {
|
|
308
|
-
...y_axis,
|
|
309
|
-
range: [Math.min(start_y, end_y), Math.max(start_y, end_y)],
|
|
310
|
-
};
|
|
311
|
-
y2_axis = {
|
|
312
|
-
...y2_axis,
|
|
313
|
-
range: [Math.min(start_y2, end_y2), Math.max(start_y2, end_y2)],
|
|
314
|
-
};
|
|
641
|
+
const dx = Math.abs(drag_state.start.x - drag_state.current.x)
|
|
642
|
+
const dy = Math.abs(drag_state.start.y - drag_state.current.y)
|
|
643
|
+
if (dx > 5 && dy > 5) {
|
|
644
|
+
// Update axis ranges to trigger reactivity and prevent effect from overriding
|
|
645
|
+
x_axis = {
|
|
646
|
+
...x_axis,
|
|
647
|
+
range: [Math.min(start_x, end_x), Math.max(start_x, end_x)],
|
|
315
648
|
}
|
|
649
|
+
if (x2_series.length > 0) {
|
|
650
|
+
x2_axis = {
|
|
651
|
+
...x2_axis,
|
|
652
|
+
range: [Math.min(start_x2, end_x2), Math.max(start_x2, end_x2)],
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
y_axis = {
|
|
656
|
+
...y_axis,
|
|
657
|
+
range: [Math.min(start_y, end_y), Math.max(start_y, end_y)],
|
|
658
|
+
}
|
|
659
|
+
y2_axis = {
|
|
660
|
+
...y2_axis,
|
|
661
|
+
range: [Math.min(start_y2, end_y2), Math.max(start_y2, end_y2)],
|
|
662
|
+
}
|
|
663
|
+
}
|
|
316
664
|
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const on_window_mouse_move = (evt: MouseEvent) => {
|
|
668
|
+
if (!drag_state.start || !drag_state.bounds) return
|
|
321
669
|
drag_state.current = {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
window.removeEventListener(`
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
670
|
+
x: evt.clientX - drag_state.bounds.left,
|
|
671
|
+
y: evt.clientY - drag_state.bounds.top,
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const on_window_mouse_up = () => {
|
|
676
|
+
handle_zoom()
|
|
677
|
+
drag_state = { start: null, current: null, bounds: null }
|
|
678
|
+
window.removeEventListener(`mousemove`, on_window_mouse_move)
|
|
679
|
+
window.removeEventListener(`mouseup`, on_window_mouse_up)
|
|
680
|
+
document.body.style.cursor = `default`
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Pan drag handlers
|
|
684
|
+
const on_pan_move = (evt: MouseEvent) => {
|
|
685
|
+
if (!pan_drag_state) return
|
|
686
|
+
const dx = evt.clientX - pan_drag_state.start.x
|
|
687
|
+
const dy = evt.clientY - pan_drag_state.start.y
|
|
688
|
+
|
|
339
689
|
// Convert pixel delta to data delta (note: drag direction is inverted for natural pan feel)
|
|
340
|
-
const plot_width = width - pad.l - pad.r
|
|
341
|
-
const plot_height = height - pad.t - pad.b
|
|
342
|
-
const sensitivity = pan?.drag_sensitivity ?? 1
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
690
|
+
const plot_width = width - pad.l - pad.r
|
|
691
|
+
const plot_height = height - pad.t - pad.b
|
|
692
|
+
const sensitivity = pan?.drag_sensitivity ?? 1
|
|
693
|
+
|
|
694
|
+
const x_delta = pixels_to_data_delta(
|
|
695
|
+
-dx * sensitivity,
|
|
696
|
+
pan_drag_state.initial_x_range,
|
|
697
|
+
plot_width,
|
|
698
|
+
)
|
|
699
|
+
const x2_delta = pixels_to_data_delta(
|
|
700
|
+
-dx * sensitivity,
|
|
701
|
+
pan_drag_state.initial_x2_range,
|
|
702
|
+
plot_width,
|
|
703
|
+
)
|
|
704
|
+
const y_delta = pixels_to_data_delta(
|
|
705
|
+
dy * sensitivity,
|
|
706
|
+
pan_drag_state.initial_y_range,
|
|
707
|
+
plot_height,
|
|
708
|
+
)
|
|
709
|
+
const y2_delta = pixels_to_data_delta(
|
|
710
|
+
dy * sensitivity,
|
|
711
|
+
pan_drag_state.initial_y2_range,
|
|
712
|
+
plot_height,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
ranges.current.x = pan_range(pan_drag_state.initial_x_range, x_delta)
|
|
716
|
+
ranges.current.x2 = pan_range(pan_drag_state.initial_x2_range, x2_delta)
|
|
717
|
+
ranges.current.y = pan_range(pan_drag_state.initial_y_range, y_delta)
|
|
718
|
+
ranges.current.y2 = pan_range(pan_drag_state.initial_y2_range, y2_delta)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const on_pan_end = () => {
|
|
722
|
+
pan_drag_state = null
|
|
723
|
+
document.body.style.cursor = ``
|
|
724
|
+
window.removeEventListener(`mousemove`, on_pan_move)
|
|
725
|
+
window.removeEventListener(`mouseup`, on_pan_end)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function handle_mouse_down(evt: MouseEvent) {
|
|
729
|
+
const coords = get_relative_coords(evt)
|
|
730
|
+
if (!coords || !svg_element) return
|
|
731
|
+
|
|
360
732
|
// Check if pan is enabled and shift is held for pan mode
|
|
361
|
-
const pan_enabled = pan?.enabled !== false
|
|
733
|
+
const pan_enabled = pan?.enabled !== false
|
|
362
734
|
if (pan_enabled && evt.shiftKey) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
735
|
+
evt.preventDefault()
|
|
736
|
+
pan_drag_state = {
|
|
737
|
+
start: { x: evt.clientX, y: evt.clientY },
|
|
738
|
+
initial_x_range: [...ranges.current.x] as [number, number],
|
|
739
|
+
initial_x2_range: [...ranges.current.x2] as [number, number],
|
|
740
|
+
initial_y_range: [...ranges.current.y] as [number, number],
|
|
741
|
+
initial_y2_range: [...ranges.current.y2] as [number, number],
|
|
742
|
+
}
|
|
743
|
+
document.body.style.cursor = `grabbing`
|
|
744
|
+
window.addEventListener(`mousemove`, on_pan_move)
|
|
745
|
+
window.addEventListener(`mouseup`, on_pan_end)
|
|
746
|
+
return
|
|
374
747
|
}
|
|
748
|
+
|
|
375
749
|
drag_state = {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
window.addEventListener(`mousemove`, on_window_mouse_move)
|
|
381
|
-
window.addEventListener(`mouseup`, on_window_mouse_up)
|
|
382
|
-
evt.preventDefault()
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
750
|
+
start: coords,
|
|
751
|
+
current: coords,
|
|
752
|
+
bounds: svg_element.getBoundingClientRect(),
|
|
753
|
+
}
|
|
754
|
+
window.addEventListener(`mousemove`, on_window_mouse_move)
|
|
755
|
+
window.addEventListener(`mouseup`, on_window_mouse_up)
|
|
756
|
+
evt.preventDefault()
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Wheel handler for pan (requires focus and shift)
|
|
760
|
+
function handle_wheel(evt: WheelEvent) {
|
|
761
|
+
const pan_enabled = pan?.enabled !== false
|
|
387
762
|
// Only capture wheel when focused AND Shift is held
|
|
388
763
|
// Use shift_held state (tracked via keydown/keyup) for compatibility with synthetic events
|
|
389
|
-
if (!pan_enabled || !is_focused || !shift_held)
|
|
390
|
-
|
|
391
|
-
evt.preventDefault()
|
|
764
|
+
if (!pan_enabled || !is_focused || !shift_held) return
|
|
765
|
+
|
|
766
|
+
evt.preventDefault()
|
|
767
|
+
|
|
392
768
|
// Clamp to at least 1 to avoid Infinity deltas when padding equals container size
|
|
393
|
-
const plot_width = Math.max(1, width - pad.l - pad.r)
|
|
394
|
-
const plot_height = Math.max(1, height - pad.t - pad.b)
|
|
395
|
-
const sensitivity = pan?.wheel_sensitivity ?? 1
|
|
769
|
+
const plot_width = Math.max(1, width - pad.l - pad.r)
|
|
770
|
+
const plot_height = Math.max(1, height - pad.t - pad.b)
|
|
771
|
+
const sensitivity = pan?.wheel_sensitivity ?? 1
|
|
772
|
+
|
|
396
773
|
// Determine pan direction based on wheel delta
|
|
397
|
-
const x_delta = pixels_to_data_delta(
|
|
398
|
-
|
|
399
|
-
|
|
774
|
+
const x_delta = pixels_to_data_delta(
|
|
775
|
+
evt.deltaX * sensitivity,
|
|
776
|
+
ranges.current.x,
|
|
777
|
+
plot_width,
|
|
778
|
+
)
|
|
779
|
+
const x2_delta = pixels_to_data_delta(
|
|
780
|
+
evt.deltaX * sensitivity,
|
|
781
|
+
ranges.current.x2,
|
|
782
|
+
plot_width,
|
|
783
|
+
)
|
|
784
|
+
const y_delta = pixels_to_data_delta(
|
|
785
|
+
evt.deltaY * sensitivity,
|
|
786
|
+
ranges.current.y,
|
|
787
|
+
plot_height,
|
|
788
|
+
)
|
|
789
|
+
const y2_delta = pixels_to_data_delta(
|
|
790
|
+
evt.deltaY * sensitivity,
|
|
791
|
+
ranges.current.y2,
|
|
792
|
+
plot_height,
|
|
793
|
+
)
|
|
794
|
+
|
|
400
795
|
if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
|
|
401
|
-
|
|
796
|
+
ranges.current.x = pan_range(ranges.current.x, x_delta)
|
|
797
|
+
ranges.current.x2 = pan_range(ranges.current.x2, x2_delta)
|
|
798
|
+
} else {
|
|
799
|
+
ranges.current.y = pan_range(ranges.current.y, y_delta)
|
|
800
|
+
ranges.current.y2 = pan_range(ranges.current.y2, y2_delta)
|
|
402
801
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return;
|
|
413
|
-
evt.preventDefault();
|
|
414
|
-
const touches = Array.from(evt.touches);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Touch handlers for pinch-zoom and two-finger pan
|
|
805
|
+
function handle_touch_start(evt: TouchEvent) {
|
|
806
|
+
const touch_enabled = pan?.enabled !== false && pan?.touch_enabled !== false
|
|
807
|
+
if (!touch_enabled || evt.touches.length !== 2) return
|
|
808
|
+
|
|
809
|
+
evt.preventDefault()
|
|
810
|
+
const touches = Array.from(evt.touches)
|
|
415
811
|
touch_state = {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
evt.
|
|
426
|
-
|
|
427
|
-
|
|
812
|
+
start_touches: touches.map((touch) => ({ x: touch.clientX, y: touch.clientY })),
|
|
813
|
+
initial_x_range: [...ranges.current.x] as [number, number],
|
|
814
|
+
initial_x2_range: [...ranges.current.x2] as [number, number],
|
|
815
|
+
initial_y_range: [...ranges.current.y] as [number, number],
|
|
816
|
+
initial_y2_range: [...ranges.current.y2] as [number, number],
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function handle_touch_move(evt: TouchEvent) {
|
|
821
|
+
if (!touch_state || evt.touches.length !== 2) return
|
|
822
|
+
evt.preventDefault()
|
|
823
|
+
|
|
824
|
+
const [t1, t2] = Array.from(evt.touches)
|
|
825
|
+
const [s1, s2] = touch_state.start_touches
|
|
826
|
+
|
|
428
827
|
// Calculate center movement for pan
|
|
429
|
-
const start_center = { x: (s1.x + s2.x) / 2, y: (s1.y + s2.y) / 2 }
|
|
828
|
+
const start_center = { x: (s1.x + s2.x) / 2, y: (s1.y + s2.y) / 2 }
|
|
430
829
|
const curr_center = {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
const dx = curr_center.x - start_center.x
|
|
435
|
-
const dy = curr_center.y - start_center.y
|
|
830
|
+
x: (t1.clientX + t2.clientX) / 2,
|
|
831
|
+
y: (t1.clientY + t2.clientY) / 2,
|
|
832
|
+
}
|
|
833
|
+
const dx = curr_center.x - start_center.x
|
|
834
|
+
const dy = curr_center.y - start_center.y
|
|
835
|
+
|
|
436
836
|
// Calculate pinch scale (curr/start so spread = zoom out, pinch = zoom in)
|
|
437
|
-
const start_dist = Math.hypot(s2.x - s1.x, s2.y - s1.y)
|
|
837
|
+
const start_dist = Math.hypot(s2.x - s1.x, s2.y - s1.y)
|
|
438
838
|
// Guard against zero-distance pinch to avoid Infinity scale
|
|
439
|
-
if (start_dist < Number.EPSILON)
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
839
|
+
if (start_dist < Number.EPSILON) return
|
|
840
|
+
const curr_dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY)
|
|
841
|
+
const scale = curr_dist / start_dist
|
|
842
|
+
|
|
443
843
|
// Clamp to at least 1 to avoid Infinity deltas when padding equals container size
|
|
444
|
-
const plot_width = Math.max(1, width - pad.l - pad.r)
|
|
445
|
-
const plot_height = Math.max(1, height - pad.t - pad.b)
|
|
844
|
+
const plot_width = Math.max(1, width - pad.l - pad.r)
|
|
845
|
+
const plot_height = Math.max(1, height - pad.t - pad.b)
|
|
846
|
+
|
|
446
847
|
// If scale changed significantly, treat as pinch-zoom
|
|
447
848
|
// Also guard against scale being too small to avoid division by zero
|
|
448
849
|
if (Math.abs(scale - 1) > PINCH_ZOOM_THRESHOLD && scale > Number.EPSILON) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
850
|
+
// Pinch zoom centered on gesture center
|
|
851
|
+
// Divide by scale so spread (scale > 1) = smaller span (zoom in)
|
|
852
|
+
const x_span = touch_state.initial_x_range[1] - touch_state.initial_x_range[0]
|
|
853
|
+
const x2_span = touch_state.initial_x2_range[1] -
|
|
854
|
+
touch_state.initial_x2_range[0]
|
|
855
|
+
const y_span = touch_state.initial_y_range[1] - touch_state.initial_y_range[0]
|
|
856
|
+
const y2_span = touch_state.initial_y2_range[1] -
|
|
857
|
+
touch_state.initial_y2_range[0]
|
|
858
|
+
const x_center =
|
|
859
|
+
(touch_state.initial_x_range[0] + touch_state.initial_x_range[1]) / 2
|
|
860
|
+
const x2_center =
|
|
861
|
+
(touch_state.initial_x2_range[0] + touch_state.initial_x2_range[1]) / 2
|
|
862
|
+
const y_center =
|
|
863
|
+
(touch_state.initial_y_range[0] + touch_state.initial_y_range[1]) / 2
|
|
864
|
+
const y2_center =
|
|
865
|
+
(touch_state.initial_y2_range[0] + touch_state.initial_y2_range[1]) / 2
|
|
866
|
+
|
|
867
|
+
ranges.current.x = [
|
|
868
|
+
x_center - x_span / scale / 2,
|
|
869
|
+
x_center + x_span / scale / 2,
|
|
870
|
+
]
|
|
871
|
+
ranges.current.x2 = [
|
|
872
|
+
x2_center - x2_span / scale / 2,
|
|
873
|
+
x2_center + x2_span / scale / 2,
|
|
874
|
+
]
|
|
875
|
+
ranges.current.y = [
|
|
876
|
+
y_center - y_span / scale / 2,
|
|
877
|
+
y_center + y_span / scale / 2,
|
|
878
|
+
]
|
|
879
|
+
ranges.current.y2 = [
|
|
880
|
+
y2_center - y2_span / scale / 2,
|
|
881
|
+
y2_center + y2_span / scale / 2,
|
|
882
|
+
]
|
|
883
|
+
} else {
|
|
884
|
+
// Pan
|
|
885
|
+
const x_delta = pixels_to_data_delta(
|
|
886
|
+
-dx,
|
|
887
|
+
touch_state.initial_x_range,
|
|
888
|
+
plot_width,
|
|
889
|
+
)
|
|
890
|
+
const x2_delta = pixels_to_data_delta(
|
|
891
|
+
-dx,
|
|
892
|
+
touch_state.initial_x2_range,
|
|
893
|
+
plot_width,
|
|
894
|
+
)
|
|
895
|
+
const y_delta = pixels_to_data_delta(
|
|
896
|
+
dy,
|
|
897
|
+
touch_state.initial_y_range,
|
|
898
|
+
plot_height,
|
|
899
|
+
)
|
|
900
|
+
const y2_delta = pixels_to_data_delta(
|
|
901
|
+
dy,
|
|
902
|
+
touch_state.initial_y2_range,
|
|
903
|
+
plot_height,
|
|
904
|
+
)
|
|
905
|
+
ranges.current.x = pan_range(touch_state.initial_x_range, x_delta)
|
|
906
|
+
ranges.current.x2 = pan_range(touch_state.initial_x2_range, x2_delta)
|
|
907
|
+
ranges.current.y = pan_range(touch_state.initial_y_range, y_delta)
|
|
908
|
+
ranges.current.y2 = pan_range(touch_state.initial_y2_range, y2_delta)
|
|
479
909
|
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function handle_touch_end() {
|
|
913
|
+
touch_state = null
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function handle_double_click() {
|
|
485
917
|
// Reset zoom to initial ranges (undo any pan/zoom)
|
|
486
|
-
ranges.current.x = [...ranges.initial.x]
|
|
487
|
-
ranges.current.
|
|
488
|
-
ranges.current.
|
|
918
|
+
ranges.current.x = [...ranges.initial.x] as [number, number]
|
|
919
|
+
ranges.current.x2 = [...ranges.initial.x2] as [number, number]
|
|
920
|
+
ranges.current.y = [...ranges.initial.y] as [number, number]
|
|
921
|
+
ranges.current.y2 = [...ranges.initial.y2] as [number, number]
|
|
489
922
|
// Also reset axis props so future data changes recalculate auto ranges
|
|
490
|
-
x_axis = { ...x_axis, range: [null, null] }
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
923
|
+
x_axis = { ...x_axis, range: [null, null] }
|
|
924
|
+
x2_axis = { ...x2_axis, range: [null, null] }
|
|
925
|
+
y_axis = { ...y_axis, range: [null, null] }
|
|
926
|
+
y2_axis = { ...y2_axis, range: [null, null] }
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function handle_mouse_move(
|
|
930
|
+
evt: MouseEvent,
|
|
931
|
+
value: number,
|
|
932
|
+
count: number,
|
|
933
|
+
property: string,
|
|
934
|
+
active_y_axis: `y1` | `y2` = `y1`,
|
|
935
|
+
series_idx: number = 0,
|
|
936
|
+
active_x_axis: `x1` | `x2` = `x1`,
|
|
937
|
+
) {
|
|
938
|
+
hovered = true
|
|
496
939
|
hover_info = {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
940
|
+
value,
|
|
941
|
+
count,
|
|
942
|
+
property,
|
|
943
|
+
active_y_axis,
|
|
944
|
+
active_x_axis,
|
|
945
|
+
x: value,
|
|
946
|
+
y: count,
|
|
947
|
+
series_idx,
|
|
948
|
+
metadata: null,
|
|
949
|
+
label: property,
|
|
950
|
+
x_axis: active_x_axis === `x2` ? x2_axis : x_axis,
|
|
951
|
+
x2_axis,
|
|
952
|
+
y_axis: active_y_axis === `y2` ? y2_axis : y_axis,
|
|
953
|
+
y2_axis,
|
|
954
|
+
}
|
|
955
|
+
change({ value, count, property })
|
|
956
|
+
on_bar_hover?.({ value, count, property, event: evt })
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function toggle_series_visibility(series_idx: number) {
|
|
514
960
|
if (series_idx >= 0 && series_idx < series.length) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
(legend?.on_toggle || on_series_toggle)(series_idx);
|
|
961
|
+
// Toggle series visibility
|
|
962
|
+
series = series.map((srs: DataSeries, idx: number) => {
|
|
963
|
+
if (idx === series_idx) return { ...srs, visible: !(srs.visible ?? true) }
|
|
964
|
+
return srs
|
|
965
|
+
})
|
|
966
|
+
;(legend?.on_toggle || on_series_toggle)(series_idx)
|
|
522
967
|
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Set theme-aware background when entering fullscreen
|
|
971
|
+
$effect(() => {
|
|
972
|
+
set_fullscreen_bg(wrapper, fullscreen, `--histogram-fullscreen-bg`)
|
|
973
|
+
})
|
|
974
|
+
|
|
975
|
+
// State accessors for shared axis change handler
|
|
976
|
+
const axis_state: AxisChangeState<DataSeries> = {
|
|
977
|
+
get_axis: (axis) => {
|
|
978
|
+
if (axis === `x`) return x_axis
|
|
979
|
+
if (axis === `x2`) return x2_axis
|
|
980
|
+
if (axis === `y`) return y_axis
|
|
981
|
+
return y2_axis
|
|
982
|
+
},
|
|
531
983
|
set_axis: (axis, config) => {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
else
|
|
538
|
-
y2_axis = { ...y2_axis, ...config };
|
|
984
|
+
// Spread into existing state to preserve merged type structure
|
|
985
|
+
if (axis === `x`) x_axis = { ...x_axis, ...config }
|
|
986
|
+
else if (axis === `x2`) x2_axis = { ...x2_axis, ...config }
|
|
987
|
+
else if (axis === `y`) y_axis = { ...y_axis, ...config }
|
|
988
|
+
else y2_axis = { ...y2_axis, ...config }
|
|
539
989
|
},
|
|
540
990
|
get_series: () => series,
|
|
541
991
|
set_series: (new_series) => (series = new_series),
|
|
542
992
|
get_loading: () => axis_loading,
|
|
543
993
|
set_loading: (axis) => (axis_loading = axis),
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Create shared handler bound to this component's state
|
|
997
|
+
// Using $derived so handler updates when callback props change
|
|
998
|
+
const handle_axis_change = $derived(create_axis_change_handler(
|
|
999
|
+
axis_state,
|
|
1000
|
+
data_loader,
|
|
1001
|
+
on_axis_change,
|
|
1002
|
+
on_error,
|
|
1003
|
+
))
|
|
1004
|
+
|
|
1005
|
+
let auto_load_attempted = false // prevent infinite retries on failure
|
|
1006
|
+
|
|
1007
|
+
// Auto-load data if series is empty but options exist (runs once)
|
|
1008
|
+
$effect(() => {
|
|
551
1009
|
if (series.length === 0 && data_loader && !auto_load_attempted) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
1010
|
+
// Check x-axis first, then y-axis
|
|
1011
|
+
if (x_axis.options?.length) {
|
|
1012
|
+
auto_load_attempted = true
|
|
1013
|
+
const first_key = x_axis.selected_key ?? x_axis.options[0].key
|
|
1014
|
+
handle_axis_change(`x`, first_key).catch(() => {})
|
|
1015
|
+
} else if (y_axis.options?.length) {
|
|
1016
|
+
auto_load_attempted = true
|
|
1017
|
+
const first_key = y_axis.selected_key ?? y_axis.options[0].key
|
|
1018
|
+
handle_axis_change(`y`, first_key).catch(() => {})
|
|
1019
|
+
}
|
|
563
1020
|
}
|
|
564
|
-
})
|
|
1021
|
+
})
|
|
565
1022
|
</script>
|
|
566
1023
|
|
|
567
1024
|
{#snippet ref_lines_layer(lines: IndexedRefLine[])}
|
|
@@ -569,11 +1026,12 @@ $effect(() => {
|
|
|
569
1026
|
<ReferenceLine
|
|
570
1027
|
ref_line={line}
|
|
571
1028
|
line_idx={line.idx}
|
|
572
|
-
x_min={ranges.current.x[0]}
|
|
573
|
-
x_max={ranges.current.x[1]}
|
|
1029
|
+
x_min={line.x_axis === `x2` ? ranges.current.x2[0] : ranges.current.x[0]}
|
|
1030
|
+
x_max={line.x_axis === `x2` ? ranges.current.x2[1] : ranges.current.x[1]}
|
|
574
1031
|
y_min={line.y_axis === `y2` ? ranges.current.y2[0] : ranges.current.y[0]}
|
|
575
1032
|
y_max={line.y_axis === `y2` ? ranges.current.y2[1] : ranges.current.y[1]}
|
|
576
1033
|
x_scale={scales.x}
|
|
1034
|
+
x2_scale={scales.x2}
|
|
577
1035
|
y_scale={scales.y}
|
|
578
1036
|
y2_scale={scales.y2}
|
|
579
1037
|
{clip_path_id}
|
|
@@ -625,6 +1083,9 @@ $effect(() => {
|
|
|
625
1083
|
<svg
|
|
626
1084
|
bind:this={svg_element}
|
|
627
1085
|
role="application"
|
|
1086
|
+
aria-label={rest[`aria-label`] ??
|
|
1087
|
+
([final_x_axis.label, final_y_axis.label].filter(Boolean).join(` vs `) ||
|
|
1088
|
+
`Histogram`)}
|
|
628
1089
|
tabindex="0"
|
|
629
1090
|
onfocusin={() => (is_focused = true)}
|
|
630
1091
|
onfocusout={() => (is_focused = false)}
|
|
@@ -670,79 +1131,33 @@ $effect(() => {
|
|
|
670
1131
|
<!-- Reference lines: below grid (must render first to appear behind grid) -->
|
|
671
1132
|
{@render ref_lines_layer(ref_lines_by_z.below_grid)}
|
|
672
1133
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
{
|
|
678
|
-
{
|
|
679
|
-
{
|
|
680
|
-
{
|
|
681
|
-
|
|
682
|
-
|
|
1134
|
+
<ZoomRect start={drag_state.start} current={drag_state.current} />
|
|
1135
|
+
|
|
1136
|
+
<ZeroLines
|
|
1137
|
+
{display}
|
|
1138
|
+
x_scale_fn={scales.x}
|
|
1139
|
+
x2_scale_fn={scales.x2}
|
|
1140
|
+
y_scale_fn={scales.y}
|
|
1141
|
+
y2_scale_fn={scales.y2}
|
|
1142
|
+
x_range={ranges.current.x}
|
|
1143
|
+
x2_range={ranges.current.x2}
|
|
1144
|
+
y_range={ranges.current.y}
|
|
1145
|
+
y2_range={ranges.current.y2}
|
|
1146
|
+
x_scale_type={final_x_axis.scale_type}
|
|
1147
|
+
x2_scale_type={final_x2_axis.scale_type}
|
|
1148
|
+
y_scale_type={final_y_axis.scale_type}
|
|
1149
|
+
y2_scale_type={final_y2_axis.scale_type}
|
|
1150
|
+
has_x2={x2_series.length > 0}
|
|
1151
|
+
has_y2={y2_series.length > 0}
|
|
1152
|
+
{width}
|
|
1153
|
+
{height}
|
|
1154
|
+
{pad}
|
|
1155
|
+
/>
|
|
683
1156
|
|
|
684
1157
|
<!-- Reference lines: below lines -->
|
|
685
1158
|
{@render ref_lines_layer(ref_lines_by_z.below_lines)}
|
|
686
1159
|
|
|
687
|
-
<!--
|
|
688
|
-
{#each histogram_data as
|
|
689
|
-
{ id, bins, color, label, y_scale, y_axis },
|
|
690
|
-
series_idx
|
|
691
|
-
(id ?? series_idx)
|
|
692
|
-
}
|
|
693
|
-
<g class="histogram-series" data-series-idx={series_idx}>
|
|
694
|
-
{#each bins as bin, bin_idx (bin_idx)}
|
|
695
|
-
{@const bar_x = scales.x(bin.x0!)}
|
|
696
|
-
{@const bar_width = Math.max(1, Math.abs(scales.x(bin.x1!) - bar_x))}
|
|
697
|
-
{@const bar_height = Math.max(0, (height - pad.b) - y_scale(bin.length))}
|
|
698
|
-
{@const bar_y = y_scale(bin.length)}
|
|
699
|
-
{@const value = (bin.x0! + bin.x1!) / 2}
|
|
700
|
-
{#if bar_height > 0}
|
|
701
|
-
<path
|
|
702
|
-
d={bar_path(
|
|
703
|
-
bar_x,
|
|
704
|
-
bar_y,
|
|
705
|
-
bar_width,
|
|
706
|
-
bar_height,
|
|
707
|
-
Math.min(final_bar.border_radius ?? 0, bar_width / 2, bar_height / 2),
|
|
708
|
-
)}
|
|
709
|
-
fill={color}
|
|
710
|
-
opacity={final_bar.opacity}
|
|
711
|
-
stroke={final_bar.stroke_color}
|
|
712
|
-
stroke-opacity={final_bar.stroke_opacity}
|
|
713
|
-
stroke-width={final_bar.stroke_width}
|
|
714
|
-
role="button"
|
|
715
|
-
tabindex="0"
|
|
716
|
-
onmousemove={(evt) =>
|
|
717
|
-
handle_mouse_move(
|
|
718
|
-
evt,
|
|
719
|
-
value,
|
|
720
|
-
bin.length,
|
|
721
|
-
label,
|
|
722
|
-
(y_axis ?? `y1`) as `y1` | `y2`,
|
|
723
|
-
series_idx,
|
|
724
|
-
)}
|
|
725
|
-
onmouseleave={() => {
|
|
726
|
-
hover_info = null
|
|
727
|
-
change(null)
|
|
728
|
-
on_bar_hover?.(null)
|
|
729
|
-
}}
|
|
730
|
-
onclick={(event) =>
|
|
731
|
-
on_bar_click?.({ value, count: bin.length, property: label, event })}
|
|
732
|
-
onkeydown={(event: KeyboardEvent) => {
|
|
733
|
-
if ([`Enter`, ` `].includes(event.key)) {
|
|
734
|
-
event.preventDefault()
|
|
735
|
-
on_bar_click?.({ value, count: bin.length, property: label, event })
|
|
736
|
-
}
|
|
737
|
-
}}
|
|
738
|
-
style:cursor={on_bar_click ? `pointer` : undefined}
|
|
739
|
-
/>
|
|
740
|
-
{/if}
|
|
741
|
-
{/each}
|
|
742
|
-
</g>
|
|
743
|
-
{/each}
|
|
744
|
-
|
|
745
|
-
<!-- Reference lines: below points (after bars, before axes/labels) -->
|
|
1160
|
+
<!-- Reference lines: below points -->
|
|
746
1161
|
{@render ref_lines_layer(ref_lines_by_z.below_points)}
|
|
747
1162
|
|
|
748
1163
|
<!-- X-axis -->
|
|
@@ -761,7 +1176,7 @@ $effect(() => {
|
|
|
761
1176
|
{@const inside = final_x_axis.tick?.label?.inside ?? false}
|
|
762
1177
|
{@const shift_x = final_x_axis.tick?.label?.shift?.x ?? 0}
|
|
763
1178
|
{@const shift_y = final_x_axis.tick?.label?.shift?.y ?? 0}
|
|
764
|
-
{@const base_y = inside ? -8 :
|
|
1179
|
+
{@const base_y = inside ? -8 : 8}
|
|
765
1180
|
{@const text_y = base_y + shift_y}
|
|
766
1181
|
{@const dominant_baseline = inside ? `auto` : `hanging`}
|
|
767
1182
|
<g class="tick" transform="translate({tick_x}, {height - pad.b})">
|
|
@@ -793,31 +1208,87 @@ $effect(() => {
|
|
|
793
1208
|
</g>
|
|
794
1209
|
{/each}
|
|
795
1210
|
{#if final_x_axis.label || x_axis.options?.length}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
y={height - 10 + (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
options={x_axis.options}
|
|
809
|
-
selected_key={x_axis.selected_key}
|
|
810
|
-
loading={axis_loading === `x`}
|
|
811
|
-
axis_type="x"
|
|
812
|
-
color={final_x_axis.color}
|
|
813
|
-
on_select={(key) => handle_axis_change(`x`, key)}
|
|
814
|
-
class="axis-label x-label"
|
|
815
|
-
/>
|
|
816
|
-
</div>
|
|
817
|
-
</foreignObject>
|
|
1211
|
+
{@const { label_shift, label = ``, options, selected_key, color } = final_x_axis}
|
|
1212
|
+
<AxisLabel
|
|
1213
|
+
x={(pad.l + width - pad.r) / 2 + (label_shift?.x ?? 0)}
|
|
1214
|
+
y={height - 10 + (label_shift?.y ?? 0)}
|
|
1215
|
+
{label}
|
|
1216
|
+
{options}
|
|
1217
|
+
{selected_key}
|
|
1218
|
+
loading={axis_loading === `x`}
|
|
1219
|
+
axis_type="x"
|
|
1220
|
+
{color}
|
|
1221
|
+
on_select={(key) => handle_axis_change(`x`, key)}
|
|
1222
|
+
/>
|
|
818
1223
|
{/if}
|
|
819
1224
|
</g>
|
|
820
1225
|
|
|
1226
|
+
<!-- X2-axis (Top) -->
|
|
1227
|
+
{#if x2_series.length > 0}
|
|
1228
|
+
<g class="x2-axis">
|
|
1229
|
+
<line
|
|
1230
|
+
x1={pad.l}
|
|
1231
|
+
x2={width - pad.r}
|
|
1232
|
+
y1={pad.t}
|
|
1233
|
+
y2={pad.t}
|
|
1234
|
+
stroke={final_x2_axis.color || `var(--border-color, gray)`}
|
|
1235
|
+
stroke-width="1"
|
|
1236
|
+
/>
|
|
1237
|
+
{#each ticks.x2 as tick (tick)}
|
|
1238
|
+
{@const tick_x = scales.x2(tick as number)}
|
|
1239
|
+
{@const custom_label = get_tick_label(tick as number, final_x2_axis.ticks)}
|
|
1240
|
+
{@const inside = final_x2_axis.tick?.label?.inside ?? false}
|
|
1241
|
+
{@const shift_x = final_x2_axis.tick?.label?.shift?.x ?? 0}
|
|
1242
|
+
{@const shift_y = final_x2_axis.tick?.label?.shift?.y ?? 0}
|
|
1243
|
+
{@const base_y = inside ? 8 : -20}
|
|
1244
|
+
{@const text_y = base_y + shift_y}
|
|
1245
|
+
{@const dominant_baseline = inside ? `hanging` : `auto`}
|
|
1246
|
+
<g class="tick" transform="translate({tick_x}, {pad.t})">
|
|
1247
|
+
{#if display.x2_grid}
|
|
1248
|
+
<line
|
|
1249
|
+
y1="0"
|
|
1250
|
+
y2={height - pad.b - pad.t}
|
|
1251
|
+
stroke="var(--border-color, gray)"
|
|
1252
|
+
stroke-dasharray="4"
|
|
1253
|
+
stroke-width="1"
|
|
1254
|
+
{...final_x2_axis.grid_style ?? {}}
|
|
1255
|
+
/>
|
|
1256
|
+
{/if}
|
|
1257
|
+
<line
|
|
1258
|
+
y1={inside ? 0 : -5}
|
|
1259
|
+
y2={inside ? 5 : 0}
|
|
1260
|
+
stroke={final_x2_axis.color || `var(--border-color, gray)`}
|
|
1261
|
+
stroke-width="1"
|
|
1262
|
+
/>
|
|
1263
|
+
<text
|
|
1264
|
+
x={shift_x}
|
|
1265
|
+
y={text_y}
|
|
1266
|
+
text-anchor="middle"
|
|
1267
|
+
dominant-baseline={dominant_baseline}
|
|
1268
|
+
fill={final_x2_axis.color || `var(--text-color)`}
|
|
1269
|
+
>
|
|
1270
|
+
{custom_label ?? format_value(tick, final_x2_axis.format)}
|
|
1271
|
+
</text>
|
|
1272
|
+
</g>
|
|
1273
|
+
{/each}
|
|
1274
|
+
{#if final_x2_axis.label || x2_axis.options?.length}
|
|
1275
|
+
{@const { label_shift, label = ``, options, selected_key, color } =
|
|
1276
|
+
final_x2_axis}
|
|
1277
|
+
<AxisLabel
|
|
1278
|
+
x={(pad.l + width - pad.r) / 2 + (label_shift?.x ?? 0)}
|
|
1279
|
+
y={Math.max(12, pad.t - (label_shift?.y ?? 40))}
|
|
1280
|
+
{label}
|
|
1281
|
+
{options}
|
|
1282
|
+
{selected_key}
|
|
1283
|
+
loading={axis_loading === `x2`}
|
|
1284
|
+
axis_type="x2"
|
|
1285
|
+
{color}
|
|
1286
|
+
on_select={(key) => handle_axis_change(`x2`, key)}
|
|
1287
|
+
/>
|
|
1288
|
+
{/if}
|
|
1289
|
+
</g>
|
|
1290
|
+
{/if}
|
|
1291
|
+
|
|
821
1292
|
<!-- Y-axis -->
|
|
822
1293
|
<g class="y-axis">
|
|
823
1294
|
<line
|
|
@@ -866,41 +1337,25 @@ $effect(() => {
|
|
|
866
1337
|
</g>
|
|
867
1338
|
{/each}
|
|
868
1339
|
{#if final_y_axis.label || y_axis.options?.length}
|
|
869
|
-
{@const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
)
|
|
876
|
-
)
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
style="overflow: visible; pointer-events: none"
|
|
889
|
-
transform="rotate(-90, {y_label_x}, {y_label_y})"
|
|
890
|
-
>
|
|
891
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="pointer-events: auto">
|
|
892
|
-
<InteractiveAxisLabel
|
|
893
|
-
label={final_y_axis.label ?? ``}
|
|
894
|
-
options={y_axis.options}
|
|
895
|
-
selected_key={y_axis.selected_key}
|
|
896
|
-
loading={axis_loading === `y`}
|
|
897
|
-
axis_type="y"
|
|
898
|
-
color={final_y_axis.color}
|
|
899
|
-
on_select={(key) => handle_axis_change(`y`, key)}
|
|
900
|
-
class="axis-label y-label"
|
|
901
|
-
/>
|
|
902
|
-
</div>
|
|
903
|
-
</foreignObject>
|
|
1340
|
+
{@const { label_shift, label = ``, options, selected_key, color, tick } =
|
|
1341
|
+
final_y_axis}
|
|
1342
|
+
{@const y_inside = tick?.label?.inside ?? false}
|
|
1343
|
+
<AxisLabel
|
|
1344
|
+
x={Math.max(
|
|
1345
|
+
12,
|
|
1346
|
+
pad.l - (y_inside ? 0 : tick_label_widths.y_max) - LABEL_GAP_DEFAULT,
|
|
1347
|
+
) +
|
|
1348
|
+
(label_shift?.x ?? 0)}
|
|
1349
|
+
y={pad.t + (height - pad.t - pad.b) / 2 + (label_shift?.y ?? 0)}
|
|
1350
|
+
rotate
|
|
1351
|
+
{label}
|
|
1352
|
+
{options}
|
|
1353
|
+
{selected_key}
|
|
1354
|
+
loading={axis_loading === `y`}
|
|
1355
|
+
axis_type="y"
|
|
1356
|
+
{color}
|
|
1357
|
+
on_select={(key) => handle_axis_change(`y`, key)}
|
|
1358
|
+
/>
|
|
904
1359
|
{/if}
|
|
905
1360
|
</g>
|
|
906
1361
|
|
|
@@ -952,80 +1407,86 @@ $effect(() => {
|
|
|
952
1407
|
</g>
|
|
953
1408
|
{/each}
|
|
954
1409
|
{#if final_y2_axis.label || y2_axis.options?.length}
|
|
955
|
-
{@const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
<foreignObject
|
|
974
|
-
x={y2_label_x - AXIS_LABEL_CONTAINER.x_offset}
|
|
975
|
-
y={y2_label_y - AXIS_LABEL_CONTAINER.y_offset}
|
|
976
|
-
width={AXIS_LABEL_CONTAINER.width}
|
|
977
|
-
height={AXIS_LABEL_CONTAINER.height}
|
|
978
|
-
style="overflow: visible; pointer-events: none"
|
|
979
|
-
transform="rotate(-90, {y2_label_x}, {y2_label_y})"
|
|
980
|
-
>
|
|
981
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="pointer-events: auto">
|
|
982
|
-
<InteractiveAxisLabel
|
|
983
|
-
label={final_y2_axis.label ?? ``}
|
|
984
|
-
options={y2_axis.options}
|
|
985
|
-
selected_key={y2_axis.selected_key}
|
|
986
|
-
loading={axis_loading === `y2`}
|
|
987
|
-
axis_type="y2"
|
|
988
|
-
color={final_y2_axis.color}
|
|
989
|
-
on_select={(key) => handle_axis_change(`y2`, key)}
|
|
990
|
-
class="axis-label y2-label"
|
|
991
|
-
/>
|
|
992
|
-
</div>
|
|
993
|
-
</foreignObject>
|
|
1410
|
+
{@const { label_shift, label = ``, options, selected_key, color, tick } =
|
|
1411
|
+
final_y2_axis}
|
|
1412
|
+
{@const inside = tick?.label?.inside ?? false}
|
|
1413
|
+
{@const tick_shift = inside ? 0 : (tick?.label?.shift?.x ?? 0) + 8}
|
|
1414
|
+
{@const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max}
|
|
1415
|
+
<AxisLabel
|
|
1416
|
+
x={width - pad.r + tick_shift + tick_width_contribution +
|
|
1417
|
+
LABEL_GAP_DEFAULT + (label_shift?.x ?? 0)}
|
|
1418
|
+
y={pad.t + (height - pad.t - pad.b) / 2 + (label_shift?.y ?? 0)}
|
|
1419
|
+
rotate
|
|
1420
|
+
{label}
|
|
1421
|
+
{options}
|
|
1422
|
+
{selected_key}
|
|
1423
|
+
loading={axis_loading === `y2`}
|
|
1424
|
+
axis_type="y2"
|
|
1425
|
+
{color}
|
|
1426
|
+
on_select={(key) => handle_axis_change(`y2`, key)}
|
|
1427
|
+
/>
|
|
994
1428
|
{/if}
|
|
995
1429
|
</g>
|
|
996
1430
|
{/if}
|
|
997
1431
|
|
|
998
|
-
<!--
|
|
999
|
-
{#
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1432
|
+
<!-- Histogram bars (rendered after axes so bars appear above grid lines) -->
|
|
1433
|
+
{#each histogram_data as
|
|
1434
|
+
{ id, bins, color, label, x_scale, y_scale, x_axis: srs_x_axis, y_axis },
|
|
1435
|
+
series_idx
|
|
1436
|
+
(id ?? series_idx)
|
|
1437
|
+
}
|
|
1438
|
+
<g class="histogram-series" data-series-idx={series_idx}>
|
|
1439
|
+
{#each bins as bin, bin_idx (bin_idx)}
|
|
1440
|
+
{@const bar_x = x_scale(bin.x0!)}
|
|
1441
|
+
{@const bar_width = Math.max(1, Math.abs(x_scale(bin.x1!) - bar_x))}
|
|
1442
|
+
{@const bar_height = Math.max(0, (height - pad.b) - y_scale(bin.length))}
|
|
1443
|
+
{@const bar_y = y_scale(bin.length)}
|
|
1444
|
+
{@const value = (bin.x0! + bin.x1!) / 2}
|
|
1445
|
+
{#if bar_height > 0}
|
|
1446
|
+
<path
|
|
1447
|
+
d={bar_path(
|
|
1448
|
+
bar_x,
|
|
1449
|
+
bar_y,
|
|
1450
|
+
bar_width,
|
|
1451
|
+
bar_height,
|
|
1452
|
+
Math.min(final_bar.border_radius ?? 0, bar_width / 2, bar_height / 2),
|
|
1453
|
+
)}
|
|
1454
|
+
fill={color}
|
|
1455
|
+
opacity={final_bar.opacity}
|
|
1456
|
+
stroke={final_bar.stroke_color}
|
|
1457
|
+
stroke-opacity={final_bar.stroke_opacity}
|
|
1458
|
+
stroke-width={final_bar.stroke_width}
|
|
1459
|
+
role="button"
|
|
1460
|
+
tabindex="0"
|
|
1461
|
+
onmousemove={(evt) =>
|
|
1462
|
+
handle_mouse_move(
|
|
1463
|
+
evt,
|
|
1464
|
+
value,
|
|
1465
|
+
bin.length,
|
|
1466
|
+
label,
|
|
1467
|
+
(y_axis ?? `y1`) as `y1` | `y2`,
|
|
1468
|
+
series_idx,
|
|
1469
|
+
(srs_x_axis ?? `x1`) as `x1` | `x2`,
|
|
1470
|
+
)}
|
|
1471
|
+
onmouseleave={() => {
|
|
1472
|
+
hover_info = null
|
|
1473
|
+
change(null)
|
|
1474
|
+
on_bar_hover?.(null)
|
|
1475
|
+
}}
|
|
1476
|
+
onclick={(event) =>
|
|
1477
|
+
on_bar_click?.({ value, count: bin.length, property: label, event })}
|
|
1478
|
+
onkeydown={(event: KeyboardEvent) => {
|
|
1479
|
+
if ([`Enter`, ` `].includes(event.key)) {
|
|
1480
|
+
event.preventDefault()
|
|
1481
|
+
on_bar_click?.({ value, count: bin.length, property: label, event })
|
|
1482
|
+
}
|
|
1483
|
+
}}
|
|
1484
|
+
style:cursor={on_bar_click ? `pointer` : undefined}
|
|
1485
|
+
/>
|
|
1486
|
+
{/if}
|
|
1487
|
+
{/each}
|
|
1488
|
+
</g>
|
|
1489
|
+
{/each}
|
|
1029
1490
|
|
|
1030
1491
|
<!-- Reference lines: above all -->
|
|
1031
1492
|
{@render ref_lines_layer(ref_lines_by_z.above_all)}
|
|
@@ -1033,8 +1494,8 @@ $effect(() => {
|
|
|
1033
1494
|
|
|
1034
1495
|
<!-- Tooltip (outside SVG for proper HTML rendering) -->
|
|
1035
1496
|
{#if hover_info}
|
|
1036
|
-
{@const { value, count, property, active_y_axis } = hover_info}
|
|
1037
|
-
{@const tooltip_x = scales.x(value)}
|
|
1497
|
+
{@const { value, count, property, active_y_axis, active_x_axis } = hover_info}
|
|
1498
|
+
{@const tooltip_x = (active_x_axis === `x2` ? scales.x2 : scales.x)(value)}
|
|
1038
1499
|
{@const tooltip_y = (active_y_axis === `y2` ? scales.y2 : scales.y)(count)}
|
|
1039
1500
|
{@const tooltip_pos = constrain_tooltip_position(
|
|
1040
1501
|
tooltip_x,
|
|
@@ -1045,7 +1506,6 @@ $effect(() => {
|
|
|
1045
1506
|
height,
|
|
1046
1507
|
{ offset_x: 5, offset_y: -10 },
|
|
1047
1508
|
)}
|
|
1048
|
-
{@const active_y_config = active_y_axis === `y2` ? final_y2_axis : final_y_axis}
|
|
1049
1509
|
<PlotTooltip
|
|
1050
1510
|
x={tooltip_pos.x}
|
|
1051
1511
|
y={tooltip_pos.y}
|
|
@@ -1055,8 +1515,8 @@ $effect(() => {
|
|
|
1055
1515
|
{#if tooltip}
|
|
1056
1516
|
{@render tooltip({ ...hover_info, fullscreen })}
|
|
1057
1517
|
{:else}
|
|
1058
|
-
<div>Value: {format_value(value,
|
|
1059
|
-
<div>Count: {format_value(count,
|
|
1518
|
+
<div>Value: {format_value(value, hover_info.x_axis.format || `.3~s`)}</div>
|
|
1519
|
+
<div>Count: {format_value(count, hover_info.y_axis.format || `.3~s`)}</div>
|
|
1060
1520
|
{#if mode === `overlay`}<div>{property}</div>{/if}
|
|
1061
1521
|
{/if}
|
|
1062
1522
|
</PlotTooltip>
|
|
@@ -1066,7 +1526,7 @@ $effect(() => {
|
|
|
1066
1526
|
<HistogramControls
|
|
1067
1527
|
toggle_props={{
|
|
1068
1528
|
...controls_toggle_props,
|
|
1069
|
-
style: `--ctrl-btn-right: var(--fullscreen-btn-offset,
|
|
1529
|
+
style: `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); ${
|
|
1070
1530
|
controls_toggle_props?.style ?? ``
|
|
1071
1531
|
}`,
|
|
1072
1532
|
}}
|
|
@@ -1080,12 +1540,16 @@ $effect(() => {
|
|
|
1080
1540
|
bind:display
|
|
1081
1541
|
bind:bar
|
|
1082
1542
|
bind:x_axis
|
|
1543
|
+
bind:x2_axis
|
|
1083
1544
|
bind:y_axis
|
|
1084
1545
|
bind:y2_axis
|
|
1085
1546
|
auto_x_range={auto_ranges.x}
|
|
1547
|
+
auto_x2_range={auto_ranges.x2}
|
|
1086
1548
|
auto_y_range={auto_ranges.y}
|
|
1087
1549
|
auto_y2_range={auto_ranges.y2}
|
|
1088
1550
|
{series}
|
|
1551
|
+
has_x2_points={x2_series.length > 0}
|
|
1552
|
+
has_y2_points={y2_series.length > 0}
|
|
1089
1553
|
children={controls_extra}
|
|
1090
1554
|
/>
|
|
1091
1555
|
{/if}
|
|
@@ -1178,7 +1642,7 @@ $effect(() => {
|
|
|
1178
1642
|
font-weight: var(--histogram-font-weight);
|
|
1179
1643
|
font-size: var(--histogram-font-size);
|
|
1180
1644
|
}
|
|
1181
|
-
g:is(.x-axis, .y-axis, .y2-axis) .tick text {
|
|
1645
|
+
g:is(.x-axis, .x2-axis, .y-axis, .y2-axis) .tick text {
|
|
1182
1646
|
font-size: var(--tick-font-size, 0.8em); /* shrink tick labels */
|
|
1183
1647
|
}
|
|
1184
1648
|
.histogram-series path {
|
|
@@ -1187,15 +1651,4 @@ $effect(() => {
|
|
|
1187
1651
|
.histogram-series path:hover {
|
|
1188
1652
|
opacity: 1 !important;
|
|
1189
1653
|
}
|
|
1190
|
-
.zoom-rect {
|
|
1191
|
-
fill: var(--histogram-zoom-rect-fill, rgba(100, 100, 255, 0.2));
|
|
1192
|
-
stroke: var(--histogram-zoom-rect-stroke, rgba(100, 100, 255, 0.8));
|
|
1193
|
-
stroke-width: var(--histogram-zoom-rect-stroke-width, 1);
|
|
1194
|
-
pointer-events: none;
|
|
1195
|
-
}
|
|
1196
|
-
.zero-line {
|
|
1197
|
-
stroke: var(--histogram-zero-line-color, light-dark(black, white));
|
|
1198
|
-
stroke-width: var(--histogram-zero-line-width, 1);
|
|
1199
|
-
opacity: var(--histogram-zero-line-opacity);
|
|
1200
|
-
}
|
|
1201
1654
|
</style>
|