matterviz 0.3.5 → 0.3.7
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/MillerIndexInput.svelte +5 -5
- package/dist/api/optimade.js +3 -3
- package/dist/brillouin/BrillouinZone.svelte +5 -2
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +1 -3
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +1 -1
- package/dist/brillouin/BrillouinZoneScene.svelte +5 -5
- package/dist/brillouin/compute.js +21 -21
- package/dist/brillouin/index.d.ts +1 -1
- package/dist/brillouin/index.js +0 -1
- package/dist/brillouin/types.d.ts +8 -13
- package/dist/chempot-diagram/ChemPotDiagram.svelte +3 -3
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +3 -4
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +33 -34
- package/dist/chempot-diagram/compute.js +1 -7
- package/dist/chempot-diagram/temperature.d.ts +1 -1
- package/dist/chempot-diagram/temperature.js +1 -3
- package/dist/chempot-diagram/types.d.ts +4 -9
- package/dist/colors/index.js +5 -5
- package/dist/composition/Composition.svelte +2 -1
- package/dist/composition/Formula.svelte +7 -4
- package/dist/composition/FormulaFilter.svelte +1 -3
- package/dist/composition/format.js +4 -4
- package/dist/composition/parse.d.ts +2 -1
- package/dist/composition/parse.js +61 -46
- package/dist/convex-hull/ConvexHull2D.svelte +62 -51
- package/dist/convex-hull/ConvexHull3D.svelte +101 -90
- package/dist/convex-hull/ConvexHull4D.svelte +70 -58
- package/dist/convex-hull/ConvexHullControls.svelte +24 -35
- package/dist/convex-hull/ConvexHullInfoPane.svelte +8 -5
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +2 -0
- package/dist/convex-hull/ConvexHullStats.svelte +9 -2
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +2 -0
- package/dist/convex-hull/GasPressureControls.svelte +7 -7
- package/dist/convex-hull/StructurePopup.svelte +65 -30
- package/dist/convex-hull/StructurePopup.svelte.d.ts +6 -6
- package/dist/convex-hull/TemperatureSlider.svelte +8 -5
- package/dist/convex-hull/barycentric-coords.d.ts +2 -2
- package/dist/convex-hull/barycentric-coords.js +2 -2
- package/dist/convex-hull/gas-thermodynamics.js +2 -4
- package/dist/convex-hull/helpers.d.ts +13 -2
- package/dist/convex-hull/helpers.js +37 -16
- package/dist/convex-hull/index.d.ts +1 -0
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +2 -1
- package/dist/convex-hull/thermodynamics.js +7 -7
- package/dist/convex-hull/types.d.ts +15 -15
- package/dist/effects.svelte.d.ts +12 -0
- package/dist/effects.svelte.js +37 -0
- package/dist/element/BohrAtom.svelte +4 -4
- package/dist/element/data.json.gz.d.ts +3 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +0 -1
- package/dist/fermi-surface/FermiSurface.svelte +4 -4
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte +15 -19
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +8 -6
- package/dist/fermi-surface/compute.js +2 -2
- package/dist/fermi-surface/export.js +13 -26
- package/dist/fermi-surface/parse.js +8 -12
- package/dist/fermi-surface/types.d.ts +2 -5
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +21 -3
- package/dist/heatmap-matrix/index.js +6 -6
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +1 -1
- package/dist/io/export.js +1 -1
- package/dist/io/index.d.ts +1 -1
- package/dist/io/index.js +0 -1
- package/dist/io/url-drop.js +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +11 -25
- package/dist/isosurface/slice.js +1 -1
- package/dist/isosurface/types.js +12 -12
- package/dist/labels.d.ts +1 -1
- package/dist/labels.js +14 -11
- package/dist/layout/InfoTag.svelte +6 -4
- package/dist/layout/PropertyFilter.svelte +4 -2
- package/dist/layout/json-tree/JsonTree.svelte +22 -14
- package/dist/layout/json-tree/JsonValue.svelte +2 -2
- package/dist/layout/json-tree/types.d.ts +3 -2
- package/dist/layout/json-tree/types.js +0 -1
- package/dist/layout/json-tree/utils.d.ts +4 -4
- package/dist/layout/json-tree/utils.js +12 -20
- package/dist/marching-cubes.js +13 -15
- package/dist/math.d.ts +11 -1
- package/dist/math.js +15 -6
- package/dist/overlays/DragControlTab.svelte +98 -0
- package/dist/overlays/DragControlTab.svelte.d.ts +8 -0
- package/dist/overlays/DraggablePane.svelte +7 -84
- package/dist/overlays/index.d.ts +1 -0
- package/dist/overlays/index.js +1 -0
- package/dist/periodic-table/PeriodicTable.svelte +11 -11
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +4 -2
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramControls.svelte +4 -9
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +2 -10
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +2 -3
- package/dist/phase-diagram/TdbInfoPanel.svelte +3 -3
- package/dist/phase-diagram/build-diagram.js +11 -18
- package/dist/phase-diagram/diagram-input.d.ts +5 -9
- package/dist/phase-diagram/index.d.ts +2 -2
- package/dist/phase-diagram/index.js +0 -2
- package/dist/phase-diagram/parse.d.ts +2 -2
- package/dist/phase-diagram/parse.js +6 -10
- package/dist/phase-diagram/svg-to-diagram.js +15 -15
- package/dist/phase-diagram/types.d.ts +5 -11
- package/dist/phase-diagram/utils.d.ts +2 -2
- package/dist/phase-diagram/utils.js +9 -11
- package/dist/plot/BarPlot.svelte +162 -314
- package/dist/plot/BarPlot.svelte.d.ts +5 -4
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/BinnedScatterPlot.svelte +1114 -0
- package/dist/plot/BinnedScatterPlot.svelte.d.ts +66 -0
- package/dist/plot/ColorBar.svelte +19 -17
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/FillArea.svelte +2 -4
- package/dist/plot/FillArea.svelte.d.ts +1 -1
- package/dist/plot/Histogram.svelte +167 -281
- package/dist/plot/Histogram.svelte.d.ts +1 -1
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/InteractiveAxisLabel.svelte +5 -3
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/PlotAxis.svelte +169 -0
- package/dist/plot/PlotAxis.svelte.d.ts +24 -0
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/ReferenceLine3D.svelte +53 -51
- package/dist/plot/ReferencePlane.svelte +39 -42
- package/dist/plot/ScatterPlot.svelte +300 -367
- package/dist/plot/ScatterPlot.svelte.d.ts +8 -5
- package/dist/plot/ScatterPlot3D.svelte +33 -6
- package/dist/plot/ScatterPlot3D.svelte.d.ts +3 -2
- package/dist/plot/ScatterPlot3DControls.svelte +9 -9
- package/dist/plot/ScatterPlotControls.svelte +3 -4
- package/dist/plot/ScatterPoint.svelte +18 -27
- package/dist/plot/ScatterPoint.svelte.d.ts +4 -3
- package/dist/plot/Surface3D.svelte +4 -7
- package/dist/plot/ZeroLines.svelte +2 -1
- package/dist/plot/ZeroLines.svelte.d.ts +2 -1
- package/dist/plot/ZoomRect.svelte +2 -2
- package/dist/plot/ZoomRect.svelte.d.ts +3 -3
- package/dist/plot/adaptive-density.d.ts +69 -0
- package/dist/plot/adaptive-density.js +191 -0
- package/dist/plot/auto-place.d.ts +43 -0
- package/dist/plot/auto-place.js +122 -0
- package/dist/plot/axis-utils.js +3 -5
- package/dist/plot/binned-scatter-types.d.ts +59 -0
- package/dist/plot/binned-scatter-types.js +1 -0
- package/dist/plot/data-cleaning.js +1 -1
- package/dist/plot/data-transform.js +1 -1
- package/dist/plot/fill-utils.d.ts +4 -9
- package/dist/plot/fill-utils.js +29 -44
- package/dist/plot/index.d.ts +4 -0
- package/dist/plot/index.js +2 -0
- package/dist/plot/interactions.d.ts +4 -4
- package/dist/plot/interactions.js +4 -3
- package/dist/plot/layout.d.ts +20 -2
- package/dist/plot/layout.js +59 -16
- package/dist/plot/reference-line.d.ts +1 -1
- package/dist/plot/reference-line.js +9 -11
- package/dist/plot/scales.d.ts +1 -1
- package/dist/plot/scales.js +20 -23
- package/dist/plot/types.d.ts +30 -58
- package/dist/plot/types.js +2 -6
- package/dist/plot/utils/label-placement.d.ts +24 -3
- package/dist/plot/utils/label-placement.js +82 -12
- package/dist/plot/utils/series-visibility.d.ts +8 -2
- package/dist/plot/utils/series-visibility.js +23 -5
- package/dist/rdf/RdfPlot.svelte +5 -5
- package/dist/rdf/calc-rdf.js +3 -3
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.js +2 -0
- package/dist/spectral/Bands.svelte +1 -1
- package/dist/spectral/BandsAndDos.svelte +22 -16
- package/dist/spectral/BrillouinBandsDos.svelte +20 -16
- package/dist/spectral/Dos.svelte +1 -1
- package/dist/spectral/helpers.d.ts +4 -2
- package/dist/spectral/helpers.js +44 -35
- package/dist/spectral/index.d.ts +1 -1
- package/dist/spectral/index.js +0 -1
- package/dist/structure/AtomLegend.svelte +23 -6
- package/dist/structure/AtomLegend.svelte.d.ts +1 -0
- package/dist/structure/CanvasTooltip.svelte +9 -9
- package/dist/structure/CanvasTooltip.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +14 -16
- package/dist/structure/Structure.svelte +317 -68
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureControls.svelte +20 -45
- package/dist/structure/StructureExportPane.svelte +2 -1
- package/dist/structure/StructureInfoPane.svelte +10 -8
- package/dist/structure/StructureScene.svelte +527 -177
- package/dist/structure/StructureScene.svelte.d.ts +5 -2
- package/dist/structure/atom-properties.js +4 -4
- package/dist/structure/bond-order-perception.js +115 -98
- package/dist/structure/bonding.d.ts +27 -1
- package/dist/structure/bonding.js +187 -16
- package/dist/structure/export.js +1 -1
- package/dist/structure/index.d.ts +3 -2
- package/dist/structure/index.js +0 -2
- package/dist/structure/parse.js +88 -59
- package/dist/symmetry/WyckoffTable.svelte +7 -0
- package/dist/symmetry/index.js +13 -14
- package/dist/table/HeatmapTable.svelte +45 -66
- package/dist/table/HeatmapTable.svelte.d.ts +1 -1
- package/dist/table/ToggleMenu.svelte +19 -10
- package/dist/theme/themes.mjs +12 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +0 -1
- package/dist/trajectory/Trajectory.svelte +43 -15
- package/dist/trajectory/TrajectoryInfoPane.svelte +2 -2
- package/dist/trajectory/extract.js +1 -1
- package/dist/trajectory/frame-reader.js +4 -4
- package/dist/trajectory/helpers.d.ts +5 -4
- package/dist/trajectory/helpers.js +9 -17
- package/dist/trajectory/index.d.ts +2 -2
- package/dist/trajectory/index.js +2 -2
- package/dist/trajectory/parse/ase.js +4 -4
- package/dist/trajectory/parse/hdf5.js +1 -1
- package/dist/trajectory/parse/index.js +2 -3
- package/dist/trajectory/parse/lammps.js +1 -1
- package/dist/trajectory/parse/vasp.js +1 -1
- package/dist/trajectory/plotting.d.ts +1 -1
- package/dist/trajectory/plotting.js +38 -38
- package/dist/trajectory/types.d.ts +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +9 -0
- package/dist/xrd/calc-xrd.js +3 -4
- package/dist/xrd/parse.js +1 -1
- package/package.json +42 -22
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { format_value, symbol_names } from '../labels'
|
|
8
8
|
import { sanitize_html } from '../sanitize'
|
|
9
9
|
import { FullscreenToggle, set_fullscreen_bg } from '../layout'
|
|
10
|
-
import type { Vec2 } from '../math'
|
|
10
|
+
import type { Point2D, Vec2 } from '../math'
|
|
11
11
|
import type {
|
|
12
12
|
AxisLoadError,
|
|
13
13
|
BasePlotProps,
|
|
@@ -30,18 +30,16 @@
|
|
|
30
30
|
ScaleType,
|
|
31
31
|
ScatterHandlerEvent,
|
|
32
32
|
ScatterHandlerProps,
|
|
33
|
-
Sides,
|
|
34
33
|
StyleOverrides,
|
|
35
34
|
UserContentProps,
|
|
36
|
-
XyObj,
|
|
37
35
|
} from './'
|
|
38
36
|
import {
|
|
39
|
-
AxisLabel,
|
|
40
37
|
ColorBar,
|
|
41
38
|
compute_element_placement,
|
|
42
39
|
FillArea,
|
|
43
40
|
get_tick_label,
|
|
44
41
|
Line,
|
|
42
|
+
PlotAxis,
|
|
45
43
|
PlotLegend,
|
|
46
44
|
PlotTooltip,
|
|
47
45
|
ReferenceLine,
|
|
@@ -50,6 +48,12 @@
|
|
|
50
48
|
ZeroLines,
|
|
51
49
|
ZoomRect,
|
|
52
50
|
} from './'
|
|
51
|
+
import {
|
|
52
|
+
build_obstacles_norm,
|
|
53
|
+
has_explicit_position,
|
|
54
|
+
measured_footprint,
|
|
55
|
+
place_decorations,
|
|
56
|
+
} from './auto-place'
|
|
53
57
|
import type { AxisChangeState } from './axis-utils'
|
|
54
58
|
import { create_axis_change_handler } from './axis-utils'
|
|
55
59
|
import {
|
|
@@ -63,12 +67,12 @@
|
|
|
63
67
|
create_hover_lock,
|
|
64
68
|
} from './hover-lock.svelte'
|
|
65
69
|
import {
|
|
66
|
-
DEFAULT_GRID_STYLE,
|
|
67
70
|
DEFAULT_MARKERS,
|
|
68
71
|
get_scale_type_name,
|
|
69
72
|
is_time_scale,
|
|
70
73
|
} from './types'
|
|
71
74
|
import { compute_label_positions } from './utils/label-placement'
|
|
75
|
+
import type { SeriesVisibilitySnapshot } from './utils/series-visibility'
|
|
72
76
|
import {
|
|
73
77
|
handle_legend_double_click,
|
|
74
78
|
toggle_group_visibility,
|
|
@@ -101,13 +105,15 @@
|
|
|
101
105
|
pixels_to_data_delta,
|
|
102
106
|
sync_y2_range,
|
|
103
107
|
} from './interactions'
|
|
104
|
-
import type { Rect } from './layout'
|
|
108
|
+
import type { Rect, Sides } from './layout'
|
|
105
109
|
import {
|
|
106
110
|
calc_auto_padding,
|
|
107
111
|
constrain_tooltip_position,
|
|
108
112
|
filter_padding,
|
|
109
113
|
LABEL_GAP_DEFAULT,
|
|
114
|
+
measure_full_footprint,
|
|
110
115
|
measure_max_tick_width,
|
|
116
|
+
sample_series_obstacle_points,
|
|
111
117
|
} from './layout'
|
|
112
118
|
import type { IndexedRefLine } from './reference-line'
|
|
113
119
|
import { group_ref_lines_by_z, index_ref_lines } from './reference-line'
|
|
@@ -119,6 +125,9 @@
|
|
|
119
125
|
get_nice_data_range,
|
|
120
126
|
} from './scales'
|
|
121
127
|
|
|
128
|
+
const in_range = (val: number | null | undefined, lo: number, hi: number) =>
|
|
129
|
+
val != null && !isNaN(val) && val >= Math.min(lo, hi) && val <= Math.max(lo, hi)
|
|
130
|
+
|
|
122
131
|
let {
|
|
123
132
|
series = $bindable([]),
|
|
124
133
|
x_axis = $bindable({}),
|
|
@@ -205,14 +214,15 @@
|
|
|
205
214
|
color_bar?:
|
|
206
215
|
| (ComponentProps<typeof ColorBar> & {
|
|
207
216
|
margin?: number | Sides
|
|
208
|
-
tween?: TweenOptions<
|
|
217
|
+
tween?: TweenOptions<Point2D>
|
|
209
218
|
responsive?: boolean // Allow colorbar to reposition if density changes (default: false)
|
|
219
|
+
axis_clearance?: number // Min distance kept from plot edges/axes (default: 15)
|
|
210
220
|
})
|
|
211
221
|
| null
|
|
212
222
|
label_placement_config?: Partial<LabelPlacementConfig>
|
|
213
223
|
hover_config?: Partial<HoverConfig>
|
|
214
224
|
legend?: LegendConfig | null
|
|
215
|
-
point_tween?: TweenOptions<
|
|
225
|
+
point_tween?: TweenOptions<Point2D>
|
|
216
226
|
line_tween?: TweenOptions<string>
|
|
217
227
|
point_events?: Record<
|
|
218
228
|
string,
|
|
@@ -295,8 +305,8 @@
|
|
|
295
305
|
)
|
|
296
306
|
|
|
297
307
|
// State for rectangle zoom selection
|
|
298
|
-
let drag_start_coords = $state<
|
|
299
|
-
let drag_current_coords = $state<
|
|
308
|
+
let drag_start_coords = $state<Point2D | null>(null)
|
|
309
|
+
let drag_current_coords = $state<Point2D | null>(null)
|
|
300
310
|
|
|
301
311
|
// Zoom/pan state - track both initial (data-driven) and current (after pan/zoom) ranges
|
|
302
312
|
let initial_x_range = $state<[number, number]>([0, 1])
|
|
@@ -307,7 +317,7 @@
|
|
|
307
317
|
let zoom_x2_range = $state<[number, number]>([0, 1])
|
|
308
318
|
let zoom_y_range = $state<[number, number]>([0, 1])
|
|
309
319
|
let zoom_y2_range = $state<[number, number]>([0, 1])
|
|
310
|
-
let
|
|
320
|
+
let prev_series_visibility: SeriesVisibilitySnapshot | null = $state(null)
|
|
311
321
|
|
|
312
322
|
// Y2 axis sync configuration
|
|
313
323
|
let y2_sync_config = $derived(normalize_y2_sync(y2_axis?.sync))
|
|
@@ -348,7 +358,7 @@
|
|
|
348
358
|
>(null)
|
|
349
359
|
|
|
350
360
|
// Fill region hover state
|
|
351
|
-
let
|
|
361
|
+
let hovered_fill_key = $state<string | null>(null)
|
|
352
362
|
|
|
353
363
|
// Reference line hover state
|
|
354
364
|
let hovered_ref_line_idx = $state<number | null>(null)
|
|
@@ -357,7 +367,7 @@
|
|
|
357
367
|
let axis_loading = $state<`x` | `x2` | `y` | `y2` | null>(null)
|
|
358
368
|
|
|
359
369
|
// State to hold the calculated label positions after simulation
|
|
360
|
-
let label_positions = $state<Record<string,
|
|
370
|
+
let label_positions = $state<Record<string, Point2D>>({})
|
|
361
371
|
|
|
362
372
|
// State for legend dragging
|
|
363
373
|
let legend_is_dragging = $state(false)
|
|
@@ -414,9 +424,9 @@
|
|
|
414
424
|
let y2_points = $derived(points_by_axis.y2)
|
|
415
425
|
let x2_points = $derived(points_by_axis.x2)
|
|
416
426
|
|
|
417
|
-
// Layout:
|
|
427
|
+
// Layout: tick-label padding (decoration reservations are added in `pad` below)
|
|
418
428
|
const default_padding = { t: 5, b: 50, l: 50, r: 20 }
|
|
419
|
-
let
|
|
429
|
+
let base_pad = $state(untrack(() => filter_padding(padding, default_padding)))
|
|
420
430
|
|
|
421
431
|
// Update padding when format or ticks change
|
|
422
432
|
$effect(() => {
|
|
@@ -432,13 +442,84 @@
|
|
|
432
442
|
: filter_padding(padding, default_padding)
|
|
433
443
|
|
|
434
444
|
if (
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
)
|
|
445
|
+
base_pad.t !== new_pad.t ||
|
|
446
|
+
base_pad.b !== new_pad.b ||
|
|
447
|
+
base_pad.l !== new_pad.l ||
|
|
448
|
+
base_pad.r !== new_pad.r
|
|
449
|
+
) base_pad = new_pad
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// === Auto-move legend/colorbar outside the plot when interior overlap is unavoidable ===
|
|
453
|
+
// (shared logic lives in auto-place.ts so every 2D plot reuses it)
|
|
454
|
+
// ColorBar's orientation prop defaults to horizontal, so treat unset as horizontal too
|
|
455
|
+
const colorbar_is_horizontal = $derived((color_bar?.orientation ?? `horizontal`) === `horizontal`)
|
|
456
|
+
const colorbar_footprint = $derived(
|
|
457
|
+
colorbar_element?.offsetWidth && colorbar_element?.offsetHeight
|
|
458
|
+
? measure_full_footprint(colorbar_element)
|
|
459
|
+
: colorbar_is_horizontal
|
|
460
|
+
? { width: 220, height: 56 }
|
|
461
|
+
: { width: 56, height: 100 },
|
|
462
|
+
)
|
|
463
|
+
const legend_footprint = $derived(measured_footprint(legend_element, { width: 120, height: 80 }))
|
|
464
|
+
const legend_has_explicit_pos = $derived(has_explicit_position(legend?.style))
|
|
465
|
+
|
|
466
|
+
// Plot-specific obstacle field: series points/lines normalized to [0,1] (y=0 at top)
|
|
467
|
+
const obstacles_norm = $derived.by(() => {
|
|
468
|
+
if (!width || !height || !filtered_series) return []
|
|
469
|
+
const base_w = width - base_pad.l - base_pad.r
|
|
470
|
+
const base_h = height - base_pad.t - base_pad.b
|
|
471
|
+
if (base_w <= 0 || base_h <= 0) return []
|
|
472
|
+
const norm_x = is_time_x
|
|
473
|
+
? scaleTime().domain([new Date(x_min), new Date(x_max)]).range([0, 1])
|
|
474
|
+
: create_scale(final_x_axis.scale_type ?? `linear`, [x_min, x_max], [0, 1])
|
|
475
|
+
const norm_y = create_scale(final_y_axis.scale_type ?? `linear`, [y_min, y_max], [0, 1])
|
|
476
|
+
return build_obstacles_norm(
|
|
477
|
+
filtered_series
|
|
478
|
+
.filter((srs) => srs?.filtered_data)
|
|
479
|
+
.map((srs) => ({
|
|
480
|
+
points: srs.filtered_data.map((pt) => ({
|
|
481
|
+
x: is_time_x ? norm_x(new Date(pt.x)) : norm_x(pt.x),
|
|
482
|
+
y: 1 - norm_y(pt.y), // norm_y is 0 at bottom; invert so 0 = top
|
|
483
|
+
})),
|
|
484
|
+
draws_line: styles.show_lines && (srs.markers ?? DEFAULT_MARKERS).includes(`line`),
|
|
485
|
+
})),
|
|
486
|
+
base_w,
|
|
487
|
+
base_h,
|
|
488
|
+
)
|
|
440
489
|
})
|
|
441
490
|
|
|
491
|
+
const decor = $derived.by(() =>
|
|
492
|
+
place_decorations({
|
|
493
|
+
base_pad,
|
|
494
|
+
width,
|
|
495
|
+
height,
|
|
496
|
+
obstacles_norm,
|
|
497
|
+
// gate on legend_element (the actual render signal) not legend_data, whose fill entries read
|
|
498
|
+
// computed_fills -> pad and would make this derived reference itself
|
|
499
|
+
legend: legend != null && legend_element != null &&
|
|
500
|
+
!legend_has_explicit_pos && !legend_is_dragging && !legend_manual_position
|
|
501
|
+
? { footprint: legend_footprint, clearance: legend?.axis_clearance }
|
|
502
|
+
: null,
|
|
503
|
+
// gate on a measured colorbar: its outside style stretches it to full width, so deciding from
|
|
504
|
+
// the (wide) pre-measure fallback would flip-flop placement between interior and outside
|
|
505
|
+
colorbar: Boolean(color_bar) && all_color_values.length > 0 && !color_bar?.wrapper_style &&
|
|
506
|
+
(colorbar_element?.offsetWidth ?? 0) > 0 && (colorbar_element?.offsetHeight ?? 0) > 0
|
|
507
|
+
? {
|
|
508
|
+
footprint: colorbar_footprint,
|
|
509
|
+
horizontal: colorbar_is_horizontal,
|
|
510
|
+
clearance: color_bar?.axis_clearance,
|
|
511
|
+
}
|
|
512
|
+
: null,
|
|
513
|
+
})
|
|
514
|
+
)
|
|
515
|
+
const pad = $derived(decor.pad)
|
|
516
|
+
const legend_auto_outside = $derived(decor.legend_outside)
|
|
517
|
+
const legend_outside_x = $derived(decor.legend_pos.x)
|
|
518
|
+
const legend_outside_y = $derived(decor.legend_pos.y)
|
|
519
|
+
const effective_cbar_wrapper_style = $derived(
|
|
520
|
+
color_bar?.wrapper_style ?? (decor.colorbar_outside ? decor.colorbar_style : undefined),
|
|
521
|
+
)
|
|
522
|
+
|
|
442
523
|
// Reactive clip area dimensions to ensure proper responsiveness
|
|
443
524
|
let clip_area = $derived({
|
|
444
525
|
x: pad.l || 0,
|
|
@@ -686,10 +767,6 @@
|
|
|
686
767
|
)
|
|
687
768
|
|
|
688
769
|
// Filter to points within the plot bounds (handles inverted ranges like [3.5, 1.4])
|
|
689
|
-
const in_range = (val: number | null | undefined, lo: number, hi: number) =>
|
|
690
|
-
val != null && !isNaN(val) && val >= Math.min(lo, hi) &&
|
|
691
|
-
val <= Math.max(lo, hi)
|
|
692
|
-
|
|
693
770
|
// Determine which ranges to use based on series axis properties
|
|
694
771
|
const [series_x_min, series_x_max] = (data_series.x_axis ?? `x1`) === `x2`
|
|
695
772
|
? [x2_min, x2_max]
|
|
@@ -720,7 +797,10 @@
|
|
|
720
797
|
),
|
|
721
798
|
)
|
|
722
799
|
|
|
723
|
-
//
|
|
800
|
+
// Obstacle field for legend/colorbar auto-placement. Sampling only data points lets the
|
|
801
|
+
// legend land on top of a steep connecting line whose markers are sparse (e.g. y=x^2), so
|
|
802
|
+
// sample_series_obstacle_points also walks each drawn segment at a fixed pixel cadence.
|
|
803
|
+
const SEGMENT_SAMPLE_STEP = 12 // px between samples taken along a connecting line
|
|
724
804
|
let plot_points_for_placement = $derived.by(() => {
|
|
725
805
|
if (!width || !height || !filtered_series) return []
|
|
726
806
|
|
|
@@ -729,21 +809,17 @@
|
|
|
729
809
|
for (const series_data of filtered_series) {
|
|
730
810
|
if (!series_data?.filtered_data) continue
|
|
731
811
|
const use_x2_scale = series_data.x_axis === `x2`
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (isFinite(point_x_coord) && isFinite(point_y_coord)) {
|
|
744
|
-
points.push({ x: point_x_coord, y: point_y_coord })
|
|
745
|
-
}
|
|
746
|
-
}
|
|
812
|
+
const active_x_scale = use_x2_scale ? x2_scale_fn : x_scale_fn
|
|
813
|
+
const active_is_time_x = use_x2_scale ? is_time_x2 : is_time_x
|
|
814
|
+
const active_y_scale = series_data.y_axis === `y2` ? y2_scale_fn : y_scale_fn
|
|
815
|
+
const draws_line = styles.show_lines &&
|
|
816
|
+
(series_data.markers ?? DEFAULT_MARKERS).includes(`line`)
|
|
817
|
+
|
|
818
|
+
const pixel_points = series_data.filtered_data.map((point) => ({
|
|
819
|
+
x: active_is_time_x ? active_x_scale(new Date(point.x)) : active_x_scale(point.x),
|
|
820
|
+
y: active_y_scale(point.y),
|
|
821
|
+
}))
|
|
822
|
+
points.push(...sample_series_obstacle_points(pixel_points, draws_line, SEGMENT_SAMPLE_STEP))
|
|
747
823
|
}
|
|
748
824
|
return points
|
|
749
825
|
})
|
|
@@ -756,11 +832,29 @@
|
|
|
756
832
|
line_dash?: string
|
|
757
833
|
}
|
|
758
834
|
|
|
835
|
+
const fill_hover_key = (
|
|
836
|
+
source_type: `fill_region` | `error_band`,
|
|
837
|
+
source_idx: number,
|
|
838
|
+
id?: string | number,
|
|
839
|
+
is_duplicate_id = false,
|
|
840
|
+
): string => {
|
|
841
|
+
if (id == null) return `${source_type}:idx:${source_idx}`
|
|
842
|
+
if (is_duplicate_id) return `${source_type}:id:${id}:idx:${source_idx}`
|
|
843
|
+
return `${source_type}:id:${id}`
|
|
844
|
+
}
|
|
845
|
+
const has_duplicate_id = <T extends { id?: string | number }>(
|
|
846
|
+
items: readonly T[] | undefined,
|
|
847
|
+
source_idx: number,
|
|
848
|
+
id?: string | number,
|
|
849
|
+
): boolean =>
|
|
850
|
+
id != null && (items?.some((item, idx) => idx !== source_idx && item.id === id) ?? false)
|
|
851
|
+
|
|
759
852
|
// Computed fill regions: merge fill_regions and converted error_bands, resolve boundaries
|
|
760
853
|
type ComputedFill = FillRegion & {
|
|
761
854
|
idx: number
|
|
762
855
|
source_type: `fill_region` | `error_band`
|
|
763
856
|
source_idx: number
|
|
857
|
+
hover_key: string
|
|
764
858
|
path_segments: string[]
|
|
765
859
|
}
|
|
766
860
|
let computed_fills = $derived.by((): ComputedFill[] => {
|
|
@@ -774,16 +868,29 @@
|
|
|
774
868
|
region: FillRegion | null
|
|
775
869
|
source_type: `fill_region` | `error_band`
|
|
776
870
|
source_idx: number
|
|
871
|
+
hover_key: string
|
|
777
872
|
}[] = [
|
|
778
873
|
...(fill_regions ?? []).map((region, source_idx) => ({
|
|
779
874
|
region,
|
|
780
875
|
source_type: `fill_region` as const,
|
|
781
876
|
source_idx,
|
|
877
|
+
hover_key: fill_hover_key(
|
|
878
|
+
`fill_region`,
|
|
879
|
+
source_idx,
|
|
880
|
+
region.id,
|
|
881
|
+
has_duplicate_id(fill_regions, source_idx, region.id),
|
|
882
|
+
),
|
|
782
883
|
})),
|
|
783
884
|
...(error_bands ?? []).map((band, source_idx) => ({
|
|
784
885
|
region: convert_error_band_to_fill_region(band, series_with_ids),
|
|
785
886
|
source_type: `error_band` as const,
|
|
786
887
|
source_idx,
|
|
888
|
+
hover_key: fill_hover_key(
|
|
889
|
+
`error_band`,
|
|
890
|
+
source_idx,
|
|
891
|
+
band.id,
|
|
892
|
+
has_duplicate_id(error_bands, source_idx, band.id),
|
|
893
|
+
),
|
|
787
894
|
})),
|
|
788
895
|
]
|
|
789
896
|
|
|
@@ -808,8 +915,9 @@
|
|
|
808
915
|
region: FillRegion
|
|
809
916
|
source_type: `fill_region` | `error_band`
|
|
810
917
|
source_idx: number
|
|
918
|
+
hover_key: string
|
|
811
919
|
} => entry.region !== null)
|
|
812
|
-
.map(({ region, source_type, source_idx }, idx) => {
|
|
920
|
+
.map(({ region, source_type, source_idx, hover_key }, idx) => {
|
|
813
921
|
if (region.visible === false) return null
|
|
814
922
|
|
|
815
923
|
// Domain context for boundary resolution
|
|
@@ -876,7 +984,7 @@
|
|
|
876
984
|
|
|
877
985
|
if (path_segments.length === 0) return null
|
|
878
986
|
|
|
879
|
-
return { ...region, idx, source_type, source_idx, path_segments }
|
|
987
|
+
return { ...region, idx, source_type, source_idx, hover_key, path_segments }
|
|
880
988
|
})
|
|
881
989
|
.filter((fill): fill is ComputedFill => fill !== null)
|
|
882
990
|
})
|
|
@@ -1070,14 +1178,10 @@
|
|
|
1070
1178
|
const plot_width = width - pad.l - pad.r
|
|
1071
1179
|
const plot_height = height - pad.t - pad.b
|
|
1072
1180
|
|
|
1073
|
-
// Use measured size if available, otherwise estimate
|
|
1074
|
-
const legend_size = legend_element
|
|
1075
|
-
? { width: legend_element.offsetWidth, height: legend_element.offsetHeight }
|
|
1076
|
-
: { width: 120, height: 80 }
|
|
1077
|
-
|
|
1078
1181
|
const placement_config = {
|
|
1079
1182
|
plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
|
|
1080
|
-
|
|
1183
|
+
element: legend_element,
|
|
1184
|
+
element_size: { width: 120, height: 80 }, // fallback before first render
|
|
1081
1185
|
axis_clearance: legend?.axis_clearance,
|
|
1082
1186
|
exclude_rects: [],
|
|
1083
1187
|
points: plot_points_for_placement,
|
|
@@ -1093,13 +1197,12 @@
|
|
|
1093
1197
|
const plot_width = width - pad.l - pad.r
|
|
1094
1198
|
const plot_height = height - pad.t - pad.b
|
|
1095
1199
|
|
|
1096
|
-
//
|
|
1097
|
-
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
:
|
|
1101
|
-
|
|
1102
|
-
: { width: 40, height: 100 }
|
|
1200
|
+
// Fallback estimate (with room for tick labels) used before the colorbar first
|
|
1201
|
+
// renders; compute_element_placement measures the real footprint once it's laid out
|
|
1202
|
+
const is_horizontal = (color_bar.orientation ?? `horizontal`) === `horizontal`
|
|
1203
|
+
const colorbar_size = is_horizontal
|
|
1204
|
+
? { width: 220, height: 56 }
|
|
1205
|
+
: { width: 56, height: 100 }
|
|
1103
1206
|
|
|
1104
1207
|
// Build exclusion rects (avoid legend if it's placed)
|
|
1105
1208
|
const exclude_rects: Rect[] = []
|
|
@@ -1114,9 +1217,11 @@
|
|
|
1114
1217
|
|
|
1115
1218
|
return compute_element_placement({
|
|
1116
1219
|
plot_bounds: { x: pad.l, y: pad.t, width: plot_width, height: plot_height },
|
|
1220
|
+
element: colorbar_element,
|
|
1117
1221
|
element_size: colorbar_size,
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1222
|
+
// Small gap from the corner; the full-footprint measurement reserves the tick
|
|
1223
|
+
// labels, so this alone keeps the colorbar off the axes
|
|
1224
|
+
axis_clearance: color_bar?.axis_clearance ?? 15,
|
|
1120
1225
|
exclude_rects,
|
|
1121
1226
|
points: plot_points_for_placement,
|
|
1122
1227
|
})
|
|
@@ -1867,7 +1972,7 @@
|
|
|
1867
1972
|
</script>
|
|
1868
1973
|
|
|
1869
1974
|
{#snippet fill_regions_layer(fills: typeof computed_fills)}
|
|
1870
|
-
{#each fills as fill (fill.
|
|
1975
|
+
{#each fills as fill (fill.hover_key)}
|
|
1871
1976
|
{#each fill.path_segments as
|
|
1872
1977
|
path_d,
|
|
1873
1978
|
segment_idx
|
|
@@ -1880,13 +1985,13 @@
|
|
|
1880
1985
|
{clip_path_id}
|
|
1881
1986
|
{x_scale_fn}
|
|
1882
1987
|
{y_scale_fn}
|
|
1883
|
-
|
|
1884
|
-
on_click={(event) => {
|
|
1988
|
+
is_hovered={hovered_fill_key === fill.hover_key}
|
|
1989
|
+
on_click={(event: FillHandlerEvent) => {
|
|
1885
1990
|
fill.on_click?.(event)
|
|
1886
1991
|
on_fill_click?.(event)
|
|
1887
1992
|
}}
|
|
1888
|
-
on_hover={(event) => {
|
|
1889
|
-
|
|
1993
|
+
on_hover={(event: FillHandlerEvent | null) => {
|
|
1994
|
+
hovered_fill_key = event ? fill.hover_key : null
|
|
1890
1995
|
fill.on_hover?.(event)
|
|
1891
1996
|
on_fill_hover?.(event)
|
|
1892
1997
|
}}
|
|
@@ -2020,289 +2125,115 @@
|
|
|
2020
2125
|
<!-- Reference lines: below grid -->
|
|
2021
2126
|
{@render ref_lines_layer(ref_lines_by_z.below_grid)}
|
|
2022
2127
|
|
|
2023
|
-
<
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
{/if}
|
|
2041
|
-
<line y1="0" y2={inside ? -5 : 5} stroke="var(--border-color, gray)" />
|
|
2042
|
-
|
|
2043
|
-
{#if tick >= Math.min(x_min, x_max) && tick <= Math.max(x_min, x_max)}
|
|
2044
|
-
{@const base_y = inside ? -8 : 20}
|
|
2045
|
-
{@const shift = final_x_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
|
|
2046
|
-
{@const x = shift.x ?? 0}
|
|
2047
|
-
{@const y = base_y + (shift.y ?? 0)}
|
|
2048
|
-
{@const custom_label = get_tick_label(tick, final_x_axis.ticks)}
|
|
2049
|
-
{@const dominant_baseline = inside ? `auto` : `hanging`}
|
|
2050
|
-
<text
|
|
2051
|
-
{x}
|
|
2052
|
-
{y}
|
|
2053
|
-
dominant-baseline={dominant_baseline}
|
|
2054
|
-
fill={final_x_axis.color}
|
|
2055
|
-
>
|
|
2056
|
-
{custom_label ?? format_value(tick, final_x_axis.format ?? ``)}
|
|
2057
|
-
</text>
|
|
2058
|
-
{/if}
|
|
2059
|
-
</g>
|
|
2060
|
-
{/if}
|
|
2061
|
-
{/if}
|
|
2062
|
-
{/each}
|
|
2063
|
-
{/if}
|
|
2128
|
+
<PlotAxis
|
|
2129
|
+
side="x"
|
|
2130
|
+
ticks={x_tick_values}
|
|
2131
|
+
place={(tick) => (is_time_x ? x_scale_fn(new Date(tick)) : x_scale_fn(tick))}
|
|
2132
|
+
axis={final_x_axis}
|
|
2133
|
+
{pad}
|
|
2134
|
+
{width}
|
|
2135
|
+
{height}
|
|
2136
|
+
show_grid={final_display.x_grid}
|
|
2137
|
+
show_baseline={false}
|
|
2138
|
+
domain={[x_min, x_max]}
|
|
2139
|
+
tick_label={(tick) => get_tick_label(tick, final_x_axis.ticks)}
|
|
2140
|
+
label_x={width / 2 + (final_x_axis.label_shift?.x ?? 0)}
|
|
2141
|
+
label_y={height - pad.b - (final_x_axis.label_shift?.y ?? -40)}
|
|
2142
|
+
axis_loading={axis_loading === `x`}
|
|
2143
|
+
on_axis_change={(key) => handle_axis_change(`x`, key)}
|
|
2144
|
+
/>
|
|
2064
2145
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
{/if}
|
|
2146
|
+
<!-- Current frame indicator -->
|
|
2147
|
+
{#if current_x_value != null}
|
|
2148
|
+
{@const current_pos_raw = is_time_x
|
|
2149
|
+
? x_scale_fn(new Date(current_x_value))
|
|
2150
|
+
: x_scale_fn(current_x_value)}
|
|
2151
|
+
{#if isFinite(current_pos_raw)}
|
|
2152
|
+
{@const current_pos = current_pos_raw}
|
|
2153
|
+
{#if current_pos >= pad.l && current_pos <= width - pad.r}
|
|
2154
|
+
{@const active_tick_height = 7}
|
|
2155
|
+
<rect
|
|
2156
|
+
x={current_pos - 1.5}
|
|
2157
|
+
y={height - pad.b - active_tick_height / 2}
|
|
2158
|
+
width="3"
|
|
2159
|
+
height={active_tick_height}
|
|
2160
|
+
fill="var(--scatter-current-frame-color, #ff6b35)"
|
|
2161
|
+
stroke="white"
|
|
2162
|
+
stroke-width="1"
|
|
2163
|
+
class="current-frame-indicator"
|
|
2164
|
+
/>
|
|
2085
2165
|
{/if}
|
|
2086
2166
|
{/if}
|
|
2167
|
+
{/if}
|
|
2087
2168
|
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
{
|
|
2102
|
-
</g>
|
|
2103
|
-
|
|
2104
|
-
<g class="y-axis">
|
|
2105
|
-
{#if width > 0 && height > 0}
|
|
2106
|
-
{#each y_tick_values as tick, idx (tick)}
|
|
2107
|
-
{@const tick_pos_raw = y_scale_fn(tick)}
|
|
2108
|
-
{#if isFinite(tick_pos_raw)}
|
|
2109
|
-
// Check if tick position is finite
|
|
2110
|
-
{@const tick_pos = tick_pos_raw}
|
|
2111
|
-
{#if tick_pos >= pad.t && tick_pos <= height - pad.b}
|
|
2112
|
-
{@const inside = final_y_axis.tick?.label?.inside ?? false}
|
|
2113
|
-
<g class="tick" transform="translate({pad.l}, {tick_pos})">
|
|
2114
|
-
{#if final_display.y_grid}
|
|
2115
|
-
<line
|
|
2116
|
-
x1="0"
|
|
2117
|
-
x2={width - pad.l - pad.r}
|
|
2118
|
-
{...DEFAULT_GRID_STYLE}
|
|
2119
|
-
{...final_y_axis.grid_style}
|
|
2120
|
-
/>
|
|
2121
|
-
{/if}
|
|
2122
|
-
<line
|
|
2123
|
-
x1={inside ? 0 : -5}
|
|
2124
|
-
x2={inside ? 5 : 0}
|
|
2125
|
-
stroke="var(--border-color, gray)"
|
|
2126
|
-
/>
|
|
2127
|
-
|
|
2128
|
-
{#if tick >= Math.min(y_min, y_max) && tick <= Math.max(y_min, y_max)}
|
|
2129
|
-
{@const base_x = inside ? 8 : -8}
|
|
2130
|
-
{@const shift = final_y_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
|
|
2131
|
-
{@const x = base_x + (shift.x ?? 0)}
|
|
2132
|
-
{@const y = shift.y ?? 0}
|
|
2133
|
-
{@const custom_label = get_tick_label(tick, final_y_axis.ticks)}
|
|
2134
|
-
{@const text_anchor = inside ? `start` : `end`}
|
|
2135
|
-
<text {x} {y} text-anchor={text_anchor} fill={final_y_axis.color}>
|
|
2136
|
-
{custom_label ?? format_value(tick, final_y_axis.format ?? ``)}
|
|
2137
|
-
{#if final_y_axis.unit && idx === 0}
|
|
2138
|
-
‌ {final_y_axis.unit}
|
|
2139
|
-
{/if}
|
|
2140
|
-
</text>
|
|
2141
|
-
{/if}
|
|
2142
|
-
</g>
|
|
2143
|
-
{/if}
|
|
2144
|
-
{/if}
|
|
2145
|
-
{/each}
|
|
2146
|
-
{/if}
|
|
2147
|
-
|
|
2148
|
-
{#if height > 0 && (final_y_axis.label || final_y_axis.options?.length)}
|
|
2149
|
-
{@const { label_shift, label = ``, options, selected_key, color, tick } =
|
|
2150
|
-
final_y_axis}
|
|
2151
|
-
{@const y_inside = tick?.label?.inside ?? false}
|
|
2152
|
-
{@const y_label_x = Math.max(
|
|
2169
|
+
<PlotAxis
|
|
2170
|
+
side="y"
|
|
2171
|
+
ticks={y_tick_values}
|
|
2172
|
+
place={y_scale_fn}
|
|
2173
|
+
axis={final_y_axis}
|
|
2174
|
+
{pad}
|
|
2175
|
+
{width}
|
|
2176
|
+
{height}
|
|
2177
|
+
show_grid={final_display.y_grid}
|
|
2178
|
+
show_baseline={false}
|
|
2179
|
+
domain={[y_min, y_max]}
|
|
2180
|
+
unit_on_first_tick
|
|
2181
|
+
tick_label={(tick) => get_tick_label(tick, final_y_axis.ticks)}
|
|
2182
|
+
label_x={Math.max(
|
|
2153
2183
|
12,
|
|
2154
|
-
pad.l - (
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
{label}
|
|
2162
|
-
{options}
|
|
2163
|
-
{selected_key}
|
|
2164
|
-
loading={axis_loading === `y`}
|
|
2165
|
-
axis_type="y"
|
|
2166
|
-
{color}
|
|
2167
|
-
on_select={(key) => handle_axis_change(`y`, key)}
|
|
2168
|
-
/>
|
|
2169
|
-
{/if}
|
|
2170
|
-
</g>
|
|
2184
|
+
pad.l - (final_y_axis.tick?.label?.inside ? 0 : tick_label_widths.y_max) -
|
|
2185
|
+
LABEL_GAP_DEFAULT,
|
|
2186
|
+
) + (final_y_axis.label_shift?.x ?? 0)}
|
|
2187
|
+
label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y_axis.label_shift?.y ?? 0)}
|
|
2188
|
+
axis_loading={axis_loading === `y`}
|
|
2189
|
+
on_axis_change={(key) => handle_axis_change(`y`, key)}
|
|
2190
|
+
/>
|
|
2171
2191
|
|
|
2172
2192
|
<!-- Y2-axis (Right) -->
|
|
2173
2193
|
{#if y2_points.length > 0}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
/>
|
|
2197
|
-
|
|
2198
|
-
{#if tick >= Math.min(y2_min, y2_max) && tick <= Math.max(y2_min, y2_max)}
|
|
2199
|
-
{@const base_x = inside ? -8 : 8}
|
|
2200
|
-
{@const shift = final_y2_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
|
|
2201
|
-
{@const x = base_x + (shift.x ?? 0)}
|
|
2202
|
-
{@const y = shift.y ?? 0}
|
|
2203
|
-
{@const custom_label = get_tick_label(tick, final_y2_axis.ticks)}
|
|
2204
|
-
{@const text_anchor = inside ? `end` : `start`}
|
|
2205
|
-
<text {x} {y} text-anchor={text_anchor} fill={final_y2_axis.color}>
|
|
2206
|
-
{custom_label ?? format_value(tick, final_y2_axis.format ?? ``)}
|
|
2207
|
-
{#if final_y2_axis.unit && idx === 0}
|
|
2208
|
-
‌ {final_y2_axis.unit}
|
|
2209
|
-
{/if}
|
|
2210
|
-
</text>
|
|
2211
|
-
{/if}
|
|
2212
|
-
</g>
|
|
2213
|
-
{/if}
|
|
2214
|
-
{/if}
|
|
2215
|
-
{/each}
|
|
2216
|
-
{/if}
|
|
2217
|
-
|
|
2218
|
-
{#if height > 0 && (final_y2_axis.label || final_y2_axis.options?.length)}
|
|
2219
|
-
{@const { label_shift, label = ``, options, selected_key, color, tick } =
|
|
2220
|
-
final_y2_axis}
|
|
2221
|
-
{@const inside = tick?.label?.inside ?? false}
|
|
2222
|
-
{@const tick_shift = inside ? 0 : (tick?.label?.shift?.x ?? 0) + 8}
|
|
2223
|
-
{@const tick_width_contribution = inside ? 0 : tick_label_widths.y2_max}
|
|
2224
|
-
<AxisLabel
|
|
2225
|
-
x={width - pad.r + tick_shift + tick_width_contribution +
|
|
2226
|
-
LABEL_GAP_DEFAULT + (label_shift?.x ?? 0)}
|
|
2227
|
-
y={pad.t + (height - pad.t - pad.b) / 2 + (label_shift?.y ?? 0)}
|
|
2228
|
-
rotate
|
|
2229
|
-
{label}
|
|
2230
|
-
{options}
|
|
2231
|
-
{selected_key}
|
|
2232
|
-
loading={axis_loading === `y2`}
|
|
2233
|
-
axis_type="y2"
|
|
2234
|
-
{color}
|
|
2235
|
-
on_select={(key) => handle_axis_change(`y2`, key)}
|
|
2236
|
-
/>
|
|
2237
|
-
{/if}
|
|
2238
|
-
</g>
|
|
2194
|
+
{@const y2_inside = final_y2_axis.tick?.label?.inside ?? false}
|
|
2195
|
+
{@const y2_tick_shift = y2_inside ? 0 : (final_y2_axis.tick?.label?.shift?.x ?? 0) + 8}
|
|
2196
|
+
{@const y2_tick_width = y2_inside ? 0 : tick_label_widths.y2_max}
|
|
2197
|
+
<PlotAxis
|
|
2198
|
+
side="y2"
|
|
2199
|
+
ticks={y2_tick_values}
|
|
2200
|
+
place={y2_scale_fn}
|
|
2201
|
+
axis={final_y2_axis}
|
|
2202
|
+
{pad}
|
|
2203
|
+
{width}
|
|
2204
|
+
{height}
|
|
2205
|
+
show_grid={final_display.y2_grid}
|
|
2206
|
+
show_baseline={false}
|
|
2207
|
+
domain={[y2_min, y2_max]}
|
|
2208
|
+
unit_on_first_tick
|
|
2209
|
+
tick_label={(tick) => get_tick_label(tick, final_y2_axis.ticks)}
|
|
2210
|
+
label_x={width - pad.r + y2_tick_shift + y2_tick_width + LABEL_GAP_DEFAULT +
|
|
2211
|
+
(final_y2_axis.label_shift?.x ?? 0)}
|
|
2212
|
+
label_y={pad.t + (height - pad.t - pad.b) / 2 + (final_y2_axis.label_shift?.y ?? 0)}
|
|
2213
|
+
axis_loading={axis_loading === `y2`}
|
|
2214
|
+
on_axis_change={(key) => handle_axis_change(`y2`, key)}
|
|
2215
|
+
/>
|
|
2239
2216
|
{/if}
|
|
2240
2217
|
|
|
2241
2218
|
<!-- X2-axis (Top) -->
|
|
2242
2219
|
{#if x2_points.length > 0}
|
|
2243
|
-
<
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
/>
|
|
2261
|
-
{/if}
|
|
2262
|
-
<line
|
|
2263
|
-
y1="0"
|
|
2264
|
-
y2={inside ? 5 : -5}
|
|
2265
|
-
stroke={final_x2_axis.color || `var(--border-color, gray)`}
|
|
2266
|
-
/>
|
|
2267
|
-
|
|
2268
|
-
{#if tick >= Math.min(x2_min, x2_max) && tick <= Math.max(x2_min, x2_max)}
|
|
2269
|
-
{@const base_y = inside ? 8 : -20}
|
|
2270
|
-
{@const shift = final_x2_axis.tick?.label?.shift ?? { x: 0, y: 0 }}
|
|
2271
|
-
{@const x = shift.x ?? 0}
|
|
2272
|
-
{@const y = base_y + (shift.y ?? 0)}
|
|
2273
|
-
{@const custom_label = get_tick_label(tick, final_x2_axis.ticks)}
|
|
2274
|
-
{@const dominant_baseline = inside ? `hanging` : `auto`}
|
|
2275
|
-
<text
|
|
2276
|
-
{x}
|
|
2277
|
-
{y}
|
|
2278
|
-
dominant-baseline={dominant_baseline}
|
|
2279
|
-
fill={final_x2_axis.color}
|
|
2280
|
-
>
|
|
2281
|
-
{custom_label ?? format_value(tick, final_x2_axis.format ?? ``)}
|
|
2282
|
-
</text>
|
|
2283
|
-
{/if}
|
|
2284
|
-
</g>
|
|
2285
|
-
{/if}
|
|
2286
|
-
{/if}
|
|
2287
|
-
{/each}
|
|
2288
|
-
{/if}
|
|
2289
|
-
|
|
2290
|
-
{#if final_x2_axis.label || final_x2_axis.options?.length}
|
|
2291
|
-
{@const { label_shift, label = ``, options, selected_key, color } =
|
|
2292
|
-
final_x2_axis}
|
|
2293
|
-
<AxisLabel
|
|
2294
|
-
x={width / 2 + (label_shift?.x ?? 0)}
|
|
2295
|
-
y={Math.max(12, pad.t - (label_shift?.y ?? 40))}
|
|
2296
|
-
{label}
|
|
2297
|
-
{options}
|
|
2298
|
-
{selected_key}
|
|
2299
|
-
loading={axis_loading === `x2`}
|
|
2300
|
-
axis_type="x2"
|
|
2301
|
-
{color}
|
|
2302
|
-
on_select={(key) => handle_axis_change(`x2`, key)}
|
|
2303
|
-
/>
|
|
2304
|
-
{/if}
|
|
2305
|
-
</g>
|
|
2220
|
+
<PlotAxis
|
|
2221
|
+
side="x2"
|
|
2222
|
+
ticks={x2_tick_values}
|
|
2223
|
+
place={(tick) => (is_time_x2 ? x2_scale_fn(new Date(tick)) : x2_scale_fn(tick))}
|
|
2224
|
+
axis={final_x2_axis}
|
|
2225
|
+
{pad}
|
|
2226
|
+
{width}
|
|
2227
|
+
{height}
|
|
2228
|
+
show_grid={final_display.x2_grid}
|
|
2229
|
+
show_baseline={false}
|
|
2230
|
+
domain={[x2_min, x2_max]}
|
|
2231
|
+
tick_label={(tick) => get_tick_label(tick, final_x2_axis.ticks)}
|
|
2232
|
+
label_x={width / 2 + (final_x2_axis.label_shift?.x ?? 0)}
|
|
2233
|
+
label_y={Math.max(12, pad.t - (final_x2_axis.label_shift?.y ?? 40))}
|
|
2234
|
+
axis_loading={axis_loading === `x2`}
|
|
2235
|
+
on_axis_change={(key) => handle_axis_change(`x2`, key)}
|
|
2236
|
+
/>
|
|
2306
2237
|
{/if}
|
|
2307
2238
|
|
|
2308
2239
|
<!-- Tooltip rendered inside overlay (moved outside SVG for stacking above colorbar) -->
|
|
@@ -2417,7 +2348,12 @@
|
|
|
2417
2348
|
}
|
|
2418
2349
|
{@const label_id = `${point.series_idx}-${point.point_idx}`}
|
|
2419
2350
|
{@const calculated_label_pos = label_positions[label_id]}
|
|
2420
|
-
{@const
|
|
2351
|
+
{@const point_label = point.point_label ?? {}}
|
|
2352
|
+
{@const label_style = point_label.auto_placement &&
|
|
2353
|
+
actual_label_config.max_neighbors &&
|
|
2354
|
+
!calculated_label_pos
|
|
2355
|
+
? {}
|
|
2356
|
+
: point_label}
|
|
2421
2357
|
{@const final_label = calculated_label_pos
|
|
2422
2358
|
? {
|
|
2423
2359
|
...label_style,
|
|
@@ -2608,7 +2544,7 @@
|
|
|
2608
2544
|
has_x2_points={x2_points.length > 0}
|
|
2609
2545
|
has_y2_points={y2_points.length > 0}
|
|
2610
2546
|
children={controls_extra}
|
|
2611
|
-
on_touch={(key) => touched.add(key)}
|
|
2547
|
+
on_touch={(key: string) => touched.add(key)}
|
|
2612
2548
|
/>
|
|
2613
2549
|
{/if}
|
|
2614
2550
|
|
|
@@ -2627,12 +2563,10 @@
|
|
|
2627
2563
|
class="colorbar-wrapper"
|
|
2628
2564
|
role="img"
|
|
2629
2565
|
aria-label="Color scale legend"
|
|
2630
|
-
style={
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
top: ${tweened_colorbar_coords.current.y}px;
|
|
2634
|
-
pointer-events: auto;
|
|
2635
|
-
`}
|
|
2566
|
+
style={`${
|
|
2567
|
+
// explicit wrapper_style or auto-outside places the colorbar; else auto-placement coords
|
|
2568
|
+
effective_cbar_wrapper_style ??
|
|
2569
|
+
`position: absolute; left: ${tweened_colorbar_coords.current.x}px; top: ${tweened_colorbar_coords.current.y}px`}; pointer-events: auto;`}
|
|
2636
2570
|
>
|
|
2637
2571
|
<ColorBar
|
|
2638
2572
|
tick_labels={4}
|
|
@@ -2641,9 +2575,9 @@
|
|
|
2641
2575
|
color_scale_domain={color_domain}
|
|
2642
2576
|
scale_type={typeof color_scale === `string` ? undefined : color_scale.type}
|
|
2643
2577
|
range={color_domain?.every((val) => val != null) ? color_domain : undefined}
|
|
2644
|
-
|
|
2645
|
-
bar_style="width: 220px; height: 20px; {color_bar?.style ?? ``}"
|
|
2578
|
+
bar_style="width: 220px; height: 16px; {color_bar?.style ?? ``}"
|
|
2646
2579
|
{...color_bar}
|
|
2580
|
+
wrapper_style={effective_cbar_wrapper_style ? `height: 100%; width: 100%;` : ``}
|
|
2647
2581
|
/>
|
|
2648
2582
|
</div>
|
|
2649
2583
|
{/if}
|
|
@@ -2656,11 +2590,15 @@
|
|
|
2656
2590
|
{@const default_y = pad.t + 10}
|
|
2657
2591
|
{@const current_x = legend_is_dragging && legend_manual_position
|
|
2658
2592
|
? legend_manual_position.x
|
|
2593
|
+
: legend_auto_outside
|
|
2594
|
+
? legend_outside_x
|
|
2659
2595
|
: legend_placement
|
|
2660
2596
|
? tweened_legend_coords.current.x
|
|
2661
2597
|
: default_x}
|
|
2662
2598
|
{@const current_y = legend_is_dragging && legend_manual_position
|
|
2663
2599
|
? legend_manual_position.y
|
|
2600
|
+
: legend_auto_outside
|
|
2601
|
+
? legend_outside_y
|
|
2664
2602
|
: legend_placement
|
|
2665
2603
|
? tweened_legend_coords.current.y
|
|
2666
2604
|
: default_y}
|
|
@@ -2671,7 +2609,7 @@
|
|
|
2671
2609
|
on_drag={handle_legend_drag}
|
|
2672
2610
|
on_drag_end={() => (legend_is_dragging = false)}
|
|
2673
2611
|
on_hover_change={legend_hover.set_locked}
|
|
2674
|
-
on_item_hover={(series_idx) =>
|
|
2612
|
+
on_item_hover={(series_idx: number | null) =>
|
|
2675
2613
|
(hovered_legend_series_idx = series_idx != null && series_idx >= 0
|
|
2676
2614
|
? series_idx
|
|
2677
2615
|
: null)}
|
|
@@ -2679,24 +2617,24 @@
|
|
|
2679
2617
|
draggable={legend?.draggable ?? true}
|
|
2680
2618
|
{...legend}
|
|
2681
2619
|
on_toggle={legend?.on_toggle ??
|
|
2682
|
-
((series_idx) => {
|
|
2620
|
+
((series_idx: number) => {
|
|
2683
2621
|
series = toggle_series_visibility(series, series_idx)
|
|
2684
2622
|
})}
|
|
2685
2623
|
on_double_click={legend?.on_double_click ??
|
|
2686
|
-
((double_clicked_idx) => {
|
|
2624
|
+
((double_clicked_idx: number) => {
|
|
2687
2625
|
const result = handle_legend_double_click(
|
|
2688
2626
|
series,
|
|
2689
2627
|
double_clicked_idx,
|
|
2690
|
-
|
|
2628
|
+
prev_series_visibility,
|
|
2691
2629
|
)
|
|
2692
2630
|
series = result.series
|
|
2693
|
-
|
|
2631
|
+
prev_series_visibility = result.prev_visibility
|
|
2694
2632
|
})}
|
|
2695
2633
|
on_group_toggle={legend?.on_group_toggle ??
|
|
2696
|
-
((_group_name, series_indices) => {
|
|
2634
|
+
((_group_name: string, series_indices: number[]) => {
|
|
2697
2635
|
series = toggle_group_visibility(series, series_indices)
|
|
2698
2636
|
})}
|
|
2699
|
-
on_fill_toggle={(source_type
|
|
2637
|
+
on_fill_toggle={(source_type: `fill_region` | `error_band`, source_idx: number) => {
|
|
2700
2638
|
// Only fill_regions can be toggled (error_bands are not bindable)
|
|
2701
2639
|
if (source_type === `fill_region`) {
|
|
2702
2640
|
fill_regions = fill_regions.map((region, idx) =>
|
|
@@ -2706,11 +2644,14 @@
|
|
|
2706
2644
|
)
|
|
2707
2645
|
}
|
|
2708
2646
|
}}
|
|
2709
|
-
on_fill_double_click={(
|
|
2647
|
+
on_fill_double_click={(
|
|
2648
|
+
source_type: `fill_region` | `error_band`,
|
|
2649
|
+
source_idx: number,
|
|
2650
|
+
) => {
|
|
2710
2651
|
// Only fill_regions can be toggled (error_bands are not bindable)
|
|
2711
2652
|
if (source_type !== `fill_region`) return
|
|
2712
2653
|
// Toggle: if only this fill is visible, show all; otherwise show only this one
|
|
2713
|
-
const visible_count = fill_regions.filter((
|
|
2654
|
+
const visible_count = fill_regions.filter((region) => region.visible !== false).length
|
|
2714
2655
|
const this_visible = fill_regions[source_idx]?.visible !== false
|
|
2715
2656
|
if (visible_count === 1 && this_visible) {
|
|
2716
2657
|
// Show all fills
|
|
@@ -2770,6 +2711,13 @@
|
|
|
2770
2711
|
padding-top: var(--plot-fullscreen-padding-top, 2em);
|
|
2771
2712
|
box-sizing: border-box;
|
|
2772
2713
|
}
|
|
2714
|
+
/* Center the colorbar within its wrapper when shorter than it (e.g. capped by --cbar-max-height
|
|
2715
|
+
in fullscreen). Users can override via wrapper_style (inline wins). */
|
|
2716
|
+
.colorbar-wrapper {
|
|
2717
|
+
display: flex;
|
|
2718
|
+
align-items: center;
|
|
2719
|
+
justify-content: center;
|
|
2720
|
+
}
|
|
2773
2721
|
.header-controls {
|
|
2774
2722
|
position: absolute;
|
|
2775
2723
|
top: var(--ctrl-btn-top, 5pt);
|
|
@@ -2805,21 +2753,6 @@
|
|
|
2805
2753
|
font-weight: var(--scatter-font-weight);
|
|
2806
2754
|
font-size: var(--scatter-font-size);
|
|
2807
2755
|
}
|
|
2808
|
-
line {
|
|
2809
|
-
stroke: var(--scatter-grid-stroke, gray);
|
|
2810
|
-
stroke-dasharray: var(--scatter-grid-dash, 4);
|
|
2811
|
-
stroke-width: var(--scatter-grid-width, 0.4);
|
|
2812
|
-
}
|
|
2813
|
-
g:is(.x-axis, .x2-axis) text {
|
|
2814
|
-
text-anchor: middle;
|
|
2815
|
-
dominant-baseline: top;
|
|
2816
|
-
}
|
|
2817
|
-
g:is(.y-axis, .y2-axis) text {
|
|
2818
|
-
dominant-baseline: central;
|
|
2819
|
-
}
|
|
2820
|
-
g:is(.x-axis, .x2-axis, .y-axis, .y2-axis) .tick text {
|
|
2821
|
-
font-size: var(--tick-font-size, 0.8em); /* shrink tick labels */
|
|
2822
|
-
}
|
|
2823
2756
|
.scatter :global(.axis-label) {
|
|
2824
2757
|
text-align: center;
|
|
2825
2758
|
width: 100%;
|