matterviz 0.1.12 → 0.1.13
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/ContextMenu.svelte +1 -1
- package/dist/ContextMenu.svelte.d.ts +3 -3
- package/dist/DraggablePane.svelte +10 -5
- package/dist/DraggablePane.svelte.d.ts +3 -3
- package/dist/FilePicker.svelte.d.ts +3 -3
- package/dist/Icon.svelte.d.ts +3 -3
- package/dist/Nav.svelte +76 -31
- package/dist/SettingsSection.svelte.d.ts +3 -3
- package/dist/Spinner.svelte +18 -29
- package/dist/Spinner.svelte.d.ts +3 -3
- package/dist/app.css +4 -0
- package/dist/bands/Bands.svelte +216 -0
- package/dist/bands/Bands.svelte.d.ts +16 -0
- package/dist/bands/BandsAndDos.svelte +26 -0
- package/dist/bands/BandsAndDos.svelte.d.ts +16 -0
- package/dist/bands/Dos.svelte +124 -0
- package/dist/bands/Dos.svelte.d.ts +18 -0
- package/dist/bands/helpers.d.ts +11 -0
- package/dist/bands/helpers.js +195 -0
- package/dist/bands/index.d.ts +5 -0
- package/dist/bands/index.js +6 -0
- package/dist/bands/types.d.ts +54 -0
- package/dist/bands/types.js +2 -0
- package/dist/brillouin/BrillouinZone.svelte +373 -0
- package/dist/brillouin/BrillouinZone.svelte.d.ts +57 -0
- package/dist/brillouin/BrillouinZoneControls.svelte +88 -0
- package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +14 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte +129 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +115 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
- package/dist/brillouin/BrillouinZoneScene.svelte +180 -0
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +34 -0
- package/dist/brillouin/compute.d.ts +7 -0
- package/dist/brillouin/compute.js +215 -0
- package/dist/brillouin/index.d.ts +7 -0
- package/dist/brillouin/index.js +7 -0
- package/dist/brillouin/types.d.ts +23 -0
- package/dist/brillouin/types.js +1 -0
- package/dist/composition/BarChart.svelte.d.ts +3 -3
- package/dist/composition/BubbleChart.svelte +7 -6
- package/dist/composition/BubbleChart.svelte.d.ts +3 -3
- package/dist/composition/Composition.svelte.d.ts +3 -3
- package/dist/composition/PieChart.svelte +1 -1
- package/dist/composition/PieChart.svelte.d.ts +3 -3
- package/dist/composition/parse.d.ts +3 -3
- package/dist/coordination/CoordinationBarPlot.svelte +241 -0
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +29 -0
- package/dist/coordination/calc-coordination.d.ts +15 -0
- package/dist/coordination/calc-coordination.js +66 -0
- package/dist/coordination/index.d.ts +8 -0
- package/dist/coordination/index.js +7 -0
- package/dist/element/BohrAtom.svelte.d.ts +3 -3
- package/dist/element/ElementHeading.svelte.d.ts +3 -3
- package/dist/element/ElementPhoto.svelte.d.ts +3 -3
- package/dist/element/ElementStats.svelte.d.ts +3 -3
- package/dist/element/ElementTile.svelte +1 -1
- package/dist/element/ElementTile.svelte.d.ts +4 -4
- package/dist/element/Nucleus.svelte.d.ts +3 -3
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/io/export.d.ts +1 -6
- package/dist/io/export.js +12 -12
- package/dist/labels.d.ts +19 -0
- package/dist/labels.js +59 -0
- package/dist/math.d.ts +7 -2
- package/dist/math.js +24 -12
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +3 -3
- package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +3 -3
- package/dist/periodic-table/PropertySelect.svelte +1 -1
- package/dist/periodic-table/PropertySelect.svelte.d.ts +5 -4
- package/dist/periodic-table/TableInset.svelte.d.ts +3 -3
- package/dist/phase-diagram/PhaseDiagram2D.svelte +44 -107
- package/dist/phase-diagram/PhaseDiagram2D.svelte.d.ts +7 -27
- package/dist/phase-diagram/PhaseDiagram3D.svelte +67 -170
- package/dist/phase-diagram/PhaseDiagram3D.svelte.d.ts +7 -31
- package/dist/phase-diagram/PhaseDiagram4D.svelte +87 -145
- package/dist/phase-diagram/PhaseDiagram4D.svelte.d.ts +5 -28
- package/dist/phase-diagram/PhaseDiagramControls.svelte +9 -9
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +6 -6
- package/dist/phase-diagram/PhaseDiagramInfoPane.svelte +4 -4
- package/dist/phase-diagram/PhaseDiagramInfoPane.svelte.d.ts +5 -5
- package/dist/phase-diagram/PhaseDiagramStats.svelte +9 -16
- package/dist/phase-diagram/PhaseDiagramStats.svelte.d.ts +3 -3
- package/dist/phase-diagram/StructurePopup.svelte.d.ts +3 -3
- package/dist/phase-diagram/barycentric-coords.js +13 -24
- package/dist/phase-diagram/helpers.d.ts +12 -14
- package/dist/phase-diagram/helpers.js +70 -23
- package/dist/phase-diagram/index.d.ts +58 -0
- package/dist/phase-diagram/index.js +44 -0
- package/dist/phase-diagram/thermodynamics.js +2 -3
- package/dist/phase-diagram/types.d.ts +3 -8
- package/dist/plot/BarPlot.svelte +474 -240
- package/dist/plot/BarPlot.svelte.d.ts +16 -37
- package/dist/plot/BarPlotControls.svelte +33 -201
- package/dist/plot/BarPlotControls.svelte.d.ts +11 -22
- package/dist/plot/ColorBar.svelte.d.ts +3 -3
- package/dist/plot/ColorScaleSelect.svelte +1 -1
- package/dist/plot/ColorScaleSelect.svelte.d.ts +3 -3
- package/dist/plot/ElementScatter.svelte +5 -5
- package/dist/plot/ElementScatter.svelte.d.ts +8 -8
- package/dist/plot/Histogram.svelte +318 -170
- package/dist/plot/Histogram.svelte.d.ts +21 -41
- package/dist/plot/HistogramControls.svelte +115 -347
- package/dist/plot/HistogramControls.svelte.d.ts +13 -28
- package/dist/plot/Line.svelte +1 -1
- package/dist/plot/Line.svelte.d.ts +4 -4
- package/dist/plot/PlotControls.svelte +280 -0
- package/dist/plot/PlotControls.svelte.d.ts +4 -0
- package/dist/plot/PlotLegend.svelte +9 -24
- package/dist/plot/PlotLegend.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot.svelte +536 -571
- package/dist/plot/ScatterPlot.svelte.d.ts +18 -90
- package/dist/plot/ScatterPlotControls.svelte +108 -316
- package/dist/plot/ScatterPlotControls.svelte.d.ts +11 -38
- package/dist/plot/ScatterPoint.svelte +1 -1
- package/dist/plot/ScatterPoint.svelte.d.ts +5 -5
- package/dist/plot/SpacegroupBarPlot.svelte +235 -0
- package/dist/plot/SpacegroupBarPlot.svelte.d.ts +9 -0
- package/dist/plot/data-transform.d.ts +2 -2
- package/dist/plot/data-transform.js +1 -5
- package/dist/plot/index.d.ts +12 -200
- package/dist/plot/index.js +14 -6
- package/dist/plot/layout.d.ts +10 -16
- package/dist/plot/layout.js +40 -25
- package/dist/plot/scales.d.ts +7 -8
- package/dist/plot/scales.js +22 -7
- package/dist/plot/types.d.ts +303 -0
- package/dist/plot/types.js +1 -0
- package/dist/rdf/RdfPlot.svelte +156 -0
- package/dist/rdf/RdfPlot.svelte.d.ts +20 -0
- package/dist/rdf/calc-rdf.d.ts +4 -0
- package/dist/rdf/calc-rdf.js +107 -0
- package/dist/rdf/index.d.ts +22 -0
- package/dist/rdf/index.js +2 -0
- package/dist/settings.d.ts +86 -75
- package/dist/settings.js +257 -155
- package/dist/structure/Bond.svelte +126 -110
- package/dist/structure/Bond.svelte.d.ts +7 -20
- package/dist/structure/CanvasTooltip.svelte.d.ts +4 -4
- package/dist/structure/Cylinder.svelte +29 -0
- package/dist/structure/Cylinder.svelte.d.ts +10 -0
- package/dist/structure/Lattice.svelte +1 -2
- package/dist/structure/Lattice.svelte.d.ts +3 -3
- package/dist/structure/Structure.svelte +23 -8
- package/dist/structure/Structure.svelte.d.ts +10 -7
- package/dist/structure/StructureControls.svelte +25 -134
- package/dist/structure/StructureControls.svelte.d.ts +4 -9
- package/dist/structure/StructureExportPane.svelte +188 -0
- package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
- package/dist/structure/StructureInfoPane.svelte.d.ts +3 -3
- package/dist/structure/StructureLegend.svelte +91 -27
- package/dist/structure/StructureLegend.svelte.d.ts +5 -4
- package/dist/structure/StructureScene.svelte +93 -100
- package/dist/structure/StructureScene.svelte.d.ts +15 -12
- package/dist/structure/Vector.svelte +19 -25
- package/dist/structure/Vector.svelte.d.ts +3 -3
- package/dist/structure/bonding.d.ts +16 -14
- package/dist/structure/bonding.js +207 -216
- package/dist/structure/export.d.ts +3 -0
- package/dist/structure/export.js +120 -0
- package/dist/structure/index.d.ts +15 -1
- package/dist/structure/index.js +2 -0
- package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -3
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +1 -0
- package/dist/symmetry/spacegroups.d.ts +9 -0
- package/dist/symmetry/spacegroups.js +397 -0
- package/dist/theme/ThemeControl.svelte.d.ts +3 -3
- package/dist/theme/themes.js +2 -16
- package/dist/trajectory/Trajectory.svelte +52 -78
- package/dist/trajectory/Trajectory.svelte.d.ts +5 -5
- package/dist/trajectory/TrajectoryError.svelte.d.ts +3 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +1 -1
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +5 -5
- package/dist/trajectory/TrajectoryInfoPane.svelte +1 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -3
- package/dist/trajectory/extract.js +2 -1
- package/dist/trajectory/index.d.ts +1 -1
- package/dist/trajectory/index.js +1 -1
- package/dist/trajectory/parse.js +21 -14
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -0
- package/dist/xrd/XrdPlot.svelte +14 -11
- package/dist/xrd/XrdPlot.svelte.d.ts +6 -5
- package/package.json +20 -12
- package/readme.md +1 -1
- package/dist/material/MaterialCard.svelte +0 -123
- package/dist/material/MaterialCard.svelte.d.ts +0 -9
- package/dist/material/index.d.ts +0 -1
- package/dist/material/index.js +0 -1
- package/dist/plot/formatting.d.ts +0 -7
- package/dist/plot/formatting.js +0 -53
|
@@ -1,85 +1,82 @@
|
|
|
1
|
-
<script lang="ts">import {
|
|
2
|
-
import {
|
|
1
|
+
<script lang="ts">import { luminance } from '../colors';
|
|
2
|
+
import { format_value, symbol_names } from '../labels';
|
|
3
3
|
import * as math from '../math';
|
|
4
|
-
import { ColorBar, find_best_plot_area, PlotLegend, ScatterPlotControls, ScatterPoint, } from './';
|
|
4
|
+
import { ColorBar, DEFAULT_GRID_STYLE, DEFAULT_MARKERS, find_best_plot_area, get_tick_label, Line, PlotLegend, ScatterPlotControls, ScatterPoint, } from './';
|
|
5
5
|
import { DEFAULTS } from '../settings';
|
|
6
6
|
import { extent } from 'd3-array';
|
|
7
7
|
import { forceCollide, forceLink, forceSimulation } from 'd3-force';
|
|
8
8
|
import { scaleLinear, scaleLog, scaleSequential, scaleSequentialLog, scaleTime, } from 'd3-scale';
|
|
9
9
|
import * as d3_sc from 'd3-scale-chromatic';
|
|
10
10
|
import { Tween } from 'svelte/motion';
|
|
11
|
-
import {
|
|
11
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
12
12
|
import { get_relative_coords } from './interactions';
|
|
13
13
|
import { calc_auto_padding } from './layout';
|
|
14
14
|
import { generate_ticks, get_nice_data_range } from './scales';
|
|
15
|
-
let { series = [],
|
|
16
|
-
x_label = ``, x_label_shift = { x: 0, y: -40 }, x_tick_label_shift = { x: 0, y: 20 }, y_label = ``, y_label_shift = { y: 12 }, y_tick_label_shift = { x: -8, y: 0 }, y_unit = ``, tooltip_point = $bindable(null), hovered = $bindable(false), markers = DEFAULTS.scatter.markers, x_format = $bindable(``), y_format = $bindable(``), tooltip, user_content, change = () => { }, x_ticks, y_ticks = 5, x_scale_type = `linear`, y_scale_type = `linear`, show_zero_lines = true, x_grid = true, y_grid = true, color_scale = {
|
|
15
|
+
let { series = [], x_axis = $bindable({}), y_axis = $bindable({}), y2_axis = $bindable({}), display = $bindable(DEFAULTS.scatter.display), styles = $bindable({}), controls = $bindable({}), padding = {}, range_padding = 0.05, current_x_value = null, tooltip_point = $bindable(null), hovered = $bindable(false), tooltip, user_content, change = () => { }, color_scale = {
|
|
17
16
|
type: `linear`,
|
|
18
17
|
scheme: `interpolateViridis`,
|
|
19
18
|
value_range: undefined,
|
|
20
|
-
}, color_bar = {}, size_scale = { type: `linear`, radius_range: [2, 10], value_range: undefined }, label_placement_config = {}, hover_config = {}, legend = {}, point_tween, line_tween, point_events, on_point_click, on_point_hover,
|
|
21
|
-
//
|
|
22
|
-
|
|
19
|
+
}, color_bar = {}, size_scale = { type: `linear`, radius_range: [2, 10], value_range: undefined }, label_placement_config = {}, hover_config = {}, legend = {}, point_tween, line_tween, point_events, on_point_click, on_point_hover, selected_series_idx = $bindable(0), children, ...rest } = $props();
|
|
20
|
+
// Initialize style overrides with defaults (runs once to avoid infinite loop)
|
|
21
|
+
styles.point = { ...DEFAULTS.scatter.point, ...styles.point };
|
|
22
|
+
styles.line = { ...DEFAULTS.scatter.line, ...styles.line };
|
|
23
|
+
// Initialize default values
|
|
24
|
+
x_axis = {
|
|
25
|
+
format: ``,
|
|
26
|
+
scale_type: `linear`,
|
|
27
|
+
label_shift: { x: 0, y: -40 },
|
|
28
|
+
tick_label_shift: { x: 0, y: 20 },
|
|
29
|
+
range: [null, null],
|
|
30
|
+
...x_axis,
|
|
31
|
+
};
|
|
32
|
+
y_axis = {
|
|
33
|
+
format: ``,
|
|
34
|
+
scale_type: `linear`,
|
|
35
|
+
label_shift: { x: 0, y: 0 },
|
|
36
|
+
tick_label_shift: { x: -8, y: 0 },
|
|
37
|
+
range: [null, null],
|
|
38
|
+
...y_axis,
|
|
39
|
+
};
|
|
40
|
+
y2_axis = {
|
|
41
|
+
format: ``,
|
|
42
|
+
scale_type: `linear`,
|
|
43
|
+
ticks: 5,
|
|
44
|
+
label_shift: { x: 0, y: 0 },
|
|
45
|
+
tick_label_shift: { x: 8, y: 0 },
|
|
46
|
+
range: [null, null],
|
|
47
|
+
...y2_axis,
|
|
48
|
+
};
|
|
49
|
+
display = { ...DEFAULTS.scatter.display, ...display };
|
|
50
|
+
styles = {
|
|
51
|
+
show_points: DEFAULTS.scatter.show_points,
|
|
52
|
+
show_lines: DEFAULTS.scatter.show_lines,
|
|
53
|
+
...styles,
|
|
54
|
+
};
|
|
55
|
+
controls = { show: true, open: false, ...controls };
|
|
23
56
|
let [width, height] = $state([0, 0]);
|
|
24
57
|
let svg_element = $state(null); // Bind the SVG element
|
|
25
58
|
let svg_bounding_box = $state(null); // Store SVG bounds during drag
|
|
26
59
|
// Unique component ID to avoid clipPath conflicts between multiple instances
|
|
27
60
|
let component_id = $state(`scatter-${crypto.randomUUID()}`);
|
|
28
61
|
let clip_path_id = $derived(`plot-area-clip-${component_id}`);
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// If only one series is visible, ensure it's on y1 axis
|
|
38
|
-
if (visible_series.length === 1) {
|
|
39
|
-
return series.map((s) => {
|
|
40
|
-
if (s && (s.visible ?? true) && s.y_axis === `y2`) {
|
|
41
|
-
// Reassign single visible series from y2 to y1
|
|
42
|
-
return { ...s, y_axis: `y1` };
|
|
43
|
-
}
|
|
44
|
-
return s;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
// For multiple visible series, keep original assignments
|
|
48
|
-
return series;
|
|
49
|
-
});
|
|
50
|
-
// Stable ID assignment for series - computed once and cached
|
|
51
|
-
let next_id = 0;
|
|
52
|
-
const series_id_cache = new WeakMap();
|
|
53
|
-
let series_with_ids = $derived.by(() => {
|
|
54
|
-
return processed_series.map((s) => {
|
|
55
|
-
if (!s || typeof s !== `object`)
|
|
56
|
-
return s;
|
|
57
|
-
if (`_id` in s && typeof s._id === `number`)
|
|
58
|
-
return s; // Already has stable ID
|
|
59
|
-
// Check cache first
|
|
60
|
-
if (series_id_cache.has(s)) {
|
|
61
|
-
return { ...s, _id: series_id_cache.get(s) };
|
|
62
|
-
}
|
|
63
|
-
// Assign and cache new stable ID
|
|
64
|
-
const new_id = next_id++;
|
|
65
|
-
series_id_cache.set(s, new_id);
|
|
66
|
-
return { ...s, _id: new_id };
|
|
67
|
-
});
|
|
68
|
-
});
|
|
62
|
+
// Assign stable IDs to series for keying
|
|
63
|
+
let series_with_ids = $derived(series.map((s, idx) => {
|
|
64
|
+
if (!s || typeof s !== `object`)
|
|
65
|
+
return s;
|
|
66
|
+
// Use series.id if provided, otherwise fall back to index
|
|
67
|
+
// prevents re-mounts when series are reordered if stable IDs are provided
|
|
68
|
+
return { ...s, _id: s.id ?? idx };
|
|
69
|
+
}));
|
|
69
70
|
// State for rectangle zoom selection
|
|
70
71
|
let drag_start_coords = $state(null);
|
|
71
72
|
let drag_current_coords = $state(null);
|
|
72
|
-
|
|
73
|
-
let
|
|
74
|
-
let
|
|
75
|
-
let
|
|
76
|
-
let
|
|
77
|
-
let current_y2_range = $state([0, 1]);
|
|
78
|
-
let previous_series_visibility = $state(null); // State to store visibility before isolation
|
|
73
|
+
// Zoom/pan state - single range state used for both initial and current
|
|
74
|
+
let zoom_x_range = $state([0, 1]);
|
|
75
|
+
let zoom_y_range = $state([0, 1]);
|
|
76
|
+
let zoom_y2_range = $state([0, 1]);
|
|
77
|
+
let previous_series_visibility = $state(null);
|
|
79
78
|
// State to hold the calculated label positions after simulation
|
|
80
79
|
let label_positions = $state({});
|
|
81
|
-
// State for initial (non-responsive) legend placement
|
|
82
|
-
let initial_legend_placement = $state(null);
|
|
83
80
|
// State for legend dragging
|
|
84
81
|
let legend_is_dragging = $state(false);
|
|
85
82
|
let legend_drag_offset = $state({ x: 0, y: 0 });
|
|
@@ -110,16 +107,14 @@ const default_padding = { t: 5, b: 50, l: 50, r: 20 };
|
|
|
110
107
|
let pad = $state({ ...default_padding, ...padding });
|
|
111
108
|
// Update padding when format or ticks change, but prevent infinite loop
|
|
112
109
|
$effect(() => {
|
|
113
|
-
const base_pad = { ...default_padding, ...padding };
|
|
114
110
|
const new_pad = width && height && (y_tick_values.length || y2_tick_values.length)
|
|
115
111
|
? calc_auto_padding({
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
y2_format,
|
|
112
|
+
padding,
|
|
113
|
+
default_padding,
|
|
114
|
+
y_axis: { ...y_axis, tick_values: y_tick_values },
|
|
115
|
+
y2_axis: { ...y2_axis, tick_values: y2_tick_values },
|
|
121
116
|
})
|
|
122
|
-
:
|
|
117
|
+
: { ...default_padding, ...padding };
|
|
123
118
|
// Only update if padding actually changed (prevents infinite loop)
|
|
124
119
|
if (pad.t !== new_pad.t || pad.b !== new_pad.b || pad.l !== new_pad.l ||
|
|
125
120
|
pad.r !== new_pad.r)
|
|
@@ -138,36 +133,36 @@ let plot_center_y = $derived(pad.t + (height - pad.b - pad.t) / 2);
|
|
|
138
133
|
// Compute data color values for color scaling
|
|
139
134
|
let all_color_values = $derived(series_with_ids.filter(Boolean).flatMap((srs) => srs.color_values?.filter(Boolean) || []));
|
|
140
135
|
// Compute auto ranges based on data and limits
|
|
141
|
-
let auto_x_range = $derived(get_nice_data_range(all_points, (point) => point.x,
|
|
142
|
-
let auto_y_range = $derived(get_nice_data_range(y1_points, (point) => point.y,
|
|
143
|
-
let auto_y2_range = $derived(get_nice_data_range(y2_points, (point) => point.y,
|
|
144
|
-
//
|
|
136
|
+
let auto_x_range = $derived(get_nice_data_range(all_points, (point) => point.x, (x_axis.range ?? [null, null]), x_axis.scale_type, range_padding, x_axis.format?.startsWith(`%`) || false));
|
|
137
|
+
let auto_y_range = $derived(get_nice_data_range(y1_points, (point) => point.y, (y_axis.range ?? [null, null]), y_axis.scale_type, range_padding, false));
|
|
138
|
+
let auto_y2_range = $derived(get_nice_data_range(y2_points, (point) => point.y, (y2_axis.range ?? [null, null]), y2_axis.scale_type, range_padding, false));
|
|
139
|
+
// Update zoom ranges when auto ranges or explicit ranges change
|
|
145
140
|
$effect(() => {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
const new_x = [
|
|
142
|
+
x_axis.range?.[0] ?? auto_x_range[0],
|
|
143
|
+
x_axis.range?.[1] ?? auto_x_range[1],
|
|
149
144
|
];
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
const new_y = [
|
|
146
|
+
y_axis.range?.[0] ?? auto_y_range[0],
|
|
147
|
+
y_axis.range?.[1] ?? auto_y_range[1],
|
|
153
148
|
];
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
const new_y2 = [
|
|
150
|
+
y2_axis.range?.[0] ?? auto_y2_range[0],
|
|
151
|
+
y2_axis.range?.[1] ?? auto_y2_range[1],
|
|
157
152
|
];
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
153
|
+
if (new_x[0] !== zoom_x_range[0] || new_x[1] !== zoom_x_range[1]) {
|
|
154
|
+
zoom_x_range = new_x;
|
|
155
|
+
}
|
|
156
|
+
if (new_y[0] !== zoom_y_range[0] || new_y[1] !== zoom_y_range[1]) {
|
|
157
|
+
zoom_y_range = new_y;
|
|
158
|
+
}
|
|
159
|
+
if (new_y2[0] !== zoom_y2_range[0] || new_y2[1] !== zoom_y2_range[1]) {
|
|
160
|
+
zoom_y2_range = new_y2;
|
|
166
161
|
}
|
|
167
162
|
});
|
|
168
|
-
let [x_min, x_max] = $derived(
|
|
169
|
-
let [y_min, y_max] = $derived(
|
|
170
|
-
let [y2_min, y2_max] = $derived(
|
|
163
|
+
let [x_min, x_max] = $derived(zoom_x_range);
|
|
164
|
+
let [y_min, y_max] = $derived(zoom_y_range);
|
|
165
|
+
let [y2_min, y2_max] = $derived(zoom_y2_range);
|
|
171
166
|
// Create auto color range
|
|
172
167
|
let auto_color_range = $derived(
|
|
173
168
|
// Ensure we only calculate extent on actual numbers, filtering out nulls/undefined
|
|
@@ -175,25 +170,25 @@ all_color_values.length > 0
|
|
|
175
170
|
? extent(all_color_values.filter((val) => typeof val === `number`))
|
|
176
171
|
: [0, 1]);
|
|
177
172
|
// Create scale functions
|
|
178
|
-
let x_scale_fn = $derived(
|
|
173
|
+
let x_scale_fn = $derived(x_axis.format?.startsWith(`%`)
|
|
179
174
|
? scaleTime()
|
|
180
175
|
.domain([new Date(x_min), new Date(x_max)])
|
|
181
176
|
.range([pad.l, width - pad.r])
|
|
182
|
-
:
|
|
177
|
+
: x_axis.scale_type === `log`
|
|
183
178
|
? scaleLog()
|
|
184
179
|
.domain([x_min, x_max])
|
|
185
180
|
.range([pad.l, width - pad.r])
|
|
186
181
|
: scaleLinear()
|
|
187
182
|
.domain([x_min, x_max])
|
|
188
183
|
.range([pad.l, width - pad.r]));
|
|
189
|
-
let y_scale_fn = $derived(
|
|
184
|
+
let y_scale_fn = $derived(y_axis.scale_type === `log`
|
|
190
185
|
? scaleLog()
|
|
191
186
|
.domain([y_min, y_max])
|
|
192
187
|
.range([height - pad.b, pad.t])
|
|
193
188
|
: scaleLinear()
|
|
194
189
|
.domain([y_min, y_max])
|
|
195
190
|
.range([height - pad.b, pad.t]));
|
|
196
|
-
let y2_scale_fn = $derived(
|
|
191
|
+
let y2_scale_fn = $derived(y2_axis.scale_type === `log`
|
|
197
192
|
? scaleLog()
|
|
198
193
|
.domain([y2_min, y2_max])
|
|
199
194
|
.range([height - pad.b, pad.t])
|
|
@@ -231,13 +226,16 @@ let size_scale_fn = $derived.by(() => {
|
|
|
231
226
|
});
|
|
232
227
|
// Color scale function
|
|
233
228
|
let color_scale_fn = $derived.by(() => {
|
|
234
|
-
const color_func_name = color_scale
|
|
229
|
+
const color_func_name = (typeof color_scale === `string`
|
|
230
|
+
? color_scale
|
|
231
|
+
: color_scale.scheme);
|
|
235
232
|
const interpolator = typeof d3_sc[color_func_name] === `function`
|
|
236
233
|
? d3_sc[color_func_name]
|
|
237
234
|
: d3_sc.interpolateViridis;
|
|
238
|
-
const [min_val, max_val] = color_scale.value_range ??
|
|
235
|
+
const [min_val, max_val] = (typeof color_scale === `string` ? undefined : color_scale.value_range) ??
|
|
239
236
|
auto_color_range;
|
|
240
|
-
|
|
237
|
+
const scale_type = typeof color_scale === `string` ? undefined : color_scale.type;
|
|
238
|
+
return scale_type === `log`
|
|
241
239
|
? scaleSequentialLog(interpolator).domain([
|
|
242
240
|
Math.max(min_val, math.LOG_EPS),
|
|
243
241
|
Math.max(max_val, min_val * 1.1),
|
|
@@ -248,21 +246,10 @@ let color_scale_fn = $derived.by(() => {
|
|
|
248
246
|
let filtered_series = $derived(series_with_ids
|
|
249
247
|
.map((data_series, series_idx) => {
|
|
250
248
|
if (!(data_series?.visible ?? true)) {
|
|
251
|
-
return {
|
|
252
|
-
...data_series,
|
|
253
|
-
visible: false,
|
|
254
|
-
filtered_data: [],
|
|
255
|
-
};
|
|
249
|
+
return { ...data_series, visible: false, filtered_data: [] };
|
|
256
250
|
}
|
|
257
|
-
if (!data_series) {
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
x: [],
|
|
261
|
-
y: [],
|
|
262
|
-
visible: true, // Assume visible if undefined but we somehow process it
|
|
263
|
-
filtered_data: [],
|
|
264
|
-
_id: next_id++,
|
|
265
|
-
};
|
|
251
|
+
if (!data_series) { // Return empty data consistent with DataSeries structure
|
|
252
|
+
return { x: [], y: [], visible: true, filtered_data: [], _id: series_idx };
|
|
266
253
|
}
|
|
267
254
|
const { x: xs, y: ys, color_values, size_values, ...rest } = data_series;
|
|
268
255
|
// Process points internally, adding properties beyond the base Point type
|
|
@@ -309,43 +296,6 @@ let filtered_series = $derived(series_with_ids
|
|
|
309
296
|
})
|
|
310
297
|
// Filter series end up completely empty after point filtering
|
|
311
298
|
.filter((series_data) => series_data.filtered_data.length > 0));
|
|
312
|
-
// Determine axis colors based on visible series
|
|
313
|
-
let axis_colors = $derived.by(() => {
|
|
314
|
-
// Handle explicit color overrides
|
|
315
|
-
if (typeof color_axis_labels === `object`) {
|
|
316
|
-
return { y1: color_axis_labels.y1 ?? null, y2: color_axis_labels.y2 ?? null };
|
|
317
|
-
}
|
|
318
|
-
// Check if axis coloring is disabled
|
|
319
|
-
if (!color_axis_labels)
|
|
320
|
-
return { y1: null, y2: null };
|
|
321
|
-
const visible_series = filtered_series.filter((s) => s.visible !== false);
|
|
322
|
-
// Only apply axis colors if not using a color scale and both y axes are populated
|
|
323
|
-
const is_using_color_scale = all_color_values.length > 0;
|
|
324
|
-
const both_axes_populated = y1_points.length > 0 && y2_points.length > 0;
|
|
325
|
-
if (is_using_color_scale || !both_axes_populated)
|
|
326
|
-
return { y1: null, y2: null };
|
|
327
|
-
// Count series by axis and get their colors
|
|
328
|
-
const y1_series = visible_series.filter((s) => (s.y_axis ?? `y1`) === `y1`);
|
|
329
|
-
const y2_series = visible_series.filter((s) => s.y_axis === `y2`);
|
|
330
|
-
// Helper to get series color
|
|
331
|
-
const get_series_color = (series) => {
|
|
332
|
-
// Check line color first, then point color
|
|
333
|
-
if (series.line_style?.stroke)
|
|
334
|
-
return series.line_style.stroke;
|
|
335
|
-
const first_point_style = Array.isArray(series.point_style)
|
|
336
|
-
? series.point_style[0]
|
|
337
|
-
: series.point_style;
|
|
338
|
-
if (first_point_style?.fill)
|
|
339
|
-
return first_point_style.fill;
|
|
340
|
-
if (first_point_style?.stroke)
|
|
341
|
-
return first_point_style.stroke;
|
|
342
|
-
return null; // No color found
|
|
343
|
-
};
|
|
344
|
-
return {
|
|
345
|
-
y1: y1_series.length === 1 ? get_series_color(y1_series[0]) : null,
|
|
346
|
-
y2: y2_series.length >= 1 ? get_series_color(y2_series[0]) : null,
|
|
347
|
-
};
|
|
348
|
-
});
|
|
349
299
|
// Collect all plot points for legend placement calculation
|
|
350
300
|
let plot_points_for_placement = $derived.by(() => {
|
|
351
301
|
if (!width || !height || !filtered_series)
|
|
@@ -355,7 +305,7 @@ let plot_points_for_placement = $derived.by(() => {
|
|
|
355
305
|
if (!series_data?.filtered_data)
|
|
356
306
|
continue;
|
|
357
307
|
for (const point of series_data.filtered_data) {
|
|
358
|
-
const point_x_coord =
|
|
308
|
+
const point_x_coord = x_axis.format?.startsWith(`%`)
|
|
359
309
|
? x_scale_fn(new Date(point.x))
|
|
360
310
|
: x_scale_fn(point.x);
|
|
361
311
|
const point_y_coord = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(point.y);
|
|
@@ -368,23 +318,25 @@ let plot_points_for_placement = $derived.by(() => {
|
|
|
368
318
|
});
|
|
369
319
|
// Prepare data needed for the legend component
|
|
370
320
|
let legend_data = $derived.by(() => {
|
|
371
|
-
|
|
321
|
+
const items = series_with_ids.map((data_series, series_idx) => {
|
|
372
322
|
const is_visible = data_series?.visible ?? true;
|
|
373
|
-
// Prefer top-level label, fallback to metadata label
|
|
374
|
-
const
|
|
323
|
+
// Prefer top-level label, fallback to metadata label
|
|
324
|
+
const explicit_label = data_series?.label ??
|
|
375
325
|
(typeof data_series?.metadata === `object` &&
|
|
376
326
|
data_series.metadata !== null &&
|
|
377
327
|
`label` in data_series.metadata &&
|
|
378
328
|
typeof data_series.metadata.label === `string`
|
|
379
329
|
? data_series.metadata.label
|
|
380
|
-
: null)
|
|
381
|
-
|
|
330
|
+
: null);
|
|
331
|
+
// Use explicit label or generate default
|
|
332
|
+
const label = explicit_label ?? `Series ${series_idx + 1}`;
|
|
333
|
+
const has_explicit_label = explicit_label != null;
|
|
382
334
|
const display_style = {
|
|
383
335
|
symbol_type: DEFAULTS.scatter.symbol_type,
|
|
384
336
|
symbol_color: `black`, // Default marker color
|
|
385
337
|
line_color: `black`, // Default line color
|
|
386
338
|
};
|
|
387
|
-
const series_markers =
|
|
339
|
+
const series_markers = data_series?.markers ?? DEFAULT_MARKERS;
|
|
388
340
|
// Check point_style (could be object or array)
|
|
389
341
|
const first_point_style = Array.isArray(data_series?.point_style)
|
|
390
342
|
? data_series.point_style[0] // Handle potential undefined
|
|
@@ -394,10 +346,9 @@ let legend_data = $derived.by(() => {
|
|
|
394
346
|
// Assign shape only if it's one of the allowed types, else default to DEFAULTS.scatter.symbol_type
|
|
395
347
|
let final_shape = DEFAULTS.scatter.symbol_type;
|
|
396
348
|
if (Array.isArray(symbol_names) &&
|
|
397
|
-
typeof first_point_style.
|
|
398
|
-
symbol_names.includes(first_point_style.
|
|
399
|
-
final_shape = first_point_style.
|
|
400
|
-
}
|
|
349
|
+
typeof first_point_style.symbol_type === `string` &&
|
|
350
|
+
symbol_names.includes(first_point_style.symbol_type))
|
|
351
|
+
final_shape = first_point_style.symbol_type;
|
|
401
352
|
display_style.symbol_type = final_shape;
|
|
402
353
|
display_style.symbol_color = first_point_style.fill ??
|
|
403
354
|
display_style.symbol_color; // Use default if nullish
|
|
@@ -406,9 +357,8 @@ let legend_data = $derived.by(() => {
|
|
|
406
357
|
if (!display_style.symbol_color ||
|
|
407
358
|
display_style.symbol_color === `none` ||
|
|
408
359
|
display_style.symbol_color.startsWith(`rgba(`, 0) // Check if transparent
|
|
409
|
-
)
|
|
360
|
+
)
|
|
410
361
|
display_style.symbol_color = first_point_style.stroke;
|
|
411
|
-
}
|
|
412
362
|
}
|
|
413
363
|
}
|
|
414
364
|
// else: keep default display_style.symbol_type/color if no point_style
|
|
@@ -422,10 +372,17 @@ let legend_data = $derived.by(() => {
|
|
|
422
372
|
if (series_markers?.includes(`line`)) {
|
|
423
373
|
// Prefer explicit line stroke
|
|
424
374
|
let legend_line_color = data_series?.line_style?.stroke;
|
|
425
|
-
if (!legend_line_color) {
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
375
|
+
if (!legend_line_color) {
|
|
376
|
+
// Try color scale if available
|
|
377
|
+
const first_cv = Array.isArray(data_series?.color_values)
|
|
378
|
+
? data_series.color_values.find((v) => v != null)
|
|
379
|
+
: undefined;
|
|
380
|
+
legend_line_color =
|
|
381
|
+
(first_cv != null ? color_scale_fn(first_cv) : undefined) ||
|
|
382
|
+
first_point_style?.fill ||
|
|
383
|
+
first_point_style?.stroke ||
|
|
384
|
+
display_style.symbol_color ||
|
|
385
|
+
`black`;
|
|
429
386
|
}
|
|
430
387
|
display_style.line_color = legend_line_color;
|
|
431
388
|
display_style.line_dash = data_series?.line_style?.line_dash;
|
|
@@ -440,8 +397,17 @@ let legend_data = $derived.by(() => {
|
|
|
440
397
|
label,
|
|
441
398
|
visible: is_visible,
|
|
442
399
|
display_style,
|
|
400
|
+
has_explicit_label,
|
|
443
401
|
};
|
|
444
402
|
});
|
|
403
|
+
// Deduplicate by label - keep first occurrence of each unique label
|
|
404
|
+
const seen_labels = new SvelteSet();
|
|
405
|
+
return items.filter((item) => {
|
|
406
|
+
if (seen_labels.has(item.label))
|
|
407
|
+
return false;
|
|
408
|
+
seen_labels.add(item.label);
|
|
409
|
+
return true;
|
|
410
|
+
});
|
|
445
411
|
});
|
|
446
412
|
// Calculate best legend placement using new simple system
|
|
447
413
|
let legend_placement = $derived.by(() => {
|
|
@@ -479,19 +445,15 @@ let color_bar_placement = $derived.by(() => {
|
|
|
479
445
|
legend_size: estimated_size,
|
|
480
446
|
});
|
|
481
447
|
});
|
|
482
|
-
//
|
|
448
|
+
// Active legend placement (null if user set explicit position)
|
|
483
449
|
let active_legend_placement = $derived.by(() => {
|
|
484
450
|
if (!legend_placement)
|
|
485
451
|
return null;
|
|
486
|
-
// Skip auto-placement if user set explicit position
|
|
452
|
+
// Skip auto-placement if user set explicit position in style
|
|
487
453
|
const style = legend?.wrapper_style ?? ``;
|
|
488
454
|
if (/(^|[;{]\s*)(top|bottom|left|right)\s*:|position\s*:\s*absolute/.test(style))
|
|
489
455
|
return null;
|
|
490
|
-
|
|
491
|
-
if (legend?.responsive)
|
|
492
|
-
return legend_placement;
|
|
493
|
-
// Fixed mode: use initial placement (set in effect below)
|
|
494
|
-
return initial_legend_placement ?? legend_placement;
|
|
456
|
+
return legend_placement;
|
|
495
457
|
});
|
|
496
458
|
// Initialize tweened values for color bar position
|
|
497
459
|
const tweened_colorbar_coords = new Tween({ x: 0, y: 0 }, {
|
|
@@ -504,18 +466,12 @@ const tweened_legend_coords = new Tween({ x: 0, y: 0 }, { duration: 400, ...(leg
|
|
|
504
466
|
$effect(() => {
|
|
505
467
|
if (!width || !height)
|
|
506
468
|
return;
|
|
507
|
-
// Store initial placement for non-responsive mode
|
|
508
|
-
if (legend_placement && !initial_legend_placement && !legend?.responsive) {
|
|
509
|
-
initial_legend_placement = legend_placement;
|
|
510
|
-
}
|
|
511
|
-
// Update color bar
|
|
512
469
|
if (color_bar_placement) {
|
|
513
470
|
tweened_colorbar_coords.set({
|
|
514
471
|
x: color_bar_placement.x,
|
|
515
472
|
y: color_bar_placement.y,
|
|
516
473
|
});
|
|
517
474
|
}
|
|
518
|
-
// Update legend (unless manually positioned)
|
|
519
475
|
if (legend_manual_position && !legend_is_dragging) {
|
|
520
476
|
tweened_legend_coords.set(legend_manual_position);
|
|
521
477
|
}
|
|
@@ -530,26 +486,28 @@ $effect(() => {
|
|
|
530
486
|
let x_tick_values = $derived.by(() => {
|
|
531
487
|
if (!width || !height)
|
|
532
488
|
return [];
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
const x_scale_for_ticks =
|
|
536
|
-
?
|
|
537
|
-
:
|
|
538
|
-
|
|
539
|
-
|
|
489
|
+
// Choose appropriate scale for tick generation
|
|
490
|
+
// Time scales (format starts with %) use scaleTime for better tick placement
|
|
491
|
+
const x_scale_for_ticks = x_axis.format?.startsWith(`%`)
|
|
492
|
+
? scaleTime().domain([new Date(x_min), new Date(x_max)])
|
|
493
|
+
: x_axis.scale_type === `log`
|
|
494
|
+
? scaleLog().domain([x_min, x_max])
|
|
495
|
+
: scaleLinear().domain([x_min, x_max]);
|
|
496
|
+
return generate_ticks([x_min, x_max], x_axis.scale_type, x_axis.ticks, x_scale_for_ticks, {
|
|
497
|
+
format: x_axis.format,
|
|
540
498
|
});
|
|
541
499
|
});
|
|
542
500
|
let y_tick_values = $derived.by(() => {
|
|
543
501
|
if (!width || !height)
|
|
544
502
|
return [];
|
|
545
|
-
return generate_ticks([y_min, y_max],
|
|
503
|
+
return generate_ticks([y_min, y_max], y_axis.scale_type, y_axis.ticks, y_scale_fn, {
|
|
546
504
|
default_count: 5,
|
|
547
505
|
});
|
|
548
506
|
});
|
|
549
507
|
let y2_tick_values = $derived.by(() => {
|
|
550
508
|
if (!width || !height || y2_points.length === 0)
|
|
551
509
|
return [];
|
|
552
|
-
return generate_ticks([y2_min, y2_max],
|
|
510
|
+
return generate_ticks([y2_min, y2_max], y2_axis.scale_type, y2_axis.ticks, y2_scale_fn, {
|
|
553
511
|
default_count: 5,
|
|
554
512
|
});
|
|
555
513
|
});
|
|
@@ -614,15 +572,14 @@ const on_window_mouse_up = (_evt) => {
|
|
|
614
572
|
dy > min_zoom_size &&
|
|
615
573
|
next_x_range[0] !== next_x_range[1] &&
|
|
616
574
|
next_y_range[0] !== next_y_range[1]) {
|
|
617
|
-
|
|
618
|
-
|
|
575
|
+
zoom_x_range = next_x_range;
|
|
576
|
+
zoom_y_range = next_y_range;
|
|
619
577
|
}
|
|
620
|
-
// If the box is too small, we just reset without zooming (effectively ignoring the drag)
|
|
621
578
|
}
|
|
622
579
|
// Reset states and remove listeners
|
|
623
580
|
drag_start_coords = null;
|
|
624
581
|
drag_current_coords = null;
|
|
625
|
-
svg_bounding_box = null;
|
|
582
|
+
svg_bounding_box = null;
|
|
626
583
|
window.removeEventListener(`mousemove`, on_window_mouse_move);
|
|
627
584
|
window.removeEventListener(`mouseup`, on_window_mouse_up);
|
|
628
585
|
document.body.style.cursor = `default`;
|
|
@@ -632,25 +589,31 @@ function handle_mouse_down(evt) {
|
|
|
632
589
|
if (!coords || !svg_element)
|
|
633
590
|
return;
|
|
634
591
|
drag_start_coords = coords;
|
|
635
|
-
drag_current_coords = coords;
|
|
636
|
-
svg_bounding_box = svg_element.getBoundingClientRect();
|
|
637
|
-
// Add listeners to window
|
|
592
|
+
drag_current_coords = coords;
|
|
593
|
+
svg_bounding_box = svg_element.getBoundingClientRect();
|
|
638
594
|
window.addEventListener(`mousemove`, on_window_mouse_move);
|
|
639
595
|
window.addEventListener(`mouseup`, on_window_mouse_up);
|
|
640
|
-
// Prevent text selection during drag
|
|
641
596
|
evt.preventDefault();
|
|
642
597
|
}
|
|
643
598
|
function handle_mouse_leave() {
|
|
644
|
-
// Reset drag state if mouse leaves plot area
|
|
645
599
|
hovered = false;
|
|
646
600
|
tooltip_point = null;
|
|
647
|
-
on_point_hover?.({ point: null });
|
|
601
|
+
on_point_hover?.({ point: null, event: undefined });
|
|
648
602
|
}
|
|
649
603
|
function handle_double_click() {
|
|
650
|
-
// Reset zoom
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
604
|
+
// Reset zoom to auto ranges
|
|
605
|
+
zoom_x_range = [
|
|
606
|
+
x_axis.range?.[0] ?? auto_x_range[0],
|
|
607
|
+
x_axis.range?.[1] ?? auto_x_range[1],
|
|
608
|
+
];
|
|
609
|
+
zoom_y_range = [
|
|
610
|
+
y_axis.range?.[0] ?? auto_y_range[0],
|
|
611
|
+
y_axis.range?.[1] ?? auto_y_range[1],
|
|
612
|
+
];
|
|
613
|
+
zoom_y2_range = [
|
|
614
|
+
y2_axis.range?.[0] ?? auto_y2_range[0],
|
|
615
|
+
y2_axis.range?.[1] ?? auto_y2_range[1],
|
|
616
|
+
];
|
|
654
617
|
}
|
|
655
618
|
// tooltip logic: find closest point and update tooltip state
|
|
656
619
|
function update_tooltip_point(x_rel, y_rel, evt) {
|
|
@@ -667,7 +630,7 @@ function update_tooltip_point(x_rel, y_rel, evt) {
|
|
|
667
630
|
continue;
|
|
668
631
|
for (const point of series_data.filtered_data) {
|
|
669
632
|
// Calculate screen coordinates of the point
|
|
670
|
-
const point_cx =
|
|
633
|
+
const point_cx = x_axis.format?.startsWith(`%`)
|
|
671
634
|
? x_scale_fn(new Date(point.x))
|
|
672
635
|
: x_scale_fn(point.x);
|
|
673
636
|
const point_cy = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(point.y);
|
|
@@ -722,109 +685,81 @@ let actual_label_config = $derived({
|
|
|
722
685
|
$effect(() => {
|
|
723
686
|
if (!width || !height)
|
|
724
687
|
return;
|
|
725
|
-
//
|
|
726
|
-
const
|
|
688
|
+
// Collect auto-placed labels and their anchors
|
|
689
|
+
const label_nodes = [];
|
|
727
690
|
const anchor_nodes = [];
|
|
728
691
|
const links = [];
|
|
729
|
-
|
|
730
|
-
series_data.filtered_data
|
|
731
|
-
if (point.point_label?.auto_placement
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
point_radius,
|
|
760
|
-
});
|
|
761
|
-
// Link label to its fixed anchor
|
|
762
|
-
links.push({ source: id, target: fixed_anchor_id });
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
if (nodes_to_simulate.length === 0) {
|
|
692
|
+
for (const series_data of filtered_series) {
|
|
693
|
+
for (const point of series_data.filtered_data) {
|
|
694
|
+
if (!point.point_label?.auto_placement || !point.point_label.text)
|
|
695
|
+
continue;
|
|
696
|
+
const anchor_x = x_axis.format?.startsWith(`%`)
|
|
697
|
+
? x_scale_fn(new Date(point.x))
|
|
698
|
+
: x_scale_fn(point.x);
|
|
699
|
+
const anchor_y = (series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(point.y);
|
|
700
|
+
const id = `${point.series_idx}-${point.point_idx}`;
|
|
701
|
+
const anchor_id = `anchor-${id}`;
|
|
702
|
+
label_nodes.push({
|
|
703
|
+
id,
|
|
704
|
+
anchor_x,
|
|
705
|
+
anchor_y,
|
|
706
|
+
point_node: point,
|
|
707
|
+
label_width: point.point_label.text.length * 6 + 10,
|
|
708
|
+
label_height: 14,
|
|
709
|
+
x: anchor_x + (point.point_label.offset?.x ?? 5),
|
|
710
|
+
y: anchor_y + (point.point_label.offset?.y ?? 0),
|
|
711
|
+
});
|
|
712
|
+
anchor_nodes.push({
|
|
713
|
+
id: anchor_id,
|
|
714
|
+
fx: anchor_x,
|
|
715
|
+
fy: anchor_y,
|
|
716
|
+
point_radius: point.point_style?.radius ?? 3,
|
|
717
|
+
});
|
|
718
|
+
links.push({ source: id, target: anchor_id });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (label_nodes.length === 0) {
|
|
767
722
|
label_positions = {};
|
|
768
|
-
return;
|
|
723
|
+
return;
|
|
769
724
|
}
|
|
770
|
-
//
|
|
771
|
-
|
|
772
|
-
...nodes_to_simulate,
|
|
773
|
-
...anchor_nodes,
|
|
774
|
-
];
|
|
775
|
-
// 2. Setup and run the simulation
|
|
776
|
-
const simulation = forceSimulation(all_simulation_nodes)
|
|
725
|
+
// Run force simulation
|
|
726
|
+
forceSimulation([...label_nodes, ...anchor_nodes])
|
|
777
727
|
.force(`link`, forceLink(links)
|
|
778
728
|
.id((d) => d.id)
|
|
779
729
|
.distance(actual_label_config.link_distance)
|
|
780
|
-
.strength(actual_label_config.link_strength))
|
|
781
|
-
.force(`collide`, forceCollide()
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
return node_as_anchor.point_radius + 2; // +2 buffer
|
|
795
|
-
}
|
|
796
|
-
return 0; // Should not happen if nodes are constructed correctly
|
|
797
|
-
})
|
|
798
|
-
.strength(actual_label_config.collision_strength))
|
|
799
|
-
.stop();
|
|
800
|
-
// Run simulation for a fixed number of ticks
|
|
801
|
-
simulation.tick(actual_label_config.placement_ticks);
|
|
802
|
-
// 3. Store the final positions, applying link_distance_range constraint
|
|
803
|
-
nodes_to_simulate.forEach((node) => {
|
|
730
|
+
.strength(actual_label_config.link_strength))
|
|
731
|
+
.force(`collide`, forceCollide().radius((node) => {
|
|
732
|
+
const label = node;
|
|
733
|
+
const anchor = node;
|
|
734
|
+
return label.label_width
|
|
735
|
+
? Math.max(label.label_width, label.label_height) / 2 + 2
|
|
736
|
+
: (anchor.point_radius ?? 0) + 2;
|
|
737
|
+
}).strength(actual_label_config.collision_strength))
|
|
738
|
+
.stop()
|
|
739
|
+
.tick(actual_label_config.placement_ticks);
|
|
740
|
+
// Apply distance constraints and store final positions
|
|
741
|
+
const [min_dist, max_dist] = actual_label_config.link_distance_range ??
|
|
742
|
+
[null, null];
|
|
743
|
+
for (const node of label_nodes) {
|
|
804
744
|
let final_x = node.x;
|
|
805
745
|
let final_y = node.y;
|
|
806
|
-
|
|
807
|
-
if (dist_range) {
|
|
808
|
-
const [min_dist, max_dist] = dist_range;
|
|
746
|
+
if (min_dist || max_dist) {
|
|
809
747
|
const dx = final_x - node.anchor_x;
|
|
810
748
|
const dy = final_y - node.anchor_y;
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
final_x = node.anchor_x + dx * scale_factor;
|
|
817
|
-
final_y = node.anchor_y + dy * scale_factor;
|
|
749
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
750
|
+
if (max_dist && dist > max_dist) {
|
|
751
|
+
const scale = max_dist / dist;
|
|
752
|
+
final_x = node.anchor_x + dx * scale;
|
|
753
|
+
final_y = node.anchor_y + dy * scale;
|
|
818
754
|
}
|
|
819
|
-
else if (min_dist &&
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
final_y = node.anchor_y + dy * scale_factor;
|
|
755
|
+
else if (min_dist && dist > 0 && dist < min_dist) {
|
|
756
|
+
const scale = min_dist / dist;
|
|
757
|
+
final_x = node.anchor_x + dx * scale;
|
|
758
|
+
final_y = node.anchor_y + dy * scale;
|
|
824
759
|
}
|
|
825
760
|
}
|
|
826
761
|
label_positions[node.id] = { x: final_x, y: final_y };
|
|
827
|
-
}
|
|
762
|
+
}
|
|
828
763
|
});
|
|
829
764
|
// Helper function to check if two series have compatible units
|
|
830
765
|
function have_compatible_units(series1, series2) {
|
|
@@ -842,10 +777,15 @@ function toggle_series_visibility(series_idx) {
|
|
|
842
777
|
const toggled_series = series[series_idx];
|
|
843
778
|
const new_visibility = !(toggled_series.visible ?? true);
|
|
844
779
|
const target_axis = toggled_series.y_axis ?? `y1`;
|
|
780
|
+
const toggled_label = toggled_series.label;
|
|
845
781
|
// Only create new objects for series that need to change to preserve series IDs
|
|
846
782
|
series = series.map((s, idx) => {
|
|
847
|
-
|
|
848
|
-
|
|
783
|
+
// Toggle all series with the same label (for grouped series like band structures)
|
|
784
|
+
if (toggled_label && s.label === toggled_label) {
|
|
785
|
+
return { ...s, visible: new_visibility };
|
|
786
|
+
}
|
|
787
|
+
// Also toggle the specific series if it has no label
|
|
788
|
+
if (idx === series_idx && !toggled_label) {
|
|
849
789
|
return { ...s, visible: new_visibility };
|
|
850
790
|
}
|
|
851
791
|
// If we're showing a series, hide incompatible series on same axis
|
|
@@ -863,11 +803,20 @@ function toggle_series_visibility(series_idx) {
|
|
|
863
803
|
}
|
|
864
804
|
// Function to handle double-click on legend item
|
|
865
805
|
function handle_legend_double_click(double_clicked_idx) {
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
if (
|
|
806
|
+
const double_clicked_series = series[double_clicked_idx];
|
|
807
|
+
const double_clicked_label = double_clicked_series?.label;
|
|
808
|
+
const current_visibility = series.map((s) => s?.visible ?? true);
|
|
809
|
+
const visible_count = current_visibility.filter((visible) => visible).length;
|
|
810
|
+
// Check if the group is currently isolated (all series with this label visible, others hidden)
|
|
811
|
+
const group_is_isolated = series.every((srs, idx) => {
|
|
812
|
+
// For unlabeled series, use index comparison; for labeled, use label comparison
|
|
813
|
+
const is_in_group = double_clicked_label
|
|
814
|
+
? srs.label === double_clicked_label
|
|
815
|
+
: idx === double_clicked_idx;
|
|
816
|
+
const is_visible = srs?.visible ?? true;
|
|
817
|
+
return is_in_group ? is_visible : !is_visible;
|
|
818
|
+
});
|
|
819
|
+
if (group_is_isolated && previous_series_visibility) {
|
|
871
820
|
// Restore previous visibility state
|
|
872
821
|
// Only create new objects for series whose visibility actually changes
|
|
873
822
|
series = series.map((s, idx) => {
|
|
@@ -881,14 +830,18 @@ function handle_legend_double_click(double_clicked_idx) {
|
|
|
881
830
|
previous_series_visibility = null; // Clear memory
|
|
882
831
|
}
|
|
883
832
|
else {
|
|
884
|
-
// Isolate the double-clicked series
|
|
833
|
+
// Isolate the double-clicked series group
|
|
885
834
|
// Only store previous state if we are actually isolating (more than one series visible)
|
|
886
835
|
if (visible_count > 1) {
|
|
887
836
|
previous_series_visibility = [...current_visibility]; // Store current state
|
|
888
837
|
}
|
|
889
838
|
// Only create new objects for series whose visibility needs to change
|
|
890
839
|
series = series.map((s, idx) => {
|
|
891
|
-
|
|
840
|
+
// For unlabeled series, use index comparison; for labeled, use label comparison
|
|
841
|
+
const is_in_group = double_clicked_label
|
|
842
|
+
? s.label === double_clicked_label
|
|
843
|
+
: idx === double_clicked_idx;
|
|
844
|
+
const target_visibility = is_in_group;
|
|
892
845
|
const current_visibility = s?.visible ?? true;
|
|
893
846
|
if (current_visibility !== target_visibility) {
|
|
894
847
|
return { ...s, visible: target_visibility };
|
|
@@ -928,7 +881,7 @@ function handle_legend_drag_end(_event) {
|
|
|
928
881
|
}
|
|
929
882
|
function get_screen_coords(point, series) {
|
|
930
883
|
// convert data coordinates to potentially non-finite screen coordinates
|
|
931
|
-
const screen_x =
|
|
884
|
+
const screen_x = x_axis.format?.startsWith(`%`)
|
|
932
885
|
? x_scale_fn(new Date(point.x))
|
|
933
886
|
: x_scale_fn(point.x);
|
|
934
887
|
const y_val = point.y;
|
|
@@ -936,19 +889,19 @@ function get_screen_coords(point, series) {
|
|
|
936
889
|
const use_y2 = series?.y_axis === `y2`;
|
|
937
890
|
const y_scale = use_y2 ? y2_scale_fn : y_scale_fn;
|
|
938
891
|
const min_domain_y = use_y2
|
|
939
|
-
?
|
|
940
|
-
:
|
|
892
|
+
? y2_axis.scale_type === `log` ? y_scale.domain()[0] : -Infinity
|
|
893
|
+
: y_axis.scale_type === `log`
|
|
941
894
|
? y_scale.domain()[0]
|
|
942
895
|
: -Infinity;
|
|
943
896
|
const safe_y_val = use_y2
|
|
944
|
-
?
|
|
945
|
-
:
|
|
897
|
+
? y2_axis.scale_type === `log` ? Math.max(y_val, min_domain_y) : y_val
|
|
898
|
+
: y_axis.scale_type === `log`
|
|
946
899
|
? Math.max(y_val, min_domain_y)
|
|
947
900
|
: y_val;
|
|
948
901
|
const screen_y = y_scale(safe_y_val); // This might be non-finite
|
|
949
902
|
return [screen_x, screen_y];
|
|
950
903
|
}
|
|
951
|
-
let using_controls = $derived(
|
|
904
|
+
let using_controls = $derived(controls.show);
|
|
952
905
|
let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
953
906
|
</script>
|
|
954
907
|
|
|
@@ -978,43 +931,242 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
978
931
|
width,
|
|
979
932
|
x_scale_fn,
|
|
980
933
|
y_scale_fn,
|
|
981
|
-
|
|
982
|
-
x_max,
|
|
983
|
-
y_min,
|
|
984
|
-
y_max,
|
|
934
|
+
y2_scale_fn,
|
|
985
935
|
pad,
|
|
936
|
+
x_range: [x_min, x_max],
|
|
937
|
+
y_range: [y_min, y_max],
|
|
938
|
+
y2_range: [y2_min, y2_max],
|
|
986
939
|
})}
|
|
940
|
+
<g class="x-axis">
|
|
941
|
+
{#if width > 0 && height > 0}
|
|
942
|
+
{#each x_tick_values as tick (tick)}
|
|
943
|
+
{@const tick_pos_raw = x_axis.format?.startsWith(`%`)
|
|
944
|
+
? x_scale_fn(new Date(tick))
|
|
945
|
+
: x_scale_fn(tick)}
|
|
946
|
+
{#if isFinite(tick_pos_raw)}
|
|
947
|
+
// Check if tick position is finite
|
|
948
|
+
{@const tick_pos = tick_pos_raw}
|
|
949
|
+
{#if tick_pos >= pad.l && tick_pos <= width - pad.r}
|
|
950
|
+
<g class="tick" transform="translate({tick_pos}, {height - pad.b})">
|
|
951
|
+
{#if display.x_grid}
|
|
952
|
+
<line
|
|
953
|
+
y1={-(height - pad.b - pad.t)}
|
|
954
|
+
y2="0"
|
|
955
|
+
{...DEFAULT_GRID_STYLE}
|
|
956
|
+
{...(x_axis.grid_style ?? {})}
|
|
957
|
+
/>
|
|
958
|
+
{/if}
|
|
987
959
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
960
|
+
{#if tick >= x_min && tick <= x_max}
|
|
961
|
+
{@const { x, y } = x_axis.tick_label_shift ?? { x: 0, y: 20 }}
|
|
962
|
+
{@const custom_label = get_tick_label(tick, x_axis.ticks)}
|
|
963
|
+
<text {x} {y}>
|
|
964
|
+
{custom_label ?? format_value(tick, x_axis.format ?? ``)}
|
|
965
|
+
</text>
|
|
966
|
+
{/if}
|
|
967
|
+
</g>
|
|
968
|
+
{/if}
|
|
969
|
+
{/if}
|
|
970
|
+
{/each}
|
|
971
|
+
{/if}
|
|
972
|
+
|
|
973
|
+
<!-- Current frame indicator -->
|
|
974
|
+
{#if current_x_value !== null && current_x_value !== undefined}
|
|
975
|
+
{@const current_pos_raw = x_axis.format?.startsWith(`%`)
|
|
976
|
+
? x_scale_fn(new Date(current_x_value))
|
|
977
|
+
: x_scale_fn(current_x_value)}
|
|
978
|
+
{#if isFinite(current_pos_raw)}
|
|
979
|
+
{@const current_pos = current_pos_raw}
|
|
980
|
+
{#if current_pos >= pad.l && current_pos <= width - pad.r}
|
|
981
|
+
{@const active_tick_height = 7}
|
|
982
|
+
<rect
|
|
983
|
+
x={current_pos - 1.5}
|
|
984
|
+
y={height - pad.b - active_tick_height / 2}
|
|
985
|
+
width="3"
|
|
986
|
+
height={active_tick_height}
|
|
987
|
+
fill="var(--scatter-current-frame-color, #ff6b35)"
|
|
988
|
+
stroke="white"
|
|
989
|
+
stroke-width="1"
|
|
990
|
+
class="current-frame-indicator"
|
|
991
|
+
/>
|
|
992
|
+
{/if}
|
|
1004
993
|
{/if}
|
|
1005
994
|
{/if}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
995
|
+
|
|
996
|
+
{#if x_axis.label}
|
|
997
|
+
<foreignObject
|
|
998
|
+
x={width / 2 + (x_axis.label_shift?.x ?? 0) - 100}
|
|
999
|
+
y={height - pad.b - (x_axis.label_shift?.y ?? -40) - 10}
|
|
1000
|
+
width="200"
|
|
1001
|
+
height="20"
|
|
1002
|
+
>
|
|
1003
|
+
<div class="axis-label x-label">
|
|
1004
|
+
{@html x_axis.label ?? ``}
|
|
1005
|
+
</div>
|
|
1006
|
+
</foreignObject>
|
|
1007
|
+
{/if}
|
|
1008
|
+
</g>
|
|
1009
|
+
|
|
1010
|
+
<g class="y-axis">
|
|
1011
|
+
{#if width > 0 && height > 0}
|
|
1012
|
+
{#each y_tick_values as tick, idx (tick)}
|
|
1013
|
+
{@const tick_pos_raw = y_scale_fn(tick)}
|
|
1014
|
+
{#if isFinite(tick_pos_raw)}
|
|
1015
|
+
// Check if tick position is finite
|
|
1016
|
+
{@const tick_pos = tick_pos_raw}
|
|
1017
|
+
{#if tick_pos >= pad.t && tick_pos <= height - pad.b}
|
|
1018
|
+
<g class="tick" transform="translate({pad.l}, {tick_pos})">
|
|
1019
|
+
{#if display.y_grid}
|
|
1020
|
+
<line
|
|
1021
|
+
x1="0"
|
|
1022
|
+
x2={width - pad.l - pad.r}
|
|
1023
|
+
{...DEFAULT_GRID_STYLE}
|
|
1024
|
+
{...(y_axis.grid_style ?? {})}
|
|
1025
|
+
/>
|
|
1026
|
+
{/if}
|
|
1027
|
+
|
|
1028
|
+
{#if tick >= y_min && tick <= y_max}
|
|
1029
|
+
{@const { x, y } = y_axis.tick_label_shift ?? { x: -8, y: 0 }}
|
|
1030
|
+
{@const custom_label = get_tick_label(tick, y_axis.ticks)}
|
|
1031
|
+
<text {x} {y} text-anchor="end" fill={y_axis.color}>
|
|
1032
|
+
{custom_label ?? format_value(tick, y_axis.format ?? ``)}
|
|
1033
|
+
{#if y_axis.unit && idx === 0}
|
|
1034
|
+
‌ {y_axis.unit}
|
|
1035
|
+
{/if}
|
|
1036
|
+
</text>
|
|
1037
|
+
{/if}
|
|
1038
|
+
</g>
|
|
1039
|
+
{/if}
|
|
1040
|
+
{/if}
|
|
1041
|
+
{/each}
|
|
1042
|
+
{/if}
|
|
1043
|
+
|
|
1044
|
+
{#if height > 0 && y_axis.label}
|
|
1045
|
+
<foreignObject
|
|
1046
|
+
x={-100}
|
|
1047
|
+
y={-10}
|
|
1048
|
+
width="200"
|
|
1049
|
+
height="20"
|
|
1050
|
+
transform="rotate(-90, {(y_axis.label_shift?.y ?? 12)}, {pad.t +
|
|
1051
|
+
(height - pad.t - pad.b) / 2 +
|
|
1052
|
+
((y_axis.label_shift?.x ?? 0))}) translate({(y_axis.label_shift?.y ?? 12)}, {pad.t +
|
|
1053
|
+
(height - pad.t - pad.b) / 2 +
|
|
1054
|
+
((y_axis.label_shift?.x ?? 0))})"
|
|
1055
|
+
>
|
|
1056
|
+
<div class="axis-label y-label" style:color={y_axis.color}>
|
|
1057
|
+
{@html y_axis.label ?? ``}
|
|
1058
|
+
</div>
|
|
1059
|
+
</foreignObject>
|
|
1060
|
+
{/if}
|
|
1061
|
+
</g>
|
|
1062
|
+
|
|
1063
|
+
<!-- Y2-axis (Right) -->
|
|
1064
|
+
{#if y2_points.length > 0}
|
|
1065
|
+
<g class="y2-axis">
|
|
1066
|
+
{#if width > 0 && height > 0}
|
|
1067
|
+
{#each y2_tick_values as tick, idx (tick)}
|
|
1068
|
+
{@const tick_pos_raw = y2_scale_fn(tick)}
|
|
1069
|
+
{#if isFinite(tick_pos_raw)}
|
|
1070
|
+
// Check if tick position is finite
|
|
1071
|
+
{@const tick_pos = tick_pos_raw}
|
|
1072
|
+
{#if tick_pos >= pad.t && tick_pos <= height - pad.b}
|
|
1073
|
+
<g class="tick" transform="translate({width - pad.r}, {tick_pos})">
|
|
1074
|
+
{#if display.y2_grid}
|
|
1075
|
+
<line
|
|
1076
|
+
x1={-(width - pad.l - pad.r)}
|
|
1077
|
+
x2="0"
|
|
1078
|
+
{...DEFAULT_GRID_STYLE}
|
|
1079
|
+
{...(y2_axis.grid_style ?? {})}
|
|
1080
|
+
/>
|
|
1081
|
+
{/if}
|
|
1082
|
+
|
|
1083
|
+
{#if tick >= y2_min && tick <= y2_max}
|
|
1084
|
+
{@const { x, y } = y2_axis.tick_label_shift ?? { x: 8, y: 0 }}
|
|
1085
|
+
{@const custom_label = get_tick_label(tick, y2_axis.ticks)}
|
|
1086
|
+
<text {x} {y} text-anchor="start" fill={y2_axis.color}>
|
|
1087
|
+
{custom_label ?? format_value(tick, y2_axis.format ?? ``)}
|
|
1088
|
+
{#if y2_axis.unit && idx === 0}
|
|
1089
|
+
‌ {y2_axis.unit}
|
|
1090
|
+
{/if}
|
|
1091
|
+
</text>
|
|
1092
|
+
{/if}
|
|
1093
|
+
</g>
|
|
1094
|
+
{/if}
|
|
1095
|
+
{/if}
|
|
1096
|
+
{/each}
|
|
1097
|
+
{/if}
|
|
1098
|
+
|
|
1099
|
+
{#if height > 0 && y2_axis.label}
|
|
1100
|
+
<foreignObject
|
|
1101
|
+
x={-100}
|
|
1102
|
+
y={-10}
|
|
1103
|
+
width="200"
|
|
1104
|
+
height="20"
|
|
1105
|
+
transform="rotate(-90, {width - pad.r + ((y2_axis.label_shift?.y ?? 0))}, {pad.t +
|
|
1106
|
+
(height - pad.t - pad.b) / 2 +
|
|
1107
|
+
((y2_axis.label_shift?.x ?? 0))}) translate({width -
|
|
1108
|
+
pad.r +
|
|
1109
|
+
((y2_axis.label_shift?.y ?? 0))}, {pad.t +
|
|
1110
|
+
(height - pad.t - pad.b) / 2 +
|
|
1111
|
+
((y2_axis.label_shift?.x ?? 0))})"
|
|
1112
|
+
>
|
|
1113
|
+
<div class="axis-label y2-label" style:color={y2_axis.color}>
|
|
1114
|
+
{@html y2_axis.label ?? ``}
|
|
1115
|
+
</div>
|
|
1116
|
+
</foreignObject>
|
|
1017
1117
|
{/if}
|
|
1118
|
+
</g>
|
|
1119
|
+
{/if}
|
|
1120
|
+
|
|
1121
|
+
<!-- Tooltip rendered inside overlay (moved outside SVG for stacking above colorbar) -->
|
|
1122
|
+
|
|
1123
|
+
<!-- Zoom Selection Rectangle -->
|
|
1124
|
+
{#if drag_start_coords && drag_current_coords}
|
|
1125
|
+
{@const x = Math.min(drag_start_coords.x, drag_current_coords.x)}
|
|
1126
|
+
{@const y = Math.min(drag_start_coords.y, drag_current_coords.y)}
|
|
1127
|
+
{@const rect_width = Math.abs(drag_start_coords.x - drag_current_coords.x)}
|
|
1128
|
+
{@const rect_height = Math.abs(drag_start_coords.y - drag_current_coords.y)}
|
|
1129
|
+
<rect class="zoom-rect" {x} {y} width={rect_width} height={rect_height} />
|
|
1130
|
+
{/if}
|
|
1131
|
+
|
|
1132
|
+
<!-- Zero lines -->
|
|
1133
|
+
{#if display.x_zero_line && x_axis.scale_type === `linear` &&
|
|
1134
|
+
!x_axis.format?.startsWith(`%`) && x_min <= 0 && x_max >= 0}
|
|
1135
|
+
{@const zero_x_pos = x_scale_fn(0)}
|
|
1136
|
+
{#if isFinite(zero_x_pos)}
|
|
1137
|
+
<line
|
|
1138
|
+
class="zero-line"
|
|
1139
|
+
x1={zero_x_pos}
|
|
1140
|
+
x2={zero_x_pos}
|
|
1141
|
+
y1={pad.t}
|
|
1142
|
+
y2={height - pad.b}
|
|
1143
|
+
/>
|
|
1144
|
+
{/if}
|
|
1145
|
+
{/if}
|
|
1146
|
+
{#if display.y_zero_line && y_axis.scale_type === `linear` && y_min <= 0 &&
|
|
1147
|
+
y_max >= 0}
|
|
1148
|
+
{@const zero_y_pos = y_scale_fn(0)}
|
|
1149
|
+
{#if isFinite(zero_y_pos)}
|
|
1150
|
+
<line
|
|
1151
|
+
class="zero-line"
|
|
1152
|
+
x1={pad.l}
|
|
1153
|
+
x2={width - pad.r}
|
|
1154
|
+
y1={zero_y_pos}
|
|
1155
|
+
y2={zero_y_pos}
|
|
1156
|
+
/>
|
|
1157
|
+
{/if}
|
|
1158
|
+
{/if}
|
|
1159
|
+
{#if display.y_zero_line && y2_points.length > 0 &&
|
|
1160
|
+
y2_axis.scale_type === `linear` && y2_min <= 0 && y2_max >= 0}
|
|
1161
|
+
{@const zero_y2_pos = y2_scale_fn(0)}
|
|
1162
|
+
{#if isFinite(zero_y2_pos)}
|
|
1163
|
+
<line
|
|
1164
|
+
class="zero-line"
|
|
1165
|
+
x1={pad.l}
|
|
1166
|
+
x2={width - pad.r}
|
|
1167
|
+
y1={zero_y2_pos}
|
|
1168
|
+
y2={zero_y2_pos}
|
|
1169
|
+
/>
|
|
1018
1170
|
{/if}
|
|
1019
1171
|
{/if}
|
|
1020
1172
|
|
|
@@ -1030,9 +1182,9 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1030
1182
|
</defs>
|
|
1031
1183
|
|
|
1032
1184
|
<!-- Lines -->
|
|
1033
|
-
{#if
|
|
1185
|
+
{#if styles.show_lines}
|
|
1034
1186
|
{#each filtered_series ?? [] as series_data (series_data._id)}
|
|
1035
|
-
{@const series_markers = series_data.markers ??
|
|
1187
|
+
{@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
|
|
1036
1188
|
<g data-series-id={series_data._id} clip-path="url(#{clip_path_id})">
|
|
1037
1189
|
{#if series_markers?.includes(`line`)}
|
|
1038
1190
|
{@const all_line_points = series_data.x.map((x, idx) => ({
|
|
@@ -1048,13 +1200,13 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1048
1200
|
<Line
|
|
1049
1201
|
points={finite_screen_points}
|
|
1050
1202
|
origin={[
|
|
1051
|
-
|
|
1203
|
+
x_axis.format?.startsWith(`%`)
|
|
1052
1204
|
? x_scale_fn(new Date(x_min))
|
|
1053
1205
|
: x_scale_fn(x_min),
|
|
1054
1206
|
series_data.y_axis === `y2` ? y2_scale_fn(y2_min) : y_scale_fn(y_min),
|
|
1055
1207
|
]}
|
|
1056
1208
|
line_color={apply_line_controls
|
|
1057
|
-
?
|
|
1209
|
+
? styles.line?.color ?? `cornflowerblue`
|
|
1058
1210
|
: series_data.line_style?.stroke ??
|
|
1059
1211
|
(Array.isArray(series_data.point_style)
|
|
1060
1212
|
? series_data.point_style[0]?.fill
|
|
@@ -1063,9 +1215,11 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1063
1215
|
? color_scale_fn(series_data.color_values[0])
|
|
1064
1216
|
: `cornflowerblue`)}
|
|
1065
1217
|
line_width={apply_line_controls
|
|
1066
|
-
?
|
|
1218
|
+
? styles.line?.width ?? 2
|
|
1067
1219
|
: series_data.line_style?.stroke_width ?? 2}
|
|
1068
|
-
line_dash={apply_line_controls
|
|
1220
|
+
line_dash={apply_line_controls
|
|
1221
|
+
? styles.line?.dash
|
|
1222
|
+
: series_data.line_style?.line_dash}
|
|
1069
1223
|
area_color="transparent"
|
|
1070
1224
|
{line_tween}
|
|
1071
1225
|
/>
|
|
@@ -1075,9 +1229,9 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1075
1229
|
{/if}
|
|
1076
1230
|
|
|
1077
1231
|
<!-- Points -->
|
|
1078
|
-
{#if
|
|
1232
|
+
{#if styles.show_points}
|
|
1079
1233
|
{#each filtered_series ?? [] as series_data (series_data._id)}
|
|
1080
|
-
{@const series_markers = series_data.markers ??
|
|
1234
|
+
{@const series_markers = series_data.markers ?? DEFAULT_MARKERS}
|
|
1081
1235
|
<g data-series-id={series_data._id}>
|
|
1082
1236
|
{#if series_markers?.includes(`points`)}
|
|
1083
1237
|
{#each series_data.filtered_data as
|
|
@@ -1092,7 +1246,7 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1092
1246
|
...label_style,
|
|
1093
1247
|
offset: {
|
|
1094
1248
|
x: calculated_label_pos.x -
|
|
1095
|
-
(
|
|
1249
|
+
(x_axis.format?.startsWith(`%`)
|
|
1096
1250
|
? x_scale_fn(new Date(point.x))
|
|
1097
1251
|
: x_scale_fn(point.x)),
|
|
1098
1252
|
y: calculated_label_pos.y - (series_data.y_axis === `y2`
|
|
@@ -1118,26 +1272,26 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1118
1272
|
style={{
|
|
1119
1273
|
...point.point_style,
|
|
1120
1274
|
radius: apply_controls
|
|
1121
|
-
?
|
|
1275
|
+
? styles.point?.size ?? (point.size_value != null
|
|
1122
1276
|
? size_scale_fn(point.size_value)
|
|
1123
1277
|
: point.point_style?.radius ?? 4)
|
|
1124
1278
|
: point.size_value != null
|
|
1125
1279
|
? size_scale_fn(point.size_value)
|
|
1126
1280
|
: point.point_style?.radius ?? 4,
|
|
1127
1281
|
stroke_width: apply_controls
|
|
1128
|
-
?
|
|
1282
|
+
? styles.point?.stroke_width ??
|
|
1129
1283
|
point.point_style?.stroke_width ?? 1
|
|
1130
1284
|
: point.point_style?.stroke_width ?? 1,
|
|
1131
1285
|
stroke: apply_controls
|
|
1132
|
-
?
|
|
1286
|
+
? styles.point?.stroke_color ??
|
|
1133
1287
|
point.point_style?.stroke ?? `#000`
|
|
1134
1288
|
: point.point_style?.stroke ?? `#000`,
|
|
1135
1289
|
stroke_opacity: apply_controls
|
|
1136
|
-
?
|
|
1290
|
+
? styles.point?.stroke_opacity ??
|
|
1137
1291
|
point.point_style?.stroke_opacity ?? 1
|
|
1138
1292
|
: point.point_style?.stroke_opacity ?? 1,
|
|
1139
1293
|
fill_opacity: apply_controls
|
|
1140
|
-
?
|
|
1294
|
+
? styles.point?.opacity ??
|
|
1141
1295
|
point.point_style?.fill_opacity ?? 1
|
|
1142
1296
|
: point.point_style?.fill_opacity ?? 1,
|
|
1143
1297
|
cursor: on_point_click ? `pointer` : undefined,
|
|
@@ -1150,7 +1304,7 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1150
1304
|
--point-fill-color={point.color_value != null
|
|
1151
1305
|
? color_scale_fn(point.color_value)
|
|
1152
1306
|
: apply_controls
|
|
1153
|
-
?
|
|
1307
|
+
? styles.point?.color ?? point.point_style?.fill ??
|
|
1154
1308
|
`cornflowerblue`
|
|
1155
1309
|
: point.point_style?.fill ?? `cornflowerblue`}
|
|
1156
1310
|
{...point_events &&
|
|
@@ -1166,188 +1320,6 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1166
1320
|
</g>
|
|
1167
1321
|
{/each}
|
|
1168
1322
|
{/if}
|
|
1169
|
-
|
|
1170
|
-
<g class="x-axis">
|
|
1171
|
-
{#if width > 0 && height > 0}
|
|
1172
|
-
{#each x_tick_values as tick (tick)}
|
|
1173
|
-
{@const tick_pos_raw = x_format?.startsWith(`%`)
|
|
1174
|
-
? x_scale_fn(new Date(tick))
|
|
1175
|
-
: x_scale_fn(tick)}
|
|
1176
|
-
{#if isFinite(tick_pos_raw)}
|
|
1177
|
-
// Check if tick position is finite
|
|
1178
|
-
{@const tick_pos = tick_pos_raw}
|
|
1179
|
-
{#if tick_pos >= pad.l && tick_pos <= width - pad.r}
|
|
1180
|
-
<g class="tick" transform="translate({tick_pos}, {height - pad.b})">
|
|
1181
|
-
{#if x_grid}
|
|
1182
|
-
<line
|
|
1183
|
-
y1={-(height - pad.b - pad.t)}
|
|
1184
|
-
y2="0"
|
|
1185
|
-
{...typeof x_grid === `object` ? x_grid : {}}
|
|
1186
|
-
/>
|
|
1187
|
-
{/if}
|
|
1188
|
-
|
|
1189
|
-
{#if tick >= x_min && tick <= x_max}
|
|
1190
|
-
{@const { x, y } = x_tick_label_shift}
|
|
1191
|
-
<text {x} {y}>{format_value(tick, x_format)}</text>
|
|
1192
|
-
{/if}
|
|
1193
|
-
</g>
|
|
1194
|
-
{/if}
|
|
1195
|
-
{/if}
|
|
1196
|
-
{/each}
|
|
1197
|
-
{/if}
|
|
1198
|
-
|
|
1199
|
-
<!-- Current frame indicator -->
|
|
1200
|
-
{#if current_x_value !== null && current_x_value !== undefined}
|
|
1201
|
-
{@const current_pos_raw = x_format?.startsWith(`%`)
|
|
1202
|
-
? x_scale_fn(new Date(current_x_value))
|
|
1203
|
-
: x_scale_fn(current_x_value)}
|
|
1204
|
-
{#if isFinite(current_pos_raw)}
|
|
1205
|
-
{@const current_pos = current_pos_raw}
|
|
1206
|
-
{#if current_pos >= pad.l && current_pos <= width - pad.r}
|
|
1207
|
-
{@const active_tick_height = 7}
|
|
1208
|
-
<rect
|
|
1209
|
-
x={current_pos - 1.5}
|
|
1210
|
-
y={height - pad.b - active_tick_height / 2}
|
|
1211
|
-
width="3"
|
|
1212
|
-
height={active_tick_height}
|
|
1213
|
-
fill="var(--scatter-current-frame-color, #ff6b35)"
|
|
1214
|
-
stroke="white"
|
|
1215
|
-
stroke-width="0.5"
|
|
1216
|
-
class="current-frame-indicator"
|
|
1217
|
-
/>
|
|
1218
|
-
{/if}
|
|
1219
|
-
{/if}
|
|
1220
|
-
{/if}
|
|
1221
|
-
|
|
1222
|
-
<foreignObject
|
|
1223
|
-
x={width / 2 + (x_label_shift.x ?? 0) - 100}
|
|
1224
|
-
y={height - pad.b - (x_label_shift.y ?? 0) - 10}
|
|
1225
|
-
width="200"
|
|
1226
|
-
height="20"
|
|
1227
|
-
>
|
|
1228
|
-
<div class="axis-label x-label">
|
|
1229
|
-
{@html x_label ?? ``}
|
|
1230
|
-
</div>
|
|
1231
|
-
</foreignObject>
|
|
1232
|
-
</g>
|
|
1233
|
-
|
|
1234
|
-
<g class="y-axis">
|
|
1235
|
-
{#if width > 0 && height > 0}
|
|
1236
|
-
{#each y_tick_values as tick, idx (tick)}
|
|
1237
|
-
{@const tick_pos_raw = y_scale_fn(tick)}
|
|
1238
|
-
{#if isFinite(tick_pos_raw)}
|
|
1239
|
-
// Check if tick position is finite
|
|
1240
|
-
{@const tick_pos = tick_pos_raw}
|
|
1241
|
-
{#if tick_pos >= pad.t && tick_pos <= height - pad.b}
|
|
1242
|
-
<g class="tick" transform="translate({pad.l}, {tick_pos})">
|
|
1243
|
-
{#if y_grid}
|
|
1244
|
-
<line
|
|
1245
|
-
x1="0"
|
|
1246
|
-
x2={width - pad.l - pad.r}
|
|
1247
|
-
{...typeof y_grid === `object` ? y_grid : {}}
|
|
1248
|
-
/>
|
|
1249
|
-
{/if}
|
|
1250
|
-
|
|
1251
|
-
{#if tick >= y_min && tick <= y_max}
|
|
1252
|
-
{@const { x, y } = y_tick_label_shift}
|
|
1253
|
-
<text {x} {y} text-anchor="end" fill={axis_colors.y1 || undefined}>
|
|
1254
|
-
{format_value(tick, y_format)}
|
|
1255
|
-
{#if y_unit && idx === 0}
|
|
1256
|
-
‌ {y_unit}
|
|
1257
|
-
{/if}
|
|
1258
|
-
</text>
|
|
1259
|
-
{/if}
|
|
1260
|
-
</g>
|
|
1261
|
-
{/if}
|
|
1262
|
-
{/if}
|
|
1263
|
-
{/each}
|
|
1264
|
-
{/if}
|
|
1265
|
-
|
|
1266
|
-
{#if height > 0}
|
|
1267
|
-
<foreignObject
|
|
1268
|
-
x={-100}
|
|
1269
|
-
y={-10}
|
|
1270
|
-
width="200"
|
|
1271
|
-
height="20"
|
|
1272
|
-
transform="rotate(-90, {y_label_shift.y ?? 20}, {pad.t +
|
|
1273
|
-
(height - pad.t - pad.b) / 2 +
|
|
1274
|
-
(y_label_shift.x ?? 0)}) translate({y_label_shift.y ?? 20}, {pad.t +
|
|
1275
|
-
(height - pad.t - pad.b) / 2 +
|
|
1276
|
-
(y_label_shift.x ?? 0)})"
|
|
1277
|
-
>
|
|
1278
|
-
<div class="axis-label y-label" style:color={axis_colors.y1 || undefined}>
|
|
1279
|
-
{@html y_label ?? ``}
|
|
1280
|
-
</div>
|
|
1281
|
-
</foreignObject>
|
|
1282
|
-
{/if}
|
|
1283
|
-
</g>
|
|
1284
|
-
|
|
1285
|
-
<!-- Y2-axis (Right) -->
|
|
1286
|
-
{#if y2_points.length > 0}
|
|
1287
|
-
<g class="y2-axis">
|
|
1288
|
-
{#if width > 0 && height > 0}
|
|
1289
|
-
{#each y2_tick_values as tick, idx (tick)}
|
|
1290
|
-
{@const tick_pos_raw = y2_scale_fn(tick)}
|
|
1291
|
-
{#if isFinite(tick_pos_raw)}
|
|
1292
|
-
// Check if tick position is finite
|
|
1293
|
-
{@const tick_pos = tick_pos_raw}
|
|
1294
|
-
{#if tick_pos >= pad.t && tick_pos <= height - pad.b}
|
|
1295
|
-
<g class="tick" transform="translate({width - pad.r}, {tick_pos})">
|
|
1296
|
-
{#if y2_grid}
|
|
1297
|
-
<line
|
|
1298
|
-
x1={-(width - pad.l - pad.r)}
|
|
1299
|
-
x2="0"
|
|
1300
|
-
{...typeof y2_grid === `object` ? y2_grid : {}}
|
|
1301
|
-
/>
|
|
1302
|
-
{/if}
|
|
1303
|
-
|
|
1304
|
-
{#if tick >= y2_min && tick <= y2_max}
|
|
1305
|
-
{@const { x, y } = y2_tick_label_shift}
|
|
1306
|
-
<text {x} {y} text-anchor="start" fill={axis_colors.y2}>
|
|
1307
|
-
{format_value(tick, y2_format)}
|
|
1308
|
-
{#if y2_unit && idx === 0}
|
|
1309
|
-
‌ {y2_unit}
|
|
1310
|
-
{/if}
|
|
1311
|
-
</text>
|
|
1312
|
-
{/if}
|
|
1313
|
-
</g>
|
|
1314
|
-
{/if}
|
|
1315
|
-
{/if}
|
|
1316
|
-
{/each}
|
|
1317
|
-
{/if}
|
|
1318
|
-
|
|
1319
|
-
{#if height > 0 && y2_label}
|
|
1320
|
-
<foreignObject
|
|
1321
|
-
x={-100}
|
|
1322
|
-
y={-10}
|
|
1323
|
-
width="200"
|
|
1324
|
-
height="20"
|
|
1325
|
-
transform="rotate(-90, {width - pad.r + (y2_label_shift.y ?? 0)}, {pad.t +
|
|
1326
|
-
(height - pad.t - pad.b) / 2 +
|
|
1327
|
-
(y2_label_shift.x ?? 0)}) translate({width -
|
|
1328
|
-
pad.r +
|
|
1329
|
-
(y2_label_shift.y ?? 0)}, {pad.t +
|
|
1330
|
-
(height - pad.t - pad.b) / 2 +
|
|
1331
|
-
(y2_label_shift.x ?? 0)})"
|
|
1332
|
-
>
|
|
1333
|
-
<div class="axis-label y2-label" style:color={axis_colors.y2}>
|
|
1334
|
-
{@html y2_label ?? ``}
|
|
1335
|
-
</div>
|
|
1336
|
-
</foreignObject>
|
|
1337
|
-
{/if}
|
|
1338
|
-
</g>
|
|
1339
|
-
{/if}
|
|
1340
|
-
|
|
1341
|
-
<!-- Tooltip rendered inside overlay (moved outside SVG for stacking above colorbar) -->
|
|
1342
|
-
|
|
1343
|
-
<!-- Zoom Selection Rectangle -->
|
|
1344
|
-
{#if drag_start_coords && drag_current_coords}
|
|
1345
|
-
{@const x = Math.min(drag_start_coords.x, drag_current_coords.x)}
|
|
1346
|
-
{@const y = Math.min(drag_start_coords.y, drag_current_coords.y)}
|
|
1347
|
-
{@const rect_width = Math.abs(drag_start_coords.x - drag_current_coords.x)}
|
|
1348
|
-
{@const rect_height = Math.abs(drag_start_coords.y - drag_current_coords.y)}
|
|
1349
|
-
<rect class="zoom-rect" {x} {y} width={rect_width} height={rect_height} />
|
|
1350
|
-
{/if}
|
|
1351
1323
|
</svg>
|
|
1352
1324
|
|
|
1353
1325
|
<!-- Tooltip overlay above all plot overlays (legend, colorbar) -->
|
|
@@ -1355,7 +1327,7 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1355
1327
|
{@const { x, y, metadata, color_value, point_label, point_style, series_idx } =
|
|
1356
1328
|
tooltip_point}
|
|
1357
1329
|
{@const hovered_series = series_with_ids[series_idx]}
|
|
1358
|
-
{@const series_markers = hovered_series?.markers ??
|
|
1330
|
+
{@const series_markers = hovered_series?.markers ?? DEFAULT_MARKERS}
|
|
1359
1331
|
{@const is_transparent_or_none = (color: string | undefined | null): boolean =>
|
|
1360
1332
|
!color || color === `none` || color === `transparent` ||
|
|
1361
1333
|
(color.startsWith(`rgba(`) && color.endsWith(`, 0)`))}
|
|
@@ -1390,10 +1362,13 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1390
1362
|
}
|
|
1391
1363
|
return `rgba(0, 0, 0, 0.7)`
|
|
1392
1364
|
})()}
|
|
1393
|
-
{@const cx =
|
|
1365
|
+
{@const cx = x_axis.format?.startsWith(`%`) ? x_scale_fn(new Date(x)) : x_scale_fn(x)}
|
|
1394
1366
|
{@const cy = (hovered_series?.y_axis === `y2` ? y2_scale_fn : y_scale_fn)(y)}
|
|
1395
|
-
{@const x_formatted = format_value(x,
|
|
1396
|
-
{@const y_formatted = format_value(
|
|
1367
|
+
{@const x_formatted = format_value(x, x_axis.format || `.3~s`)}
|
|
1368
|
+
{@const y_formatted = format_value(
|
|
1369
|
+
y,
|
|
1370
|
+
(hovered_series?.y_axis === `y2` ? y2_axis.format : y_axis.format) || `.3~s`,
|
|
1371
|
+
)}
|
|
1397
1372
|
{@const label = point_label?.text ?? null}
|
|
1398
1373
|
{@const tooltip_lum = luminance(tooltip_bg_color ?? `rgba(0, 0, 0, 0.7)`)}
|
|
1399
1374
|
{@const tooltip_text_color = tooltip_lum > 0.5 ? `#000000` : `#ffffff`}
|
|
@@ -1412,41 +1387,22 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1412
1387
|
</div>
|
|
1413
1388
|
{/if}
|
|
1414
1389
|
|
|
1415
|
-
<!-- Control Pane
|
|
1416
|
-
{#if
|
|
1390
|
+
<!-- Control Pane -->
|
|
1391
|
+
{#if controls.show}
|
|
1417
1392
|
<ScatterPlotControls
|
|
1418
|
-
toggle_props={
|
|
1419
|
-
|
|
1420
|
-
bind:
|
|
1421
|
-
bind:
|
|
1422
|
-
bind:
|
|
1423
|
-
bind:
|
|
1424
|
-
bind:
|
|
1425
|
-
bind:
|
|
1426
|
-
bind:x_range
|
|
1427
|
-
bind:y_range
|
|
1428
|
-
bind:y2_range
|
|
1393
|
+
toggle_props={controls.toggle_props}
|
|
1394
|
+
pane_props={controls.pane_props}
|
|
1395
|
+
bind:x_axis
|
|
1396
|
+
bind:y_axis
|
|
1397
|
+
bind:y2_axis
|
|
1398
|
+
bind:display
|
|
1399
|
+
bind:styles
|
|
1400
|
+
bind:controls
|
|
1429
1401
|
{auto_x_range}
|
|
1430
1402
|
{auto_y_range}
|
|
1431
1403
|
{auto_y2_range}
|
|
1432
|
-
bind:point_size
|
|
1433
|
-
bind:point_color
|
|
1434
|
-
bind:point_opacity
|
|
1435
|
-
bind:point_stroke_width
|
|
1436
|
-
bind:point_stroke_color
|
|
1437
|
-
bind:point_stroke_opacity
|
|
1438
|
-
bind:line_width
|
|
1439
|
-
bind:line_color
|
|
1440
|
-
bind:line_opacity
|
|
1441
|
-
bind:line_dash
|
|
1442
|
-
bind:show_points
|
|
1443
|
-
bind:show_lines
|
|
1444
1404
|
bind:selected_series_idx
|
|
1445
|
-
bind:x_format
|
|
1446
|
-
bind:y_format
|
|
1447
|
-
bind:y2_format
|
|
1448
1405
|
series={series_with_ids}
|
|
1449
|
-
{plot_controls}
|
|
1450
1406
|
has_y2_points={y2_points.length > 0}
|
|
1451
1407
|
/>
|
|
1452
1408
|
{/if}
|
|
@@ -1454,15 +1410,17 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1454
1410
|
<!-- Color Bar -->
|
|
1455
1411
|
{#if color_bar && all_color_values.length > 0 && color_bar_placement}
|
|
1456
1412
|
{@const color_domain = [
|
|
1457
|
-
color_scale.value_range?.[0] ??
|
|
1458
|
-
|
|
1413
|
+
(typeof color_scale === `string` ? undefined : color_scale.value_range)?.[0] ??
|
|
1414
|
+
auto_color_range[0],
|
|
1415
|
+
(typeof color_scale === `string` ? undefined : color_scale.value_range)?.[1] ??
|
|
1416
|
+
auto_color_range[1],
|
|
1459
1417
|
] as [number, number]}
|
|
1460
1418
|
<ColorBar
|
|
1461
1419
|
tick_labels={4}
|
|
1462
1420
|
tick_side="primary"
|
|
1463
1421
|
{color_scale_fn}
|
|
1464
1422
|
color_scale_domain={color_domain}
|
|
1465
|
-
scale_type={color_scale.type}
|
|
1423
|
+
scale_type={typeof color_scale === `string` ? undefined : color_scale.type}
|
|
1466
1424
|
range={color_domain?.every((val) => val != null) ? color_domain : undefined}
|
|
1467
1425
|
wrapper_style={`
|
|
1468
1426
|
position: absolute;
|
|
@@ -1519,15 +1477,16 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1519
1477
|
<style>
|
|
1520
1478
|
div.scatter {
|
|
1521
1479
|
position: relative; /* Needed for absolute positioning of children like ColorBar */
|
|
1522
|
-
width: 100
|
|
1523
|
-
height: 100
|
|
1524
|
-
min-height: var(--scatter-min-height,
|
|
1480
|
+
width: var(--scatter-width, 100%);
|
|
1481
|
+
height: var(--scatter-height, 100%);
|
|
1482
|
+
min-height: var(--scatter-min-height, 350px);
|
|
1525
1483
|
container-type: size; /* enable cqh for panes */
|
|
1526
1484
|
container-name: scatter-plot;
|
|
1527
1485
|
z-index: var(--scatter-z-index);
|
|
1528
1486
|
}
|
|
1529
1487
|
svg {
|
|
1530
1488
|
width: 100%;
|
|
1489
|
+
height: 100%;
|
|
1531
1490
|
fill: var(--text-color);
|
|
1532
1491
|
font-weight: var(--scatter-font-weight);
|
|
1533
1492
|
overflow: visible;
|
|
@@ -1588,4 +1547,10 @@ let has_multiple_series = $derived(series_with_ids.filter(Boolean).length > 1);
|
|
|
1588
1547
|
stroke-width: var(--scatter-zoom-rect-stroke-width, 1);
|
|
1589
1548
|
pointer-events: none; /* Prevent rect from interfering with mouse events */
|
|
1590
1549
|
}
|
|
1550
|
+
.zero-line {
|
|
1551
|
+
stroke: var(--scatter-zero-line-color, light-dark(black, white));
|
|
1552
|
+
stroke-width: var(--scatter-zero-line-width, 1);
|
|
1553
|
+
stroke-dasharray: none;
|
|
1554
|
+
opacity: var(--scatter-zero-line-opacity, 0.3);
|
|
1555
|
+
}
|
|
1591
1556
|
</style>
|