matterviz 0.3.0 → 0.3.2
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/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -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 +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -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 +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- 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/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -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 +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -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 +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- 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 +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- 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 +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- 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 +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- 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/plot/BarPlot.svelte
CHANGED
|
@@ -3,23 +3,25 @@
|
|
|
3
3
|
generics="Metadata extends Record<string, unknown> = Record<string, unknown>"
|
|
4
4
|
>import { format_value } from '../labels';
|
|
5
5
|
import { FullscreenToggle, set_fullscreen_bg } from '../layout';
|
|
6
|
-
import { BarPlotControls, compute_element_placement,
|
|
7
|
-
import {
|
|
6
|
+
import { AxisLabel, BarPlotControls, compute_element_placement, PlotLegend, ReferenceLine, ScatterPoint, } from './';
|
|
7
|
+
import { create_axis_change_handler } from './axis-utils';
|
|
8
8
|
import { process_prop } from './data-transform';
|
|
9
9
|
import { create_dimension_tracker, create_hover_lock, } from './hover-lock.svelte';
|
|
10
10
|
import { get_relative_coords, pan_range, PINCH_ZOOM_THRESHOLD, pixels_to_data_delta, } from './interactions';
|
|
11
11
|
import { group_ref_lines_by_z, index_ref_lines } from './reference-line';
|
|
12
|
-
import { create_color_scale, create_scale, create_size_scale, generate_ticks, get_nice_data_range, } from './scales';
|
|
12
|
+
import { create_color_scale, create_scale, create_size_scale, generate_ticks, get_nice_data_range, get_tick_label, } from './scales';
|
|
13
13
|
import { DEFAULT_GRID_STYLE, DEFAULT_MARKERS, get_scale_type_name, } from './types';
|
|
14
14
|
import { DEFAULTS } from '../settings';
|
|
15
15
|
import { extent } from 'd3-array';
|
|
16
16
|
import { untrack } from 'svelte';
|
|
17
17
|
import { Tween } from 'svelte/motion';
|
|
18
18
|
import { SvelteMap } from 'svelte/reactivity';
|
|
19
|
-
import { calc_auto_padding, constrain_tooltip_position, filter_padding, LABEL_GAP_DEFAULT,
|
|
19
|
+
import { calc_auto_padding, constrain_tooltip_position, filter_padding, LABEL_GAP_DEFAULT, measure_max_tick_width, } from './layout';
|
|
20
20
|
import PlotTooltip from './PlotTooltip.svelte';
|
|
21
21
|
import { bar_path } from './svg';
|
|
22
|
-
|
|
22
|
+
import ZeroLines from './ZeroLines.svelte';
|
|
23
|
+
import ZoomRect from './ZoomRect.svelte';
|
|
24
|
+
let { series = $bindable([]), orientation = $bindable(`vertical`), mode = $bindable(`overlay`), x_axis = $bindable({}), x2_axis = $bindable({}), y_axis = $bindable({}), y2_axis = $bindable({}), display = $bindable(DEFAULTS.bar.display), x_range = [null, null], x2_range = [null, null], y_range = [null, null], y2_range = [null, null], range_padding = 0.05, padding = { t: 20, b: 60, l: 60, r: 20 }, legend = {}, show_legend, bar = {}, line = {}, tooltip, user_content, hovered = $bindable(false), change = () => { }, on_bar_click, on_bar_hover,
|
|
23
25
|
// Line marker props (matching ScatterPlot)
|
|
24
26
|
color_scale = {
|
|
25
27
|
type: `linear`,
|
|
@@ -38,6 +40,15 @@ y2_axis = {
|
|
|
38
40
|
range: [null, null],
|
|
39
41
|
...y2_axis,
|
|
40
42
|
};
|
|
43
|
+
x2_axis = {
|
|
44
|
+
format: ``,
|
|
45
|
+
scale_type: `linear`,
|
|
46
|
+
ticks: 5,
|
|
47
|
+
label_shift: { x: 0, y: 40 },
|
|
48
|
+
tick: { label: { shift: { x: 0, y: 0 } } },
|
|
49
|
+
range: [null, null],
|
|
50
|
+
...x2_axis,
|
|
51
|
+
};
|
|
41
52
|
let [width, height] = $state([0, 0]);
|
|
42
53
|
let wrapper = $state();
|
|
43
54
|
let svg_element = $state(null);
|
|
@@ -49,11 +60,47 @@ let axis_loading = $state(null);
|
|
|
49
60
|
// Compute ref_lines with index and group by z-index (using shared utilities)
|
|
50
61
|
let indexed_ref_lines = $derived(index_ref_lines(ref_lines));
|
|
51
62
|
let ref_lines_by_z = $derived(group_ref_lines_by_z(indexed_ref_lines));
|
|
63
|
+
let is_categorical = $derived(series.some((srs) => srs.x.some((val) => typeof val === `string`)));
|
|
64
|
+
let category_list = $derived.by(() => {
|
|
65
|
+
if (!is_categorical)
|
|
66
|
+
return [];
|
|
67
|
+
if (x_axis.categories?.length)
|
|
68
|
+
return [...x_axis.categories];
|
|
69
|
+
return [...new Set(series.flatMap((srs) => srs.x.map(String)))];
|
|
70
|
+
});
|
|
71
|
+
let category_indices = $derived(category_list.length ? category_list.map((_, idx) => idx) : null);
|
|
72
|
+
let internal_series = $derived.by(() => {
|
|
73
|
+
// safe: when !category_indices, all x values are numeric (is_categorical is false)
|
|
74
|
+
if (!category_indices)
|
|
75
|
+
return series;
|
|
76
|
+
return series.map((srs) => {
|
|
77
|
+
const orig_map = new Map(srs.x.map((val, idx) => [String(val), idx]));
|
|
78
|
+
if (orig_map.size < srs.x.length) {
|
|
79
|
+
console.warn(`BarPlot: series "${srs.label ?? `?`}" has duplicate x values — last occurrence wins`);
|
|
80
|
+
}
|
|
81
|
+
// Resolve original index for each category (undefined if series lacks it)
|
|
82
|
+
const orig_indices = category_list.map((cat) => orig_map.get(cat));
|
|
83
|
+
const remap = (arr, fallback) => orig_indices.map((oi) => oi != null ? (arr?.[oi] ?? fallback) : fallback);
|
|
84
|
+
const bw_arr = Array.isArray(srs.bar_width) ? srs.bar_width : null;
|
|
85
|
+
const meta_arr = Array.isArray(srs.metadata) ? srs.metadata : null;
|
|
86
|
+
return {
|
|
87
|
+
...srs,
|
|
88
|
+
x: category_indices,
|
|
89
|
+
y: remap(srs.y, srs.render_mode === `line` ? NaN : 0),
|
|
90
|
+
labels: remap(srs.labels, null),
|
|
91
|
+
metadata: orig_indices.map((oi) => oi != null ? (meta_arr ? meta_arr[oi] : srs.metadata) : undefined),
|
|
92
|
+
...(bw_arr ? { bar_width: remap(bw_arr, 0.5) } : {}),
|
|
93
|
+
...(srs.color_values ? { color_values: remap(srs.color_values, null) } : {}),
|
|
94
|
+
...(srs.size_values ? { size_values: remap(srs.size_values, null) } : {}),
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
});
|
|
52
98
|
// Compute auto ranges from visible series
|
|
53
|
-
let visible_series = $derived(
|
|
99
|
+
let visible_series = $derived(internal_series.filter((srs) => srs?.visible ?? true));
|
|
54
100
|
// Separate series by y-axis
|
|
55
101
|
let y1_series = $derived(visible_series.filter((srs) => (srs.y_axis ?? `y1`) === `y1`));
|
|
56
102
|
let y2_series = $derived(visible_series.filter((srs) => srs.y_axis === `y2`));
|
|
103
|
+
let x2_series = $derived(visible_series.filter((srs) => srs.x_axis === `x2`));
|
|
57
104
|
let auto_ranges = $derived.by(() => {
|
|
58
105
|
// Calculate separate ranges for y1 and y2 axes
|
|
59
106
|
const calc_y_range = (series_list, y_limit, scale_type) => {
|
|
@@ -103,29 +150,46 @@ let auto_ranges = $derived.by(() => {
|
|
|
103
150
|
}
|
|
104
151
|
return y_range;
|
|
105
152
|
};
|
|
106
|
-
// Get
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
153
|
+
// Get x values split by axis for range calculation
|
|
154
|
+
// For categorical data, use fixed range centered on integer indices
|
|
155
|
+
let x_auto_range;
|
|
156
|
+
if (category_list.length) {
|
|
157
|
+
x_auto_range = [-0.5, category_list.length - 0.5];
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const x1_x_points = visible_series
|
|
161
|
+
.filter((srs) => (srs.x_axis ?? `x1`) === `x1`)
|
|
162
|
+
.flatMap((srs) => srs.x.map((x_val) => ({ x: x_val, y: 0 })));
|
|
163
|
+
x_auto_range = x1_x_points.length
|
|
164
|
+
? get_nice_data_range(x1_x_points, (pt) => pt.x, x_range, x_axis.scale_type ?? `linear`, range_padding, x_axis.format?.startsWith(`%`) || false)
|
|
165
|
+
: [0, 1];
|
|
166
|
+
}
|
|
167
|
+
const x2_x_points = x2_series.flatMap((srs) => srs.x.map((x_val) => ({ x: x_val, y: 0 })));
|
|
168
|
+
const x2_scale_type = x2_axis.scale_type ?? `linear`;
|
|
169
|
+
const x2_auto_range = x2_x_points.length
|
|
170
|
+
? get_nice_data_range(x2_x_points, (pt) => pt.x, x2_range, x2_scale_type, range_padding, x2_axis.format?.startsWith(`%`) || false)
|
|
111
171
|
: [0, 1];
|
|
112
172
|
const y1_range = calc_y_range(y1_series, y_range, y_axis.scale_type ?? `linear`);
|
|
113
173
|
const y2_auto_range = calc_y_range(y2_series, y2_range, y2_axis.scale_type ?? `linear`);
|
|
114
174
|
// Map data ranges to axis ranges depending on orientation
|
|
115
175
|
return orientation === `horizontal`
|
|
116
|
-
? ({ x: y1_range, y: x_auto_range, y2: y2_auto_range })
|
|
117
|
-
: ({ x: x_auto_range, y: y1_range, y2: y2_auto_range });
|
|
176
|
+
? ({ x: y1_range, x2: x2_auto_range, y: x_auto_range, y2: y2_auto_range })
|
|
177
|
+
: ({ x: x_auto_range, x2: x2_auto_range, y: y1_range, y2: y2_auto_range });
|
|
118
178
|
});
|
|
119
179
|
// Initialize and current ranges
|
|
120
180
|
let ranges = $state({
|
|
121
|
-
initial: { x: [0, 1], y: [0, 1], y2: [0, 1] },
|
|
122
|
-
current: { x: [0, 1], y: [0, 1], y2: [0, 1] },
|
|
181
|
+
initial: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
|
|
182
|
+
current: { x: [0, 1], x2: [0, 1], y: [0, 1], y2: [0, 1] },
|
|
123
183
|
});
|
|
124
184
|
$effect(() => {
|
|
125
185
|
const new_x = [
|
|
126
186
|
x_axis.range?.[0] ?? auto_ranges.x[0],
|
|
127
187
|
x_axis.range?.[1] ?? auto_ranges.x[1],
|
|
128
188
|
];
|
|
189
|
+
const new_x2 = [
|
|
190
|
+
x2_axis.range?.[0] ?? auto_ranges.x2[0],
|
|
191
|
+
x2_axis.range?.[1] ?? auto_ranges.x2[1],
|
|
192
|
+
];
|
|
129
193
|
const new_y = [
|
|
130
194
|
y_axis.range?.[0] ?? auto_ranges.y[0],
|
|
131
195
|
y_axis.range?.[1] ?? auto_ranges.y[1],
|
|
@@ -138,13 +202,15 @@ $effect(() => {
|
|
|
138
202
|
// Comparing against initial preserves user's pan/zoom state
|
|
139
203
|
if (ranges.initial.x[0] !== new_x[0] ||
|
|
140
204
|
ranges.initial.x[1] !== new_x[1] ||
|
|
205
|
+
ranges.initial.x2[0] !== new_x2[0] ||
|
|
206
|
+
ranges.initial.x2[1] !== new_x2[1] ||
|
|
141
207
|
ranges.initial.y[0] !== new_y[0] ||
|
|
142
208
|
ranges.initial.y[1] !== new_y[1] ||
|
|
143
209
|
ranges.initial.y2[0] !== new_y2[0] ||
|
|
144
210
|
ranges.initial.y2[1] !== new_y2[1]) {
|
|
145
211
|
ranges = {
|
|
146
|
-
initial: { x: new_x, y: new_y, y2: new_y2 },
|
|
147
|
-
current: { x: new_x, y: new_y, y2: new_y2 },
|
|
212
|
+
initial: { x: new_x, x2: new_x2, y: new_y, y2: new_y2 },
|
|
213
|
+
current: { x: new_x, x2: new_x2, y: new_y, y2: new_y2 },
|
|
148
214
|
};
|
|
149
215
|
}
|
|
150
216
|
});
|
|
@@ -157,6 +223,7 @@ $effect(() => {
|
|
|
157
223
|
? calc_auto_padding({
|
|
158
224
|
padding,
|
|
159
225
|
default_padding,
|
|
226
|
+
x2_axis: { ...x2_axis, tick_values: ticks.x2 },
|
|
160
227
|
y_axis: { ...y_axis, tick_values: ticks.y },
|
|
161
228
|
y2_axis: { ...y2_axis, tick_values: ticks.y2 },
|
|
162
229
|
})
|
|
@@ -164,15 +231,23 @@ $effect(() => {
|
|
|
164
231
|
// Expand right padding if y2 ticks are shown (only for vertical orientation)
|
|
165
232
|
if (width && height && y2_series.length && ticks.y2.length &&
|
|
166
233
|
orientation === `vertical`) {
|
|
167
|
-
const y2_tick_width = Math.max(0, ...ticks.y2.map((tick) => measure_text_width(format_value(tick, y2_axis.format), `12px sans-serif`)));
|
|
168
234
|
// Need space for: tick shift + tick width + gap (30px) + label space (20px if present)
|
|
169
235
|
// When ticks are inside, they don't contribute to padding
|
|
170
236
|
const inside = y2_axis.tick?.label?.inside ?? false;
|
|
171
237
|
const tick_shift = inside ? 0 : (y2_axis.tick?.label?.shift?.x ?? 0) + 8;
|
|
172
|
-
const tick_width_contribution = inside ? 0 :
|
|
238
|
+
const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max;
|
|
173
239
|
const label_space = y2_axis.label ? 20 : 0;
|
|
174
240
|
new_pad.r = Math.max(new_pad.r, tick_shift + tick_width_contribution + 30 + label_space);
|
|
175
241
|
}
|
|
242
|
+
// Expand top padding if x2 ticks are shown (only for vertical orientation)
|
|
243
|
+
if (width && height && x2_series.length && ticks.x2.length &&
|
|
244
|
+
orientation === `vertical`) {
|
|
245
|
+
const inside = x2_axis.tick?.label?.inside ?? false;
|
|
246
|
+
const tick_shift = inside ? 0 : Math.abs(x2_axis.tick?.label?.shift?.y ?? 0) + 5;
|
|
247
|
+
const tick_height = inside ? 0 : 16;
|
|
248
|
+
const label_space = x2_axis.label ? 20 : 0;
|
|
249
|
+
new_pad.t = Math.max(new_pad.t, tick_shift + tick_height + 30 + label_space);
|
|
250
|
+
}
|
|
176
251
|
// Only update if padding actually changed (prevents infinite loop)
|
|
177
252
|
if (pad.t !== new_pad.t || pad.b !== new_pad.b || pad.l !== new_pad.l ||
|
|
178
253
|
pad.r !== new_pad.r)
|
|
@@ -186,6 +261,10 @@ let scales = $derived({
|
|
|
186
261
|
pad.l,
|
|
187
262
|
width - pad.r,
|
|
188
263
|
]),
|
|
264
|
+
x2: create_scale(x2_axis.scale_type ?? `linear`, ranges.current.x2, [
|
|
265
|
+
pad.l,
|
|
266
|
+
width - pad.r,
|
|
267
|
+
]),
|
|
189
268
|
y: create_scale(y_axis.scale_type ?? `linear`, ranges.current.y, [
|
|
190
269
|
height - pad.b,
|
|
191
270
|
pad.t,
|
|
@@ -217,23 +296,45 @@ let all_size_values = $derived(visible_series
|
|
|
217
296
|
let color_scale_fn = $derived(create_color_scale(color_scale, auto_color_range));
|
|
218
297
|
// Size scale function (using shared utility)
|
|
219
298
|
let size_scale_fn = $derived(create_size_scale(size_scale, all_size_values));
|
|
299
|
+
// Auto-generate tick labels for categorical data (unless user provides explicit ticks)
|
|
300
|
+
// In vertical mode categories are on x-axis; in horizontal mode on y-axis
|
|
301
|
+
let cat_axis = $derived(orientation === `horizontal` ? `y` : `x`);
|
|
302
|
+
let effective_cat_ticks = $derived.by(() => {
|
|
303
|
+
if (!category_list.length)
|
|
304
|
+
return undefined;
|
|
305
|
+
// Only respect user ticks when they're a Record (custom label mapping),
|
|
306
|
+
// not a number (tick count) or array (tick positions)
|
|
307
|
+
const user_ticks = cat_axis === `x` ? x_axis.ticks : y_axis.ticks;
|
|
308
|
+
if (user_ticks != null && typeof user_ticks === `object` &&
|
|
309
|
+
!Array.isArray(user_ticks))
|
|
310
|
+
return user_ticks;
|
|
311
|
+
return Object.fromEntries(category_list.map((cat, idx) => [idx, cat]));
|
|
312
|
+
});
|
|
220
313
|
// Ticks
|
|
221
314
|
let ticks = $derived({
|
|
222
315
|
x: width && height
|
|
223
|
-
? generate_ticks(ranges.current.x, x_axis.scale_type ?? `linear`, x_axis.ticks, scales.x, {
|
|
224
|
-
default_count: 8,
|
|
225
|
-
})
|
|
316
|
+
? (category_indices && cat_axis === `x` ? category_indices : generate_ticks(ranges.current.x, x_axis.scale_type ?? `linear`, x_axis.ticks, scales.x, { default_count: 8 }))
|
|
226
317
|
: [],
|
|
227
318
|
y: width && height
|
|
228
|
-
? generate_ticks(ranges.current.y, y_axis.scale_type ?? `linear`, y_axis.ticks, scales.y, {
|
|
229
|
-
default_count: 6,
|
|
230
|
-
})
|
|
319
|
+
? (category_indices && cat_axis === `y` ? category_indices : generate_ticks(ranges.current.y, y_axis.scale_type ?? `linear`, y_axis.ticks, scales.y, { default_count: 6 }))
|
|
231
320
|
: [],
|
|
232
321
|
y2: width && height && y2_series.length > 0 && orientation === `vertical`
|
|
233
322
|
? generate_ticks(ranges.current.y2, y2_axis.scale_type ?? `linear`, y2_axis.ticks, scales.y2, {
|
|
234
323
|
default_count: 6,
|
|
235
324
|
})
|
|
236
325
|
: [],
|
|
326
|
+
x2: width && height && x2_series.length > 0 && orientation === `vertical`
|
|
327
|
+
? generate_ticks(ranges.current.x2, x2_axis.scale_type ?? `linear`, x2_axis.ticks, scales.x2, {
|
|
328
|
+
default_count: 8,
|
|
329
|
+
})
|
|
330
|
+
: [],
|
|
331
|
+
});
|
|
332
|
+
// Cache measured tick-label widths so expensive canvas text measurement
|
|
333
|
+
// only runs when ticks/format change, not on every template rerender.
|
|
334
|
+
let tick_label_widths = $derived({
|
|
335
|
+
y_max: measure_max_tick_width(ticks.y, y_axis.format ?? ``),
|
|
336
|
+
y2_max: measure_max_tick_width(ticks.y2, y2_axis.format ?? ``),
|
|
337
|
+
x2_max: measure_max_tick_width(ticks.x2, x2_axis.format ?? ``),
|
|
237
338
|
});
|
|
238
339
|
// Zoom drag state
|
|
239
340
|
let drag_state = $state({ start: null, current: null, bounds: null });
|
|
@@ -258,6 +359,8 @@ const on_window_mouse_up = () => {
|
|
|
258
359
|
const y2 = scales.y.invert(drag_state.current.y);
|
|
259
360
|
const y2_1 = scales.y2.invert(drag_state.start.y);
|
|
260
361
|
const y2_2 = scales.y2.invert(drag_state.current.y);
|
|
362
|
+
const x2a_1_raw = scales.x2.invert(drag_state.start.x);
|
|
363
|
+
const x2a_2_raw = scales.x2.invert(drag_state.current.x);
|
|
261
364
|
const dx = Math.abs(drag_state.start.x - drag_state.current.x);
|
|
262
365
|
const dy = Math.abs(drag_state.start.y - drag_state.current.y);
|
|
263
366
|
let xr1, xr2;
|
|
@@ -271,9 +374,26 @@ const on_window_mouse_up = () => {
|
|
|
271
374
|
}
|
|
272
375
|
else
|
|
273
376
|
[xr1, xr2] = [NaN, NaN]; // bail: mixed types
|
|
377
|
+
let x2r1, x2r2;
|
|
378
|
+
if (x2a_1_raw instanceof Date && x2a_2_raw instanceof Date) {
|
|
379
|
+
;
|
|
380
|
+
[x2r1, x2r2] = [x2a_1_raw.getTime(), x2a_2_raw.getTime()];
|
|
381
|
+
}
|
|
382
|
+
else if (typeof x2a_1_raw === `number` && typeof x2a_2_raw === `number`) {
|
|
383
|
+
;
|
|
384
|
+
[x2r1, x2r2] = [x2a_1_raw, x2a_2_raw];
|
|
385
|
+
}
|
|
386
|
+
else
|
|
387
|
+
[x2r1, x2r2] = [NaN, NaN];
|
|
274
388
|
if (dx > 5 && dy > 5 && Number.isFinite(xr1) && Number.isFinite(xr2)) {
|
|
275
389
|
// Update axis ranges to trigger reactivity and prevent effect from overriding
|
|
276
390
|
x_axis = { ...x_axis, range: [Math.min(xr1, xr2), Math.max(xr1, xr2)] };
|
|
391
|
+
if (x2_series.length > 0 && Number.isFinite(x2r1) && Number.isFinite(x2r2)) {
|
|
392
|
+
x2_axis = {
|
|
393
|
+
...x2_axis,
|
|
394
|
+
range: [Math.min(x2r1, x2r2), Math.max(x2r1, x2r2)],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
277
397
|
y_axis = { ...y_axis, range: [Math.min(y1, y2), Math.max(y1, y2)] };
|
|
278
398
|
y2_axis = { ...y2_axis, range: [Math.min(y2_1, y2_2), Math.max(y2_1, y2_2)] };
|
|
279
399
|
}
|
|
@@ -292,9 +412,11 @@ const on_pan_move = (evt) => {
|
|
|
292
412
|
// Convert pixel delta to data delta (note: drag direction is inverted for natural pan feel)
|
|
293
413
|
const sensitivity = pan?.drag_sensitivity ?? 1;
|
|
294
414
|
const x_delta = pixels_to_data_delta(-dx * sensitivity, pan_drag_state.initial_x_range, chart_width);
|
|
415
|
+
const x2_delta = pixels_to_data_delta(-dx * sensitivity, pan_drag_state.initial_x2_range, chart_width);
|
|
295
416
|
const y_delta = pixels_to_data_delta(dy * sensitivity, pan_drag_state.initial_y_range, chart_height);
|
|
296
417
|
const y2_delta = pixels_to_data_delta(dy * sensitivity, pan_drag_state.initial_y2_range, chart_height);
|
|
297
418
|
ranges.current.x = pan_range(pan_drag_state.initial_x_range, x_delta);
|
|
419
|
+
ranges.current.x2 = pan_range(pan_drag_state.initial_x2_range, x2_delta);
|
|
298
420
|
ranges.current.y = pan_range(pan_drag_state.initial_y_range, y_delta);
|
|
299
421
|
ranges.current.y2 = pan_range(pan_drag_state.initial_y2_range, y2_delta);
|
|
300
422
|
};
|
|
@@ -315,6 +437,7 @@ function handle_mouse_down(evt) {
|
|
|
315
437
|
pan_drag_state = {
|
|
316
438
|
start: { x: evt.clientX, y: evt.clientY },
|
|
317
439
|
initial_x_range: [...ranges.current.x],
|
|
440
|
+
initial_x2_range: [...ranges.current.x2],
|
|
318
441
|
initial_y_range: [...ranges.current.y],
|
|
319
442
|
initial_y2_range: [...ranges.current.y2],
|
|
320
443
|
};
|
|
@@ -343,10 +466,12 @@ function handle_wheel(evt) {
|
|
|
343
466
|
const sensitivity = pan?.wheel_sensitivity ?? 1;
|
|
344
467
|
// Determine pan direction based on wheel delta
|
|
345
468
|
const x_delta = pixels_to_data_delta(evt.deltaX * sensitivity, ranges.current.x, chart_width);
|
|
469
|
+
const x2_delta = pixels_to_data_delta(evt.deltaX * sensitivity, ranges.current.x2, chart_width);
|
|
346
470
|
const y_delta = pixels_to_data_delta(evt.deltaY * sensitivity, ranges.current.y, chart_height);
|
|
347
471
|
const y2_delta = pixels_to_data_delta(evt.deltaY * sensitivity, ranges.current.y2, chart_height);
|
|
348
472
|
if (Math.abs(evt.deltaX) > Math.abs(evt.deltaY)) {
|
|
349
473
|
ranges.current.x = pan_range(ranges.current.x, x_delta);
|
|
474
|
+
ranges.current.x2 = pan_range(ranges.current.x2, x2_delta);
|
|
350
475
|
}
|
|
351
476
|
else {
|
|
352
477
|
ranges.current.y = pan_range(ranges.current.y, y_delta);
|
|
@@ -363,6 +488,7 @@ function handle_touch_start(evt) {
|
|
|
363
488
|
touch_state = {
|
|
364
489
|
start_touches: touches.map((touch) => ({ x: touch.clientX, y: touch.clientY })),
|
|
365
490
|
initial_x_range: [...ranges.current.x],
|
|
491
|
+
initial_x2_range: [...ranges.current.x2],
|
|
366
492
|
initial_y_range: [...ranges.current.y],
|
|
367
493
|
initial_y2_range: [...ranges.current.y2],
|
|
368
494
|
};
|
|
@@ -394,16 +520,23 @@ function handle_touch_move(evt) {
|
|
|
394
520
|
// Pinch zoom centered on gesture center
|
|
395
521
|
// Divide by scale so spread (scale > 1) = smaller span (zoom in)
|
|
396
522
|
const x_span = touch_state.initial_x_range[1] - touch_state.initial_x_range[0];
|
|
523
|
+
const x2_span = touch_state.initial_x2_range[1] -
|
|
524
|
+
touch_state.initial_x2_range[0];
|
|
397
525
|
const y_span = touch_state.initial_y_range[1] - touch_state.initial_y_range[0];
|
|
398
526
|
const y2_span = touch_state.initial_y2_range[1] -
|
|
399
527
|
touch_state.initial_y2_range[0];
|
|
400
528
|
const x_center = (touch_state.initial_x_range[0] + touch_state.initial_x_range[1]) / 2;
|
|
529
|
+
const x2_center = (touch_state.initial_x2_range[0] + touch_state.initial_x2_range[1]) / 2;
|
|
401
530
|
const y_center = (touch_state.initial_y_range[0] + touch_state.initial_y_range[1]) / 2;
|
|
402
531
|
const y2_center = (touch_state.initial_y2_range[0] + touch_state.initial_y2_range[1]) / 2;
|
|
403
532
|
ranges.current.x = [
|
|
404
533
|
x_center - x_span / scale / 2,
|
|
405
534
|
x_center + x_span / scale / 2,
|
|
406
535
|
];
|
|
536
|
+
ranges.current.x2 = [
|
|
537
|
+
x2_center - x2_span / scale / 2,
|
|
538
|
+
x2_center + x2_span / scale / 2,
|
|
539
|
+
];
|
|
407
540
|
ranges.current.y = [
|
|
408
541
|
y_center - y_span / scale / 2,
|
|
409
542
|
y_center + y_span / scale / 2,
|
|
@@ -416,9 +549,11 @@ function handle_touch_move(evt) {
|
|
|
416
549
|
else {
|
|
417
550
|
// Pan
|
|
418
551
|
const x_delta = pixels_to_data_delta(-dx, touch_state.initial_x_range, chart_width);
|
|
552
|
+
const x2_delta = pixels_to_data_delta(-dx, touch_state.initial_x2_range, chart_width);
|
|
419
553
|
const y_delta = pixels_to_data_delta(dy, touch_state.initial_y_range, chart_height);
|
|
420
554
|
const y2_delta = pixels_to_data_delta(dy, touch_state.initial_y2_range, chart_height);
|
|
421
555
|
ranges.current.x = pan_range(touch_state.initial_x_range, x_delta);
|
|
556
|
+
ranges.current.x2 = pan_range(touch_state.initial_x2_range, x2_delta);
|
|
422
557
|
ranges.current.y = pan_range(touch_state.initial_y_range, y_delta);
|
|
423
558
|
ranges.current.y2 = pan_range(touch_state.initial_y2_range, y2_delta);
|
|
424
559
|
}
|
|
@@ -499,13 +634,15 @@ function toggle_group_visibility(_group_name, series_indices) {
|
|
|
499
634
|
let bar_points_for_placement = $derived.by(() => {
|
|
500
635
|
if (!width || !height || !visible_series.length)
|
|
501
636
|
return [];
|
|
502
|
-
return
|
|
637
|
+
return internal_series.flatMap((srs, series_idx) => {
|
|
638
|
+
if (!(srs?.visible ?? true))
|
|
639
|
+
return [];
|
|
503
640
|
const is_line = srs.render_mode === `line`;
|
|
504
|
-
// Use original series index to look up stacked_offsets
|
|
505
|
-
const series_idx = series.indexOf(srs);
|
|
506
641
|
const series_offsets = stacked_offsets[series_idx] ?? [];
|
|
507
642
|
const use_y2 = srs.y_axis === `y2`;
|
|
508
643
|
const y_scale = use_y2 ? scales.y2 : scales.y;
|
|
644
|
+
const use_x2_pl = srs.x_axis === `x2`;
|
|
645
|
+
const x_scale_pl = use_x2_pl ? scales.x2 : scales.x;
|
|
509
646
|
return srs.x
|
|
510
647
|
.map((x_val, bar_idx) => {
|
|
511
648
|
const y_val = srs.y[bar_idx];
|
|
@@ -513,8 +650,8 @@ let bar_points_for_placement = $derived.by(() => {
|
|
|
513
650
|
? (series_offsets[bar_idx] ?? 0)
|
|
514
651
|
: 0;
|
|
515
652
|
const [bar_x, bar_y] = orientation === `vertical`
|
|
516
|
-
? [
|
|
517
|
-
: [
|
|
653
|
+
? [x_scale_pl(x_val), y_scale(base + y_val)]
|
|
654
|
+
: [x_scale_pl(base + y_val), scales.y(x_val)];
|
|
518
655
|
return { x: bar_x, y: bar_y };
|
|
519
656
|
})
|
|
520
657
|
.filter(({ x, y }) => isFinite(x) && isFinite(y));
|
|
@@ -574,7 +711,7 @@ $effect(() => {
|
|
|
574
711
|
let hover_info = $state(null);
|
|
575
712
|
let tooltip_el = $state();
|
|
576
713
|
function get_bar_data(series_idx, bar_idx, color) {
|
|
577
|
-
const srs =
|
|
714
|
+
const srs = internal_series[series_idx];
|
|
578
715
|
const [x, y] = [srs.x[bar_idx], srs.y[bar_idx]];
|
|
579
716
|
const [orient_x, orient_y] = orientation === `horizontal` ? [y, x] : [x, y];
|
|
580
717
|
const metadata = Array.isArray(srs.metadata)
|
|
@@ -582,9 +719,52 @@ function get_bar_data(series_idx, bar_idx, color) {
|
|
|
582
719
|
: srs.metadata;
|
|
583
720
|
const label = srs.labels?.[bar_idx] ?? null;
|
|
584
721
|
const active_y_axis = srs.y_axis ?? `y1`;
|
|
585
|
-
const
|
|
586
|
-
|
|
722
|
+
const active_x_axis = srs.x_axis ?? `x1`;
|
|
723
|
+
const category_label = category_list[x];
|
|
724
|
+
const coords = {
|
|
725
|
+
x,
|
|
726
|
+
y,
|
|
727
|
+
orient_x,
|
|
728
|
+
orient_y,
|
|
729
|
+
x_axis: active_x_axis === `x2` ? x2_axis : x_axis,
|
|
730
|
+
x2_axis,
|
|
731
|
+
y_axis: active_y_axis === `y2` ? y2_axis : y_axis,
|
|
732
|
+
y2_axis,
|
|
733
|
+
};
|
|
734
|
+
return {
|
|
735
|
+
...coords,
|
|
736
|
+
metadata,
|
|
737
|
+
color,
|
|
738
|
+
label,
|
|
739
|
+
series_idx,
|
|
740
|
+
bar_idx,
|
|
741
|
+
active_y_axis,
|
|
742
|
+
active_x_axis,
|
|
743
|
+
category_label,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
// Find the point closest to the cursor on a polyline overlay (O(n) scan).
|
|
747
|
+
function find_closest_point(evt, points) {
|
|
748
|
+
const svg_el = evt.target.closest(`svg`);
|
|
749
|
+
if (!svg_el)
|
|
750
|
+
return null;
|
|
751
|
+
const rect = svg_el.getBoundingClientRect();
|
|
752
|
+
const mx = evt.clientX - rect.left;
|
|
753
|
+
const my = evt.clientY - rect.top;
|
|
754
|
+
let best = null;
|
|
755
|
+
let best_dist = Infinity;
|
|
756
|
+
for (const pt of points) {
|
|
757
|
+
const dist = (pt.x - mx) ** 2 + (pt.y - my) ** 2;
|
|
758
|
+
if (dist < best_dist) {
|
|
759
|
+
best_dist = dist;
|
|
760
|
+
best = pt;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return best;
|
|
587
764
|
}
|
|
765
|
+
const line_point_fill = (pt, series_color) => pt.color_value != null
|
|
766
|
+
? color_scale_fn(pt.color_value)
|
|
767
|
+
: pt.point_style?.fill ?? series_color;
|
|
588
768
|
const handle_bar_hover = (series_idx, bar_idx, color) => (event) => {
|
|
589
769
|
hovered = true;
|
|
590
770
|
hover_info = get_bar_data(series_idx, bar_idx, color);
|
|
@@ -595,14 +775,14 @@ const handle_bar_hover = (series_idx, bar_idx, color) => (event) => {
|
|
|
595
775
|
let stacked_offsets = $derived.by(() => {
|
|
596
776
|
if (mode !== `stacked`)
|
|
597
777
|
return [];
|
|
598
|
-
const max_len = Math.max(0, ...
|
|
599
|
-
const offsets =
|
|
778
|
+
const max_len = Math.max(0, ...internal_series.map((srs) => srs.y.length));
|
|
779
|
+
const offsets = internal_series.map(() => Array.from({ length: max_len }, () => 0));
|
|
600
780
|
// Separate accumulators for y1 and y2 axes
|
|
601
781
|
const y1_pos_acc = Array.from({ length: max_len }, () => 0);
|
|
602
782
|
const y1_neg_acc = Array.from({ length: max_len }, () => 0);
|
|
603
783
|
const y2_pos_acc = Array.from({ length: max_len }, () => 0);
|
|
604
784
|
const y2_neg_acc = Array.from({ length: max_len }, () => 0);
|
|
605
|
-
|
|
785
|
+
internal_series.forEach((srs, series_idx) => {
|
|
606
786
|
if (!(srs?.visible ?? true) || srs.render_mode === `line`)
|
|
607
787
|
return;
|
|
608
788
|
const use_y2 = srs.y_axis === `y2`;
|
|
@@ -621,7 +801,7 @@ let stacked_offsets = $derived.by(() => {
|
|
|
621
801
|
let group_info = $derived.by(() => {
|
|
622
802
|
if (mode !== `grouped`)
|
|
623
803
|
return { bar_series_count: 0, bar_series_indices: [] };
|
|
624
|
-
const bar_series_indices =
|
|
804
|
+
const bar_series_indices = internal_series
|
|
625
805
|
.map((srs, idx) => (srs?.visible ?? true) && srs.render_mode !== `line` ? idx : -1)
|
|
626
806
|
.filter((idx) => idx >= 0);
|
|
627
807
|
return { bar_series_count: bar_series_indices.length, bar_series_indices };
|
|
@@ -632,11 +812,21 @@ $effect(() => {
|
|
|
632
812
|
});
|
|
633
813
|
// State accessors for shared axis change handler
|
|
634
814
|
const axis_state = {
|
|
635
|
-
get_axis: (axis) =>
|
|
815
|
+
get_axis: (axis) => {
|
|
816
|
+
if (axis === `x`)
|
|
817
|
+
return x_axis;
|
|
818
|
+
if (axis === `x2`)
|
|
819
|
+
return x2_axis;
|
|
820
|
+
if (axis === `y`)
|
|
821
|
+
return y_axis;
|
|
822
|
+
return y2_axis;
|
|
823
|
+
},
|
|
636
824
|
set_axis: (axis, config) => {
|
|
637
825
|
// Spread into existing state to preserve merged type structure
|
|
638
826
|
if (axis === `x`)
|
|
639
827
|
x_axis = { ...x_axis, ...config };
|
|
828
|
+
else if (axis === `x2`)
|
|
829
|
+
x2_axis = { ...x2_axis, ...config };
|
|
640
830
|
else if (axis === `y`)
|
|
641
831
|
y_axis = { ...y_axis, ...config };
|
|
642
832
|
else
|
|
@@ -674,11 +864,12 @@ $effect(() => {
|
|
|
674
864
|
<ReferenceLine
|
|
675
865
|
ref_line={line}
|
|
676
866
|
line_idx={line.idx}
|
|
677
|
-
x_min={ranges.current.x[0]}
|
|
678
|
-
x_max={ranges.current.x[1]}
|
|
867
|
+
x_min={line.x_axis === `x2` ? ranges.current.x2[0] : ranges.current.x[0]}
|
|
868
|
+
x_max={line.x_axis === `x2` ? ranges.current.x2[1] : ranges.current.x[1]}
|
|
679
869
|
y_min={line.y_axis === `y2` ? ranges.current.y2[0] : ranges.current.y[0]}
|
|
680
870
|
y_max={line.y_axis === `y2` ? ranges.current.y2[1] : ranges.current.y[1]}
|
|
681
871
|
x_scale={scales.x}
|
|
872
|
+
x2_scale={scales.x2}
|
|
682
873
|
y_scale={scales.y}
|
|
683
874
|
y2_scale={scales.y2}
|
|
684
875
|
{clip_path_id}
|
|
@@ -729,6 +920,8 @@ $effect(() => {
|
|
|
729
920
|
<svg
|
|
730
921
|
bind:this={svg_element}
|
|
731
922
|
role="application"
|
|
923
|
+
aria-label={rest[`aria-label`] ??
|
|
924
|
+
([x_axis.label, y_axis.label].filter(Boolean).join(` vs `) || `Bar chart`)}
|
|
732
925
|
tabindex="0"
|
|
733
926
|
onfocusin={() => (is_focused = true)}
|
|
734
927
|
onfocusout={() => (is_focused = false)}
|
|
@@ -736,10 +929,12 @@ $effect(() => {
|
|
|
736
929
|
ondblclick={() => {
|
|
737
930
|
// Reset zoom to initial ranges (undo any pan/zoom)
|
|
738
931
|
ranges.current.x = [...ranges.initial.x] as [number, number]
|
|
932
|
+
ranges.current.x2 = [...ranges.initial.x2] as [number, number]
|
|
739
933
|
ranges.current.y = [...ranges.initial.y] as [number, number]
|
|
740
934
|
ranges.current.y2 = [...ranges.initial.y2] as [number, number]
|
|
741
935
|
// Also reset axis props so future data changes recalculate auto ranges
|
|
742
936
|
x_axis = { ...x_axis, range: [null, null] }
|
|
937
|
+
x2_axis = { ...x2_axis, range: [null, null] }
|
|
743
938
|
y_axis = { ...y_axis, range: [null, null] }
|
|
744
939
|
y2_axis = { ...y2_axis, range: [null, null] }
|
|
745
940
|
}}
|
|
@@ -759,26 +954,19 @@ $effect(() => {
|
|
|
759
954
|
? `grab`
|
|
760
955
|
: `crosshair`}
|
|
761
956
|
>
|
|
762
|
-
|
|
763
|
-
{#if drag_state.start && drag_state.current && isFinite(drag_state.start.x) &&
|
|
764
|
-
isFinite(drag_state.start.y) && isFinite(drag_state.current.x) &&
|
|
765
|
-
isFinite(drag_state.current.y)}
|
|
766
|
-
{@const x = Math.min(drag_state.start.x, drag_state.current.x)}
|
|
767
|
-
{@const y = Math.min(drag_state.start.y, drag_state.current.y)}
|
|
768
|
-
{@const rect_w = Math.abs(drag_state.start.x - drag_state.current.x)}
|
|
769
|
-
{@const rect_h = Math.abs(drag_state.start.y - drag_state.current.y)}
|
|
770
|
-
<rect class="zoom-rect" {x} {y} width={rect_w} height={rect_h} />
|
|
771
|
-
{/if}
|
|
957
|
+
<ZoomRect start={drag_state.start} current={drag_state.current} />
|
|
772
958
|
|
|
773
959
|
<!-- User content (custom overlays, reference lines, etc.) -->
|
|
774
960
|
{@render user_content?.({
|
|
775
961
|
height,
|
|
776
962
|
width,
|
|
777
963
|
x_scale_fn: scales.x,
|
|
964
|
+
x2_scale_fn: scales.x2,
|
|
778
965
|
y_scale_fn: scales.y,
|
|
779
966
|
y2_scale_fn: scales.y2,
|
|
780
967
|
pad,
|
|
781
968
|
x_range: ranges.current.x,
|
|
969
|
+
x2_range: ranges.current.x2,
|
|
782
970
|
y_range: ranges.current.y,
|
|
783
971
|
y2_range: ranges.current.y2,
|
|
784
972
|
fullscreen,
|
|
@@ -833,37 +1021,106 @@ $effect(() => {
|
|
|
833
1021
|
? `rotate(${rotation}, ${shift_x}, ${text_y})`
|
|
834
1022
|
: undefined}
|
|
835
1023
|
>
|
|
836
|
-
{
|
|
1024
|
+
{
|
|
1025
|
+
get_tick_label(
|
|
1026
|
+
tick as number,
|
|
1027
|
+
cat_axis === `x` ? effective_cat_ticks : x_axis.ticks,
|
|
1028
|
+
) ??
|
|
1029
|
+
format_value(tick, x_axis.format)
|
|
1030
|
+
}
|
|
837
1031
|
</text>
|
|
838
1032
|
</g>
|
|
839
1033
|
{/if}
|
|
840
1034
|
{/each}
|
|
841
1035
|
{#if x_axis.label || x_axis.options?.length}
|
|
842
|
-
{@const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
options={x_axis.options}
|
|
855
|
-
selected_key={x_axis.selected_key}
|
|
856
|
-
loading={axis_loading === `x`}
|
|
857
|
-
axis_type="x"
|
|
858
|
-
color={x_axis.color}
|
|
859
|
-
on_select={(key) => handle_axis_change(`x`, key)}
|
|
860
|
-
class="axis-label x-label"
|
|
861
|
-
/>
|
|
862
|
-
</div>
|
|
863
|
-
</foreignObject>
|
|
1036
|
+
{@const { label_shift, label = ``, options, selected_key, color } = x_axis}
|
|
1037
|
+
<AxisLabel
|
|
1038
|
+
x={pad.l + chart_width / 2 + (label_shift?.x ?? 0)}
|
|
1039
|
+
y={height - (pad.b / 3) + (label_shift?.y ?? 0)}
|
|
1040
|
+
{label}
|
|
1041
|
+
{options}
|
|
1042
|
+
{selected_key}
|
|
1043
|
+
loading={axis_loading === `x`}
|
|
1044
|
+
axis_type="x"
|
|
1045
|
+
{color}
|
|
1046
|
+
on_select={(key) => handle_axis_change(`x`, key)}
|
|
1047
|
+
/>
|
|
864
1048
|
{/if}
|
|
865
1049
|
</g>
|
|
866
1050
|
|
|
1051
|
+
<!-- X2-axis (Top) -->
|
|
1052
|
+
<!-- Note: x2 axis is only supported for vertical orientation -->
|
|
1053
|
+
{#if x2_series.length > 0 && orientation === `vertical`}
|
|
1054
|
+
<g class="x2-axis">
|
|
1055
|
+
<line
|
|
1056
|
+
x1={pad.l}
|
|
1057
|
+
x2={width - pad.r}
|
|
1058
|
+
y1={pad.t}
|
|
1059
|
+
y2={pad.t}
|
|
1060
|
+
stroke={x2_axis.color || `var(--border-color, gray)`}
|
|
1061
|
+
stroke-width="1"
|
|
1062
|
+
/>
|
|
1063
|
+
{#each ticks.x2 as tick (tick)}
|
|
1064
|
+
{@const tick_x = scales.x2(tick as number)}
|
|
1065
|
+
{#if isFinite(tick_x)}
|
|
1066
|
+
{@const rotation = x2_axis.tick?.label?.rotation ?? 0}
|
|
1067
|
+
{@const shift_x = x2_axis.tick?.label?.shift?.x ?? 0}
|
|
1068
|
+
{@const shift_y = x2_axis.tick?.label?.shift?.y ?? 0}
|
|
1069
|
+
{@const inside = x2_axis.tick?.label?.inside ?? false}
|
|
1070
|
+
{@const base_y = inside ? 8 : (rotation !== 0 ? -8 : -18)}
|
|
1071
|
+
{@const text_y = base_y + shift_y}
|
|
1072
|
+
{@const text_anchor = rotation !== 0 ? (inside ? `start` : `end`) : `middle`}
|
|
1073
|
+
{@const dominant_baseline = inside ? `hanging` : `auto`}
|
|
1074
|
+
<g class="tick" transform="translate({tick_x}, {pad.t})">
|
|
1075
|
+
{#if display.x2_grid}
|
|
1076
|
+
<line
|
|
1077
|
+
y1="0"
|
|
1078
|
+
y2={height - pad.b - pad.t}
|
|
1079
|
+
{...DEFAULT_GRID_STYLE}
|
|
1080
|
+
{...(x2_axis.grid_style ?? {})}
|
|
1081
|
+
/>
|
|
1082
|
+
{/if}
|
|
1083
|
+
<line
|
|
1084
|
+
y1={inside ? 5 : 0}
|
|
1085
|
+
y2={inside ? 0 : -5}
|
|
1086
|
+
stroke={x2_axis.color || `var(--border-color, gray)`}
|
|
1087
|
+
stroke-width="1"
|
|
1088
|
+
/>
|
|
1089
|
+
<text
|
|
1090
|
+
x={shift_x}
|
|
1091
|
+
y={text_y}
|
|
1092
|
+
text-anchor={text_anchor}
|
|
1093
|
+
dominant-baseline={dominant_baseline}
|
|
1094
|
+
fill={x2_axis.color || `var(--text-color)`}
|
|
1095
|
+
transform={rotation !== 0
|
|
1096
|
+
? `rotate(${rotation}, ${shift_x}, ${text_y})`
|
|
1097
|
+
: undefined}
|
|
1098
|
+
>
|
|
1099
|
+
{
|
|
1100
|
+
get_tick_label(tick as number, x2_axis.ticks) ??
|
|
1101
|
+
format_value(tick, x2_axis.format)
|
|
1102
|
+
}
|
|
1103
|
+
</text>
|
|
1104
|
+
</g>
|
|
1105
|
+
{/if}
|
|
1106
|
+
{/each}
|
|
1107
|
+
{#if x2_axis.label || x2_axis.options?.length}
|
|
1108
|
+
{@const { label_shift, label = ``, options, selected_key, color } = x2_axis}
|
|
1109
|
+
<AxisLabel
|
|
1110
|
+
x={pad.l + chart_width / 2 + (label_shift?.x ?? 0)}
|
|
1111
|
+
y={Math.max(12, pad.t - (label_shift?.y ?? 40))}
|
|
1112
|
+
{label}
|
|
1113
|
+
{options}
|
|
1114
|
+
{selected_key}
|
|
1115
|
+
loading={axis_loading === `x2`}
|
|
1116
|
+
axis_type="x2"
|
|
1117
|
+
{color}
|
|
1118
|
+
on_select={(key) => handle_axis_change(`x2`, key)}
|
|
1119
|
+
/>
|
|
1120
|
+
{/if}
|
|
1121
|
+
</g>
|
|
1122
|
+
{/if}
|
|
1123
|
+
|
|
867
1124
|
<!-- Y-axis -->
|
|
868
1125
|
<g class="y-axis">
|
|
869
1126
|
<line
|
|
@@ -909,47 +1166,36 @@ $effect(() => {
|
|
|
909
1166
|
? `rotate(${rotation}, ${text_x}, ${shift_y})`
|
|
910
1167
|
: undefined}
|
|
911
1168
|
>
|
|
912
|
-
{
|
|
1169
|
+
{
|
|
1170
|
+
get_tick_label(
|
|
1171
|
+
tick as number,
|
|
1172
|
+
cat_axis === `y` ? effective_cat_ticks : y_axis.ticks,
|
|
1173
|
+
) ??
|
|
1174
|
+
format_value(tick, y_axis.format)
|
|
1175
|
+
}
|
|
913
1176
|
</text>
|
|
914
1177
|
</g>
|
|
915
1178
|
{/if}
|
|
916
1179
|
{/each}
|
|
917
1180
|
{#if y_axis.label || y_axis.options?.length}
|
|
918
|
-
{@const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
)
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
height={AXIS_LABEL_CONTAINER.height}
|
|
937
|
-
style="overflow: visible; pointer-events: none"
|
|
938
|
-
transform="rotate(-90, {y_label_x}, {y_label_y})"
|
|
939
|
-
>
|
|
940
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="pointer-events: auto">
|
|
941
|
-
<InteractiveAxisLabel
|
|
942
|
-
label={y_axis.label ?? ``}
|
|
943
|
-
options={y_axis.options}
|
|
944
|
-
selected_key={y_axis.selected_key}
|
|
945
|
-
loading={axis_loading === `y`}
|
|
946
|
-
axis_type="y"
|
|
947
|
-
color={y_axis.color}
|
|
948
|
-
on_select={(key) => handle_axis_change(`y`, key)}
|
|
949
|
-
class="axis-label y-label"
|
|
950
|
-
/>
|
|
951
|
-
</div>
|
|
952
|
-
</foreignObject>
|
|
1181
|
+
{@const { label_shift, label = ``, options, selected_key, color, tick } = y_axis}
|
|
1182
|
+
{@const y_inside = tick?.label?.inside ?? false}
|
|
1183
|
+
<AxisLabel
|
|
1184
|
+
x={Math.max(
|
|
1185
|
+
12,
|
|
1186
|
+
pad.l - (y_inside ? 0 : tick_label_widths.y_max) - LABEL_GAP_DEFAULT,
|
|
1187
|
+
) +
|
|
1188
|
+
(label_shift?.x ?? 0)}
|
|
1189
|
+
y={pad.t + chart_height / 2 + (label_shift?.y ?? 0)}
|
|
1190
|
+
rotate
|
|
1191
|
+
{label}
|
|
1192
|
+
{options}
|
|
1193
|
+
{selected_key}
|
|
1194
|
+
loading={axis_loading === `y`}
|
|
1195
|
+
axis_type="y"
|
|
1196
|
+
{color}
|
|
1197
|
+
on_select={(key) => handle_axis_change(`y`, key)}
|
|
1198
|
+
/>
|
|
953
1199
|
{/if}
|
|
954
1200
|
</g>
|
|
955
1201
|
|
|
@@ -999,51 +1245,33 @@ $effect(() => {
|
|
|
999
1245
|
? `rotate(${rotation}, ${shift_x}, ${shift_y})`
|
|
1000
1246
|
: undefined}
|
|
1001
1247
|
>
|
|
1002
|
-
{
|
|
1248
|
+
{
|
|
1249
|
+
get_tick_label(tick as number, y2_axis.ticks) ??
|
|
1250
|
+
format_value(tick, y2_axis.format)
|
|
1251
|
+
}
|
|
1003
1252
|
</text>
|
|
1004
1253
|
</g>
|
|
1005
1254
|
{/if}
|
|
1006
1255
|
{/each}
|
|
1007
1256
|
{#if y2_axis.label || y2_axis.options?.length}
|
|
1008
|
-
{@const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
<foreignObject
|
|
1027
|
-
x={y2_label_x - AXIS_LABEL_CONTAINER.x_offset}
|
|
1028
|
-
y={y2_label_y - AXIS_LABEL_CONTAINER.y_offset}
|
|
1029
|
-
width={AXIS_LABEL_CONTAINER.width}
|
|
1030
|
-
height={AXIS_LABEL_CONTAINER.height}
|
|
1031
|
-
style="overflow: visible; pointer-events: none"
|
|
1032
|
-
transform="rotate(-90, {y2_label_x}, {y2_label_y})"
|
|
1033
|
-
>
|
|
1034
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="pointer-events: auto">
|
|
1035
|
-
<InteractiveAxisLabel
|
|
1036
|
-
label={y2_axis.label ?? ``}
|
|
1037
|
-
options={y2_axis.options}
|
|
1038
|
-
selected_key={y2_axis.selected_key}
|
|
1039
|
-
loading={axis_loading === `y2`}
|
|
1040
|
-
axis_type="y2"
|
|
1041
|
-
color={y2_axis.color}
|
|
1042
|
-
on_select={(key) => handle_axis_change(`y2`, key)}
|
|
1043
|
-
class="axis-label y2-label"
|
|
1044
|
-
/>
|
|
1045
|
-
</div>
|
|
1046
|
-
</foreignObject>
|
|
1257
|
+
{@const { label_shift, label = ``, options, selected_key, color, tick } =
|
|
1258
|
+
y2_axis}
|
|
1259
|
+
{@const inside = tick?.label?.inside ?? false}
|
|
1260
|
+
{@const tick_shift = inside ? 0 : (tick?.label?.shift?.x ?? 0) + 8}
|
|
1261
|
+
{@const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max}
|
|
1262
|
+
<AxisLabel
|
|
1263
|
+
x={width - pad.r + tick_shift + tick_width_contribution +
|
|
1264
|
+
LABEL_GAP_DEFAULT + (label_shift?.x ?? 0)}
|
|
1265
|
+
y={pad.t + chart_height / 2 + (label_shift?.y ?? 0)}
|
|
1266
|
+
rotate
|
|
1267
|
+
{label}
|
|
1268
|
+
{options}
|
|
1269
|
+
{selected_key}
|
|
1270
|
+
loading={axis_loading === `y2`}
|
|
1271
|
+
axis_type="y2"
|
|
1272
|
+
{color}
|
|
1273
|
+
on_select={(key) => handle_axis_change(`y2`, key)}
|
|
1274
|
+
/>
|
|
1047
1275
|
{/if}
|
|
1048
1276
|
</g>
|
|
1049
1277
|
{/if}
|
|
@@ -1057,43 +1285,34 @@ $effect(() => {
|
|
|
1057
1285
|
|
|
1058
1286
|
<!-- Clipped content: zero lines, bars, and lines -->
|
|
1059
1287
|
<g clip-path="url(#{clip_path_id})">
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
{
|
|
1065
|
-
{
|
|
1066
|
-
|
|
1067
|
-
{
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
{
|
|
1073
|
-
{
|
|
1074
|
-
|
|
1075
|
-
{
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
{
|
|
1081
|
-
|
|
1082
|
-
<line
|
|
1083
|
-
class="zero-line"
|
|
1084
|
-
x1={pad.l}
|
|
1085
|
-
x2={width - pad.r}
|
|
1086
|
-
y1={zero_y2}
|
|
1087
|
-
y2={zero_y2}
|
|
1088
|
-
/>
|
|
1089
|
-
{/if}
|
|
1090
|
-
{/if}
|
|
1288
|
+
<ZeroLines
|
|
1289
|
+
{display}
|
|
1290
|
+
x_scale_fn={scales.x}
|
|
1291
|
+
x2_scale_fn={scales.x2}
|
|
1292
|
+
y_scale_fn={scales.y}
|
|
1293
|
+
y2_scale_fn={scales.y2}
|
|
1294
|
+
x_range={ranges.current.x}
|
|
1295
|
+
x2_range={ranges.current.x2}
|
|
1296
|
+
y_range={ranges.current.y}
|
|
1297
|
+
y2_range={ranges.current.y2}
|
|
1298
|
+
x_scale_type={x_axis.scale_type}
|
|
1299
|
+
x2_scale_type={x2_axis.scale_type}
|
|
1300
|
+
y_scale_type={y_axis.scale_type}
|
|
1301
|
+
y2_scale_type={y2_axis.scale_type}
|
|
1302
|
+
x_is_time={x_axis.format?.startsWith(`%`) ?? false}
|
|
1303
|
+
x2_is_time={x2_axis.format?.startsWith(`%`) ?? false}
|
|
1304
|
+
has_x2={x2_series.length > 0}
|
|
1305
|
+
has_y2={y2_series.length > 0}
|
|
1306
|
+
{width}
|
|
1307
|
+
{height}
|
|
1308
|
+
{pad}
|
|
1309
|
+
/>
|
|
1091
1310
|
|
|
1092
1311
|
<!-- Reference lines: below lines -->
|
|
1093
1312
|
{@render ref_lines_layer(ref_lines_by_z.below_lines)}
|
|
1094
1313
|
|
|
1095
1314
|
<!-- Bars and Lines -->
|
|
1096
|
-
{#each
|
|
1315
|
+
{#each internal_series as srs, series_idx (srs?.id ?? series_idx)}
|
|
1097
1316
|
{#if srs?.visible ?? true}
|
|
1098
1317
|
{@const is_line = srs.render_mode === `line`}
|
|
1099
1318
|
<g
|
|
@@ -1107,6 +1326,8 @@ $effect(() => {
|
|
|
1107
1326
|
{@const line_dash = srs.line_style?.line_dash ?? `none`}
|
|
1108
1327
|
{@const use_y2 = srs.y_axis === `y2`}
|
|
1109
1328
|
{@const y_scale = use_y2 ? scales.y2 : scales.y}
|
|
1329
|
+
{@const use_x2 = srs.x_axis === `x2`}
|
|
1330
|
+
{@const x_scale = use_x2 ? scales.x2 : scales.x}
|
|
1110
1331
|
{@const series_markers = srs.markers ?? DEFAULT_MARKERS}
|
|
1111
1332
|
{@const show_line = series_markers === `line` ||
|
|
1112
1333
|
series_markers === `line+points`}
|
|
@@ -1116,8 +1337,8 @@ $effect(() => {
|
|
|
1116
1337
|
const y_val = srs.y[idx]
|
|
1117
1338
|
// Lines don't stack - they show absolute values (useful for totals/trends)
|
|
1118
1339
|
const plot_x = orientation === `vertical`
|
|
1119
|
-
?
|
|
1120
|
-
:
|
|
1340
|
+
? x_scale(x_val)
|
|
1341
|
+
: x_scale(y_val)
|
|
1121
1342
|
const plot_y = orientation === `vertical`
|
|
1122
1343
|
? y_scale(y_val)
|
|
1123
1344
|
: scales.y(x_val)
|
|
@@ -1148,9 +1369,12 @@ $effect(() => {
|
|
|
1148
1369
|
point_idx: idx,
|
|
1149
1370
|
} as LineSeriesPoint
|
|
1150
1371
|
}).filter((pt) => isFinite(pt.x) && isFinite(pt.y))}
|
|
1151
|
-
{
|
|
1372
|
+
{@const polyline_str = show_line && points.length > 1
|
|
1373
|
+
? points.map((pt) => `${pt.x},${pt.y}`).join(` `)
|
|
1374
|
+
: ``}
|
|
1375
|
+
{#if polyline_str}
|
|
1152
1376
|
<polyline
|
|
1153
|
-
points={
|
|
1377
|
+
points={polyline_str}
|
|
1154
1378
|
fill="none"
|
|
1155
1379
|
stroke={color}
|
|
1156
1380
|
stroke-width={stroke_width}
|
|
@@ -1159,41 +1383,58 @@ $effect(() => {
|
|
|
1159
1383
|
stroke-linecap="round"
|
|
1160
1384
|
/>
|
|
1161
1385
|
{/if}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1386
|
+
{#if polyline_str && !show_points && (on_bar_hover || on_bar_click)}
|
|
1387
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1388
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1165
1389
|
<polyline
|
|
1166
|
-
points={
|
|
1390
|
+
points={polyline_str}
|
|
1167
1391
|
fill="none"
|
|
1168
1392
|
stroke="transparent"
|
|
1169
1393
|
stroke-width={Math.max(10, stroke_width * 3)}
|
|
1170
1394
|
stroke-linejoin="round"
|
|
1171
1395
|
stroke-linecap="round"
|
|
1172
1396
|
style:cursor={on_bar_click ? `pointer` : undefined}
|
|
1397
|
+
onmousemove={(evt) => {
|
|
1398
|
+
const pt = find_closest_point(evt, points)
|
|
1399
|
+
if (!pt) return
|
|
1400
|
+
hovered = true
|
|
1401
|
+
const fill = line_point_fill(pt, color)
|
|
1402
|
+
hover_info = get_bar_data(series_idx, pt.idx, fill)
|
|
1403
|
+
change(hover_info)
|
|
1404
|
+
on_bar_hover?.({ ...hover_info!, event: evt })
|
|
1405
|
+
}}
|
|
1406
|
+
onmouseleave={() => {
|
|
1407
|
+
change(null)
|
|
1408
|
+
hover_info = null
|
|
1409
|
+
on_bar_hover?.(null)
|
|
1410
|
+
}}
|
|
1411
|
+
onclick={(evt) => {
|
|
1412
|
+
const pt = find_closest_point(evt, points)
|
|
1413
|
+
if (!pt) return
|
|
1414
|
+
const fill = line_point_fill(pt, color)
|
|
1415
|
+
const bar_data = get_bar_data(series_idx, pt.idx, fill)
|
|
1416
|
+
on_bar_click?.({ ...bar_data, event: evt })
|
|
1417
|
+
}}
|
|
1173
1418
|
/>
|
|
1174
1419
|
{/if}
|
|
1175
1420
|
{#if show_points}
|
|
1176
1421
|
{@const clickable = on_bar_click || on_point_click}
|
|
1177
|
-
{@const get_pt = (evt: Event) =>
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
)}
|
|
1186
|
-
{@const fill = (pt: LineSeriesPoint) =>
|
|
1187
|
-
pt.color_value != null
|
|
1188
|
-
? color_scale_fn(pt.color_value)
|
|
1189
|
-
: pt.point_style?.fill ?? color}
|
|
1422
|
+
{@const get_pt = (evt: Event) => {
|
|
1423
|
+
const attr = evt.target instanceof Element
|
|
1424
|
+
? evt.target.closest(`[data-bar-idx]`)?.getAttribute(
|
|
1425
|
+
`data-bar-idx`,
|
|
1426
|
+
)
|
|
1427
|
+
: null
|
|
1428
|
+
return points.find((pt) => pt.idx === parseInt(attr ?? ``, 10))
|
|
1429
|
+
}}
|
|
1190
1430
|
{@const set_hover = (
|
|
1191
1431
|
pt: LineSeriesPoint | null,
|
|
1192
1432
|
evt: MouseEvent | FocusEvent,
|
|
1193
1433
|
) => {
|
|
1194
1434
|
if (pt) {
|
|
1195
1435
|
hovered = true
|
|
1196
|
-
|
|
1436
|
+
const fill = line_point_fill(pt, color)
|
|
1437
|
+
hover_info = get_bar_data(series_idx, pt.idx, fill)
|
|
1197
1438
|
change(hover_info)
|
|
1198
1439
|
} else {
|
|
1199
1440
|
change(null)
|
|
@@ -1208,13 +1449,15 @@ $effect(() => {
|
|
|
1208
1449
|
pt: LineSeriesPoint,
|
|
1209
1450
|
evt: MouseEvent | KeyboardEvent,
|
|
1210
1451
|
) => {
|
|
1211
|
-
const
|
|
1452
|
+
const fill = line_point_fill(pt, color)
|
|
1453
|
+
const bar_data = get_bar_data(series_idx, pt.idx, fill)
|
|
1212
1454
|
on_bar_click?.({ ...bar_data, event: evt })
|
|
1213
1455
|
on_point_click?.({ ...bar_data, event: evt, point: pt })
|
|
1214
1456
|
}}
|
|
1215
1457
|
{@const leaving = (evt: MouseEvent | FocusEvent) =>
|
|
1216
|
-
(evt.relatedTarget
|
|
1217
|
-
evt.
|
|
1458
|
+
(evt.relatedTarget instanceof Element
|
|
1459
|
+
? evt.relatedTarget.closest(`.line-points`)
|
|
1460
|
+
: null) !== evt.currentTarget}
|
|
1218
1461
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions, a11y_mouse_events_have_key_events -->
|
|
1219
1462
|
<g
|
|
1220
1463
|
class="line-points"
|
|
@@ -1247,7 +1490,7 @@ $effect(() => {
|
|
|
1247
1490
|
>
|
|
1248
1491
|
{#each points as pt (pt.idx)}
|
|
1249
1492
|
{@const sty = pt.point_style}
|
|
1250
|
-
{@const fl =
|
|
1493
|
+
{@const fl = line_point_fill(pt, color)}
|
|
1251
1494
|
{@const rad = pt.size_value != null
|
|
1252
1495
|
? size_scale_fn(pt.size_value)
|
|
1253
1496
|
: sty?.radius ?? 4}
|
|
@@ -1306,9 +1549,11 @@ $effect(() => {
|
|
|
1306
1549
|
{@const val = y_val}
|
|
1307
1550
|
{@const use_y2 = srs.y_axis === `y2`}
|
|
1308
1551
|
{@const y_scale = use_y2 ? scales.y2 : scales.y}
|
|
1552
|
+
{@const use_x2_bar = srs.x_axis === `x2`}
|
|
1553
|
+
{@const x_scale_bar = use_x2_bar ? scales.x2 : scales.x}
|
|
1309
1554
|
{@const [cat_scale, val_scale] = is_vertical
|
|
1310
|
-
? [
|
|
1311
|
-
: [scales.y,
|
|
1555
|
+
? [x_scale_bar, y_scale]
|
|
1556
|
+
: [scales.y, x_scale_bar]}
|
|
1312
1557
|
{@const c0 = cat_scale(cat_val + group_offset - half)}
|
|
1313
1558
|
{@const c1 = cat_scale(cat_val + group_offset + half)}
|
|
1314
1559
|
{@const v0 = val_scale(base)}
|
|
@@ -1405,7 +1650,9 @@ $effect(() => {
|
|
|
1405
1650
|
{/if}
|
|
1406
1651
|
|
|
1407
1652
|
{#if hover_info && hovered}
|
|
1408
|
-
{@const cx = scales.x(
|
|
1653
|
+
{@const cx = (hover_info.active_x_axis === `x2` ? scales.x2 : scales.x)(
|
|
1654
|
+
hover_info.orient_x,
|
|
1655
|
+
)}
|
|
1409
1656
|
{@const cy = (hover_info.active_y_axis === `y2` ? scales.y2 : scales.y)(
|
|
1410
1657
|
hover_info.orient_y,
|
|
1411
1658
|
)}
|
|
@@ -1418,7 +1665,6 @@ $effect(() => {
|
|
|
1418
1665
|
height,
|
|
1419
1666
|
{ offset_x: 10, offset_y: 5 },
|
|
1420
1667
|
)}
|
|
1421
|
-
{@const active_y_config = hover_info.active_y_axis === `y2` ? y2_axis : y_axis}
|
|
1422
1668
|
<PlotTooltip
|
|
1423
1669
|
x={tooltip_pos.x}
|
|
1424
1670
|
y={tooltip_pos.y}
|
|
@@ -1429,14 +1675,20 @@ $effect(() => {
|
|
|
1429
1675
|
{#if tooltip}
|
|
1430
1676
|
{@render tooltip({ ...hover_info, fullscreen })}
|
|
1431
1677
|
{:else}
|
|
1678
|
+
{@const series_label = series[hover_info.series_idx]?.label}
|
|
1679
|
+
{#if series.length > 1 && series_label}
|
|
1680
|
+
<div><strong>{series_label}</strong></div>
|
|
1681
|
+
{/if}
|
|
1432
1682
|
<div>
|
|
1433
|
-
{@html x_axis.label || `x`}: {
|
|
1434
|
-
|
|
1683
|
+
{@html hover_info.x_axis.label || `x`}: {
|
|
1684
|
+
(cat_axis === `x` ? hover_info.category_label : undefined) ??
|
|
1685
|
+
format_value(hover_info.orient_x, hover_info.x_axis.format || `.3~s`)
|
|
1435
1686
|
}
|
|
1436
1687
|
</div>
|
|
1437
1688
|
<div>
|
|
1438
|
-
{@html
|
|
1439
|
-
|
|
1689
|
+
{@html hover_info.y_axis.label || `y`}: {
|
|
1690
|
+
(cat_axis === `y` ? hover_info.category_label : undefined) ??
|
|
1691
|
+
format_value(hover_info.orient_y, hover_info.y_axis.format || `.3~s`)
|
|
1440
1692
|
}
|
|
1441
1693
|
</div>
|
|
1442
1694
|
{/if}
|
|
@@ -1447,7 +1699,7 @@ $effect(() => {
|
|
|
1447
1699
|
<BarPlotControls
|
|
1448
1700
|
toggle_props={{
|
|
1449
1701
|
...controls_toggle_props,
|
|
1450
|
-
style: `--ctrl-btn-right: var(--fullscreen-btn-offset,
|
|
1702
|
+
style: `--ctrl-btn-right: var(--fullscreen-btn-offset, 30px); ${
|
|
1451
1703
|
controls_toggle_props?.style ?? ``
|
|
1452
1704
|
}`,
|
|
1453
1705
|
}}
|
|
@@ -1457,12 +1709,16 @@ $effect(() => {
|
|
|
1457
1709
|
bind:orientation
|
|
1458
1710
|
bind:mode
|
|
1459
1711
|
bind:x_axis
|
|
1712
|
+
bind:x2_axis
|
|
1460
1713
|
bind:y_axis
|
|
1461
1714
|
bind:y2_axis
|
|
1462
1715
|
bind:display
|
|
1463
1716
|
auto_x_range={auto_ranges.x as Vec2}
|
|
1717
|
+
auto_x2_range={auto_ranges.x2 as Vec2}
|
|
1464
1718
|
auto_y_range={auto_ranges.y as Vec2}
|
|
1465
1719
|
auto_y2_range={auto_ranges.y2 as Vec2}
|
|
1720
|
+
has_x2_points={x2_series.length > 0}
|
|
1721
|
+
has_y2_points={y2_series.length > 0}
|
|
1466
1722
|
children={controls_extra}
|
|
1467
1723
|
/>
|
|
1468
1724
|
{/if}
|
|
@@ -1542,22 +1798,11 @@ $effect(() => {
|
|
|
1542
1798
|
border: var(--barplot-dragover-border, var(--dragover-border));
|
|
1543
1799
|
background-color: var(--barplot-dragover-bg, var(--dragover-bg));
|
|
1544
1800
|
}
|
|
1545
|
-
g:is(.x-axis, .y-axis, .y2-axis) .tick text {
|
|
1801
|
+
g:is(.x-axis, .x2-axis, .y-axis, .y2-axis) .tick text {
|
|
1546
1802
|
font-size: var(--tick-font-size, 0.8em);
|
|
1547
1803
|
}
|
|
1548
|
-
.zoom-rect {
|
|
1549
|
-
fill: var(--barplot-zoom-rect-fill, rgba(100, 100, 255, 0.2));
|
|
1550
|
-
stroke: var(--barplot-zoom-rect-stroke, rgba(100, 100, 255, 0.8));
|
|
1551
|
-
stroke-width: var(--barplot-zoom-rect-stroke-width, 1);
|
|
1552
|
-
pointer-events: none;
|
|
1553
|
-
}
|
|
1554
1804
|
.bar-label {
|
|
1555
1805
|
fill: var(--text-color);
|
|
1556
1806
|
font-size: 11px;
|
|
1557
1807
|
}
|
|
1558
|
-
.zero-line {
|
|
1559
|
-
stroke: var(--barplot-zero-line-color, light-dark(black, white));
|
|
1560
|
-
stroke-width: var(--barplot-zero-line-width, 1);
|
|
1561
|
-
opacity: var(--barplot-zero-line-opacity, 0.3);
|
|
1562
|
-
}
|
|
1563
1808
|
</style>
|