@fundar/data-chart-telling 0.0.24 → 0.0.26

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.
Files changed (170) hide show
  1. package/dist/charts/bar/Chart.svelte +2 -0
  2. package/dist/charts/heatmap/Chart.svelte +3 -1
  3. package/dist/charts/heatmap/Chart.svelte.d.ts +1 -2
  4. package/dist/charts/line/Chart.svelte +2 -0
  5. package/dist/charts/pyramid/Chart.svelte +3 -1
  6. package/dist/charts/pyramid/Chart.svelte.d.ts +1 -2
  7. package/dist/charts/scatter/Chart.svelte +95 -0
  8. package/dist/charts/scatter/Chart.svelte.d.ts +28 -0
  9. package/dist/configuration/config.svelte.js +13 -1
  10. package/dist/configuration/themes/index.d.ts +76 -0
  11. package/dist/index.d.ts +20 -14
  12. package/dist/index.js +9 -3
  13. package/dist/layout/coordinates/CoordinatesLayout.svelte +95 -0
  14. package/dist/layout/{plot/RuleLayout.svelte.d.ts → coordinates/CoordinatesLayout.svelte.d.ts} +13 -16
  15. package/dist/layout/coordinates/margins.d.ts +16 -0
  16. package/dist/layout/coordinates/margins.js +11 -0
  17. package/dist/{plots/geo → layout/coordinates}/projections/argentina.d.ts +1 -1
  18. package/dist/layout/{geo → coordinates}/resolveProjection.d.ts +1 -1
  19. package/dist/layout/coordinates/resolveProjection.js +24 -0
  20. package/dist/{plots/geo → layout/coordinates}/zoom.svelte.d.ts +1 -1
  21. package/dist/layout/geometry/GeometryLayout.svelte +231 -0
  22. package/dist/layout/geometry/GeometryLayout.svelte.d.ts +62 -0
  23. package/dist/layout/plot/BasePlotLayout.svelte +142 -155
  24. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +61 -51
  25. package/dist/layout/plot/margins.d.ts +0 -16
  26. package/dist/layout/plot/margins.js +0 -11
  27. package/dist/layout/{plot/AxisLayout.svelte → scales/ScalesLayout.svelte} +23 -16
  28. package/dist/layout/{plot/AxisLayout.svelte.d.ts → scales/ScalesLayout.svelte.d.ts} +4 -5
  29. package/dist/layout/scales/buildScale.d.ts +8 -0
  30. package/dist/layout/scales/buildScale.js +43 -0
  31. package/dist/layout/styles/StylesLayout.svelte +75 -0
  32. package/dist/layout/styles/StylesLayout.svelte.d.ts +15 -0
  33. package/dist/{plots/geo → layout/styles}/TileLayer.svelte +2 -2
  34. package/dist/{plots/geo → layout/styles}/TileLayer.svelte.d.ts +1 -1
  35. package/dist/layout/tooltip/Tooltip.svelte +16 -20
  36. package/dist/layout/tooltip/Tooltip.svelte.d.ts +5 -0
  37. package/dist/layout/tooltip/TooltipLayout.svelte +7 -1
  38. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +4 -0
  39. package/dist/layout/tooltip/controller.svelte.d.ts +4 -5
  40. package/dist/layout/tooltip/controller.svelte.js +28 -16
  41. package/dist/layout/tooltip/resolveTooltipPlacement.d.ts +24 -0
  42. package/dist/layout/tooltip/resolveTooltipPlacement.js +49 -0
  43. package/dist/layout/tooltip/utils.d.ts +27 -19
  44. package/dist/layout/tooltip/utils.js +16 -15
  45. package/dist/markers/BarMarker.svelte +92 -0
  46. package/dist/markers/BarMarker.svelte.d.ts +7 -0
  47. package/dist/markers/CellMarker.svelte +21 -0
  48. package/dist/markers/CellMarker.svelte.d.ts +7 -0
  49. package/dist/markers/ContourMarker.svelte +28 -0
  50. package/dist/markers/ContourMarker.svelte.d.ts +7 -0
  51. package/dist/markers/DeltaMarker.svelte +32 -39
  52. package/dist/markers/DeltaMarker.svelte.d.ts +4 -5
  53. package/dist/markers/DotMarker.svelte +35 -6
  54. package/dist/markers/DotMarker.svelte.d.ts +11 -2
  55. package/dist/markers/GeoFeaturesMarker.svelte +19 -0
  56. package/dist/markers/GeoFeaturesMarker.svelte.d.ts +7 -0
  57. package/dist/markers/GeoHoverMarker.svelte +25 -0
  58. package/dist/markers/GeoHoverMarker.svelte.d.ts +9 -0
  59. package/dist/markers/GraticuleMarker.svelte +18 -0
  60. package/dist/markers/GraticuleMarker.svelte.d.ts +7 -0
  61. package/dist/markers/HoverMarker.svelte +32 -33
  62. package/dist/markers/HoverMarker.svelte.d.ts +15 -9
  63. package/dist/markers/LineMarker.svelte +19 -0
  64. package/dist/markers/LineMarker.svelte.d.ts +7 -0
  65. package/dist/markers/LinkMarker.svelte +42 -0
  66. package/dist/markers/LinkMarker.svelte.d.ts +7 -0
  67. package/dist/markers/RuleMarker.svelte +24 -0
  68. package/dist/markers/RuleMarker.svelte.d.ts +10 -0
  69. package/dist/markers/SphereMarker.svelte +15 -0
  70. package/dist/markers/SphereMarker.svelte.d.ts +7 -0
  71. package/dist/markers/TextMarker.svelte +1 -1
  72. package/dist/markers/TextMarker.svelte.d.ts +1 -1
  73. package/dist/markers/VectorMarker.svelte +20 -0
  74. package/dist/markers/VectorMarker.svelte.d.ts +7 -0
  75. package/dist/markers/inset/InsetMarker.svelte +38 -45
  76. package/dist/markers/inset/InsetMarker.svelte.d.ts +6 -2
  77. package/dist/plots/bar/Plot.svelte +54 -84
  78. package/dist/plots/bar/Plot.svelte.d.ts +4 -5
  79. package/dist/plots/bar/ValueLabels.svelte +1 -1
  80. package/dist/plots/bar/ValueLabels.svelte.d.ts +1 -1
  81. package/dist/plots/bar/buildBarMarkers.d.ts +30 -0
  82. package/dist/plots/bar/buildBarMarkers.js +100 -0
  83. package/dist/plots/bar/layout.svelte.d.ts +1 -2
  84. package/dist/plots/bar/layout.svelte.js +1 -1
  85. package/dist/plots/geo/Plot.svelte +113 -387
  86. package/dist/plots/geo/Plot.svelte.d.ts +5 -4
  87. package/dist/plots/geo/buildGeoMarkers.d.ts +33 -0
  88. package/dist/plots/geo/buildGeoMarkers.js +47 -0
  89. package/dist/plots/heatmap/Plot.svelte +24 -49
  90. package/dist/plots/heatmap/Plot.svelte.d.ts +4 -5
  91. package/dist/plots/heatmap/ValueLabels.svelte +2 -2
  92. package/dist/plots/heatmap/ValueLabels.svelte.d.ts +2 -2
  93. package/dist/plots/heatmap/buildCellMarkers.d.ts +21 -0
  94. package/dist/plots/heatmap/buildCellMarkers.js +25 -0
  95. package/dist/plots/line/Plot.svelte +68 -141
  96. package/dist/plots/line/Plot.svelte.d.ts +4 -4
  97. package/dist/plots/line/ValueLabels.svelte +94 -49
  98. package/dist/plots/line/ValueLabels.svelte.d.ts +6 -1
  99. package/dist/plots/line/buildLineMarkers.d.ts +15 -0
  100. package/dist/plots/line/buildLineMarkers.js +57 -0
  101. package/dist/plots/pyramid/Plot.svelte +41 -117
  102. package/dist/plots/pyramid/Plot.svelte.d.ts +4 -5
  103. package/dist/plots/pyramid/ValueLabels.svelte +1 -1
  104. package/dist/plots/pyramid/ValueLabels.svelte.d.ts +1 -1
  105. package/dist/plots/pyramid/buildPyramidBarMarkers.d.ts +19 -0
  106. package/dist/plots/pyramid/buildPyramidBarMarkers.js +32 -0
  107. package/dist/plots/scatter/Plot.svelte +206 -0
  108. package/dist/plots/{bar/BarSegments.svelte.d.ts → scatter/Plot.svelte.d.ts} +9 -20
  109. package/dist/plots/scatter/ValueLabels.svelte +106 -0
  110. package/dist/{layout/plot/GridLayout.svelte.d.ts → plots/scatter/ValueLabels.svelte.d.ts} +11 -5
  111. package/dist/plots/scatter/buildScatterMarkers.d.ts +11 -0
  112. package/dist/plots/scatter/buildScatterMarkers.js +37 -0
  113. package/dist/plots/scatter/resolveRadiusPaddedDomain.d.ts +10 -0
  114. package/dist/plots/scatter/resolveRadiusPaddedDomain.js +23 -0
  115. package/dist/plots/utils/declutter.d.ts +82 -33
  116. package/dist/plots/utils/declutter.js +182 -40
  117. package/dist/plots/utils/geoAccessors.d.ts +1 -1
  118. package/dist/plots/utils/geoAccessors.js +1 -1
  119. package/dist/plots/utils/segments.d.ts +17 -10
  120. package/dist/plots/utils/segments.js +26 -16
  121. package/dist/types/charts/common.d.ts +1 -1
  122. package/dist/types/charts/props.d.ts +5 -6
  123. package/dist/types/configuration/styling.d.ts +24 -2
  124. package/dist/types/{plots/scales/geo.d.ts → layout/coordinates.d.ts} +33 -1
  125. package/dist/types/layout/geometry.d.ts +17 -0
  126. package/dist/types/{plots/axis.d.ts → layout/scales.d.ts} +11 -0
  127. package/dist/types/layout/styles.d.ts +126 -0
  128. package/dist/types/layout/tooltip.d.ts +20 -1
  129. package/dist/types/markers/all.d.ts +11 -0
  130. package/dist/types/markers/base.d.ts +45 -0
  131. package/dist/types/markers/base.js +1 -0
  132. package/dist/types/markers/common.d.ts +64 -16
  133. package/dist/types/markers/geo.d.ts +75 -100
  134. package/dist/types/markers/line.d.ts +34 -0
  135. package/dist/types/markers/line.js +1 -0
  136. package/dist/types/markers/props.d.ts +1 -1
  137. package/dist/types/plots/data/common.d.ts +1 -1
  138. package/dist/types/plots/data/scatter.d.ts +13 -0
  139. package/dist/types/plots/data/scatter.js +1 -0
  140. package/dist/types/plots/props.d.ts +46 -74
  141. package/dist/types/plots/segments/common.d.ts +6 -12
  142. package/dist/types/plots/segments/config.d.ts +1 -1
  143. package/dist/types/plots/styling.d.ts +10 -0
  144. package/dist/{plots/utils → utils}/delta.d.ts +2 -2
  145. package/dist/{plots/utils → utils}/delta.js +1 -1
  146. package/dist/utils/edgeAwareAnchor.d.ts +37 -0
  147. package/dist/utils/edgeAwareAnchor.js +65 -0
  148. package/dist/utils/grouping.d.ts +1 -1
  149. package/dist/utils/interpolate.d.ts +1 -1
  150. package/package.json +3 -1
  151. package/dist/layout/geo/GeoLayout.svelte +0 -51
  152. package/dist/layout/geo/GeoLayout.svelte.d.ts +0 -24
  153. package/dist/layout/geo/resolveProjection.js +0 -48
  154. package/dist/layout/plot/GridLayout.svelte +0 -30
  155. package/dist/layout/plot/RuleLayout.svelte +0 -111
  156. package/dist/plots/bar/BarSegments.svelte +0 -69
  157. package/dist/types/plots/delta.d.ts +0 -19
  158. package/dist/types/plots/styles/common.d.ts +0 -52
  159. package/dist/types/plots/styles/geo.d.ts +0 -42
  160. /package/dist/{plots/geo → layout/coordinates}/projections/argentina.js +0 -0
  161. /package/dist/{plots/geo → layout/coordinates}/projections/fit.d.ts +0 -0
  162. /package/dist/{plots/geo → layout/coordinates}/projections/fit.js +0 -0
  163. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.d.ts +0 -0
  164. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.js +0 -0
  165. /package/dist/{plots/geo → layout/coordinates}/zoom.svelte.js +0 -0
  166. /package/dist/types/{plots/axis.js → layout/coordinates.js} +0 -0
  167. /package/dist/types/{plots/delta.js → layout/geometry.js} +0 -0
  168. /package/dist/types/{plots/scales/geo.js → layout/scales.js} +0 -0
  169. /package/dist/types/{plots/styles/common.js → layout/styles.js} +0 -0
  170. /package/dist/types/{plots/styles/geo.js → markers/all.js} +0 -0
@@ -1,30 +1,38 @@
1
- <script lang="ts" generics="TData extends Record<string, unknown>">
1
+ <script lang="ts" generics="TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData">
2
2
  import type { ComponentProps, Snippet } from 'svelte';
3
- import { SvelteSet } from 'svelte/reactivity';
4
3
  import { Plot, Pointer } from 'svelteplot';
5
4
  import { getConfiguration } from '../../configuration/config.svelte';
6
5
  import { AUTO_MARGIN_ESTIMATE, numericMargin } from './margins';
6
+ import { buildScale } from '../scales/buildScale';
7
+ import ScalesLayout from '../scales/ScalesLayout.svelte';
8
+ import GeometryLayout from '../geometry/GeometryLayout.svelte';
9
+ import CoordinatesLayout from '../coordinates/CoordinatesLayout.svelte';
10
+ import StylesLayout from '../styles/StylesLayout.svelte';
7
11
  import { getHover, createHover } from '../tooltip/hover.svelte';
8
12
  import { createHoverController } from '../tooltip/controller.svelte';
9
13
  import TooltipLayout from '../tooltip/TooltipLayout.svelte';
10
- import type { AxisValue, AxisScale } from '../../types/plots/axis';
11
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
12
- import type { GeoProjectionConfig } from '../../types/plots/scales/geo';
13
- import type { HoverStrategy, HoverPoint } from '../../types/layout/tooltip';
14
- import type { MarginConfig, TooltipConfig } from '../../types/plots/props';
14
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
15
+ import type { CoordinatesConfig } from '../../types/layout/coordinates';
16
+ import type { StylesLayoutConfig } from '../../types/layout/styles';
17
+ import type { ScopedMarkerGroup } from '../../types/layout/geometry';
18
+ import type { HoverPoint } from '../../types/layout/tooltip';
19
+ import type { MarginConfig, HoverConfig } from '../../types/plots/props';
20
+ import type { MarkerConfig } from '../../types/markers/all';
21
+ import type { Series } from '../../types/plots/data/common';
22
+ import type { GeoFeature } from '../../types/plots/data/geo';
15
23
 
16
24
  type SveltePlotProps = ComponentProps<typeof Plot>;
17
25
 
18
26
  /**
19
- * Shared chrome around every plot kind's svelteplot markup: the pointer-event
20
- * wrapper, the hover/tooltip state machine, the `<Plot>` element itself, and
21
- * the floating tooltip. Deliberately marker/axis-agnostic axis-having plot
22
- * kinds (line/bar/heatmap/pyramid) additionally compose `AxisLayout` /
23
- * `GridLayout` / `RuleLayout` inside `children`; `GeoPlot` composes `GeoLayout`
24
- * instead, since a map has no x/y axis pair. Every plot kind supplies its own
25
- * `<Plot x y projection>` scale config (either directly via `x`/`y`/`projection`,
26
- * or by leaving `x`/`y` unset so this component derives them from `scales`) and
27
- * its own marks/markers through `children`.
27
+ * Shared chrome around every plot kind's svelteplot markup: the
28
+ * pointer-event wrapper, the hover/tooltip state machine, and the floating
29
+ * tooltip. Composes the Coordinates and Styles layers around every plot
30
+ * kind's own content, plus `GeometryLayout` (which renders nothing extra
31
+ * when `markers` is empty/unset). `ScalesLayout` composes in
32
+ * independently, only when `scales` is passed. Every plot kind supplies
33
+ * its own `<Plot x y projection>` scale config, either directly via
34
+ * `x`/`y` or by leaving them unset so this component derives them from
35
+ * `scales`.
28
36
  */
29
37
  let {
30
38
  width,
@@ -32,83 +40,84 @@
32
40
  data,
33
41
  getX,
34
42
  getY,
35
- hoverPoints,
36
- label,
37
- tooltip = undefined,
38
- hoverActive,
39
- hoverStrategy = undefined,
40
- hoverSync = undefined,
43
+ hover,
41
44
  x = undefined,
42
45
  y = undefined,
43
46
  scales = undefined,
44
- projection = undefined,
47
+ coordinates = undefined,
45
48
  margins,
46
49
  marginEstimate = AUTO_MARGIN_ESTIMATE,
47
50
  xTickRotate = undefined,
48
- excludeHover = undefined,
49
- touchAction = undefined,
51
+ styles = undefined,
52
+ markers = undefined,
53
+ seriesData = undefined,
54
+ scopedGroups = undefined,
55
+ geoAllFeatures = undefined,
50
56
  containerEl = $bindable(undefined),
51
57
  children,
52
58
  }: {
53
59
  width: number;
54
60
  height: number;
55
- /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching. */
61
+ /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching, and to `CoordinatesLayout` for a `domain: 'data'` projection auto-fit. */
56
62
  data: TData[];
57
63
  getX: (d: TData) => AxisValue;
58
64
  getY: (d: TData) => AxisValue;
59
- hoverPoints: HoverPoint<TData>[];
60
- label: (row: TData) => string;
61
- tooltip?: TooltipConfig<TData>;
62
- /** Whether the pointer/hover/tooltip machinery should mount at all. */
63
- hoverActive: boolean;
64
- /** Resolved hover strategy for the *local* hover store (ignored once a shared store is in scope). */
65
- hoverStrategy?: HoverStrategy;
66
- /** Resolved sync flag for the *local* hover store (ignored once a shared store is in scope). */
67
- hoverSync?: boolean;
65
+ /** Every hover/tooltip prop this component's own hover machinery reads — see {@link HoverConfig}. */
66
+ hover: HoverConfig<TData>;
68
67
  /** Explicit `<Plot x>` override — bypasses `scales`-derived resolution (e.g. GeoPlot's fixed lon/lat domain). */
69
68
  x?: SveltePlotProps['x'];
70
69
  /** Explicit `<Plot y>` override — see `x`. */
71
70
  y?: SveltePlotProps['y'];
72
71
  /** Scale config `x`/`y` are derived from when not given directly. */
73
72
  scales?: AxisBasedScalesConfig<TData>;
74
- /** Forwarded to `<Plot projection>` — GeoPlot only. */
75
- projection?: GeoProjectionConfig;
73
+ /** This plot kind's coordinate system config `GeoPlot` only (a projection + pan/zoom). */
74
+ coordinates?: CoordinatesConfig;
76
75
  /** Partial margin override — merged on top of the theme. */
77
76
  margins?: MarginConfig;
78
77
  /** Numeric fallback for a margin side that resolved to `'auto'` — see `numericMargin`'s doc comment. */
79
78
  marginEstimate?: Record<'top' | 'right' | 'bottom' | 'left', number>;
80
79
  /** Boosts the bottom margin for a rotated x tick label — axis-having plot kinds only. */
81
80
  xTickRotate?: number;
81
+ /** This plot kind's own `styles` prop, forwarded to the Styles layer. */
82
+ styles?: StylesLayoutConfig;
82
83
  /**
83
- * When it returns `true` for a pointer event, that event is treated as a
84
- * leave instead of a move lets a nested layout (e.g. GeoLayout's inset
85
- * markers) opt specific screen regions out of this plot's own hover
86
- * matching without this component needing to know why.
84
+ * This plot kind's own markers array. Accepts a plain array, or a
85
+ * builder function for a plot kind whose own marker construction needs
86
+ * `setHoverMatch`/`clearHoverMatch`, only available once this
87
+ * component's own hover controller is set up.
87
88
  */
88
- excludeHover?: (e: PointerEvent) => boolean;
89
- /** Forwarded to the wrapper div's `touch-action` CSS — GeoPlot's zoom/pan only. */
90
- touchAction?: string;
91
- /** The wrapper div's own elementbind for a nested layout that needs it for its own gesture handling (e.g. GeoLayout's zoom/rotate). */
89
+ markers?:
90
+ | MarkerConfig[]
91
+ | ((hover: { setHoverMatch: (rows: TData[]) => void; clearHoverMatch: () => void }) => MarkerConfig[]);
92
+ /** All resolved series, forwarded to `GeometryLayout` axis-based kinds only. Independently generic over `TSeries` (defaults to `TData`). */
93
+ seriesData?: Series<TSeries>[];
94
+ /** Per-series `GeometryLayout` groups — axis-based kinds only, same `TSeries` as `seriesData`. */
95
+ scopedGroups?: ScopedMarkerGroup<TSeries>[];
96
+ /** `GeoPlot`'s own untouched, tagged features, forwarded to `GeometryLayout` — `inset` marker only. */
97
+ geoAllFeatures?: (GeoFeature<Record<string, unknown>> & { __id: string })[];
98
+ /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. `CoordinatesLayout`'s zoom/rotate). */
92
99
  containerEl?: HTMLDivElement;
93
100
  /**
94
- * `matchedPoints` carries the source `row` alongside the display fields
95
- * needed by plot kinds whose semantic value isn't tied to a fixed
96
- * visual axis (e.g. `BarPlot`'s category, which can be drawn on either
97
- * axis depending on orientation), so a template can resolve it straight
98
- * off `row` instead of assuming which of `.x`/`.y` it landed on.
99
- * `impliesRuleX`/`impliesRuleY` tell the plot kind whether the active
100
- * strategy implies a crosshair rule on each axis by default.
101
- * `numericMargins` lets a nested layout align itself with the plot's own
102
- * content box without recomputing margins itself.
101
+ * `matchedPoints` carries the source `row` alongside the display fields,
102
+ * so a template can resolve it straight off `row` instead of assuming
103
+ * which visual axis it landed on. `activePoint` is the one matched point
104
+ * under the cursor. `numericMargins` lets a nested layout align itself
105
+ * with the plot's own content box. `setHoverMatch`/`clearHoverMatch` let
106
+ * a plot kind drive the match itself instead of svelteplot's
107
+ * distance-based `<Pointer>` see `hover.pointMatching`. When `markers`
108
+ * is given, this is `GeometryLayout`'s own `children` instead of raw
109
+ * plot content. Optional a plot kind whose every mark is a `markers`
110
+ * entry has nothing left to render here.
103
111
  */
104
- children: Snippet<
112
+ children?: Snippet<
105
113
  [{
106
114
  matchedPoints: HoverPoint<TData>[];
107
- ruleX: AxisValue | null;
108
- ruleY: AxisValue | null;
109
- impliesRuleX: boolean;
110
- impliesRuleY: boolean;
115
+ activePoint: HoverPoint<TData> | null;
116
+ impliesCrosshairX: boolean;
117
+ impliesCrosshairY: boolean;
111
118
  numericMargins: { top: number; right: number; bottom: number; left: number };
119
+ setHoverMatch: (rows: TData[]) => void;
120
+ clearHoverMatch: () => void;
112
121
  }]
113
122
  >;
114
123
  } = $props();
@@ -142,112 +151,64 @@
142
151
  left: numericMargin('left', computedMargins.left, marginEstimate),
143
152
  });
144
153
 
145
- /** One representative row per unique axis value (first occurrence in `data`). */
146
- function uniqueRows(rows: TData[], getAxisValue: (d: TData) => AxisValue): TData[] {
147
- const seen = new SvelteSet<AxisValue>();
148
- const out: TData[] = [];
149
- for (const row of rows) {
150
- const v = getAxisValue(row);
151
- if (seen.has(v)) continue;
152
- seen.add(v);
153
- out.push(row);
154
- }
155
- return out;
156
- }
157
-
158
- /**
159
- * svelteplot's own `sort` scale option only toggles a fixed ascending sort
160
- * (lexicographic for strings) — it can't take a custom comparator. When the
161
- * caller supplies one via `scales.x.sort`/`scales.y.sort` for a string-valued
162
- * axis (e.g. dates formatted as text, or categories ordered by another
163
- * field), compute the ordered domain ourselves and hand it to the scale as
164
- * an explicit `domain`, which svelteplot does respect as-is for ordinal
165
- * scales. The comparator receives one representative row per unique axis
166
- * value, not just the axis value itself, so it can sort by this axis's own
167
- * value or by any other field on the row.
168
- */
169
- function sortedScale(scale: AxisScale<TData> | undefined, rows: TData[], getAxisValue: (d: TData) => AxisValue): AxisScale<TData> | undefined {
170
- const sort = scale?.sort;
171
- if (!sort || scale?.domain) return scale;
172
- const reps = uniqueRows(rows, getAxisValue);
173
- if (reps.length === 0 || !reps.every((r) => typeof getAxisValue(r) === 'string')) return scale;
174
- const domain = [...reps].sort(sort).map(getAxisValue);
175
- return { ...scale, domain };
176
- }
177
-
178
- /**
179
- * `grid`/`seriesLayout`/`seriesPadding` and a vertical `label` are rendered
180
- * by `GridLayout`/`AxisLayout` themselves rather than passed through to
181
- * svelteplot (which only draws a fixed-style implicit grid, and never
182
- * rotates the axis title) — stripped here so they don't also trigger
183
- * svelteplot's own (duplicate, unstyled) rendering of them. Also the single
184
- * place a plot kind's fully-resolved `scales.x`/`scales.y` (defaults
185
- * already merged in by the plot itself) turns into what svelteplot's own,
186
- * narrower scale option type actually accepts.
187
- */
188
- function finalizeScale(scale: AxisScale<TData> | undefined, rows: TData[], getAxisValue: (d: TData) => AxisValue): AxisScale<TData> | undefined {
189
- const sorted = sortedScale(scale, rows, getAxisValue);
190
- if (!sorted) return sorted;
191
- const out = { ...sorted };
192
- delete out.grid;
193
- delete out.seriesLayout;
194
- delete out.seriesPadding;
195
- if (scale?.labelOrientation === 'vertical') delete out.label;
196
- return out;
197
- }
198
-
199
154
  // Cast to svelteplot's own (narrower, axis-specific) scale option type:
200
- // finalizeScale has already stripped the fields that don't exist there
155
+ // buildScale has already stripped the fields that don't exist there
201
156
  // (grid, the vertical label config, BarPlot's series-layout knobs).
202
- const resolvedX = $derived(x !== undefined ? x : (finalizeScale(scales?.x, data, getX) as SveltePlotProps['x']));
203
- const resolvedY = $derived(y !== undefined ? y : (finalizeScale(scales?.y, data, getY) as SveltePlotProps['y']));
157
+ const resolvedX = $derived(x !== undefined ? x : (buildScale(scales?.x, data, getX) as SveltePlotProps['x']));
158
+ const resolvedY = $derived(y !== undefined ? y : (buildScale(scales?.y, data, getY) as SveltePlotProps['y']));
159
+
160
+ /** Only ever meaningful for `GeoPlot` — every other kind leaves `coordinates` unset. */
161
+ const touchAction = $derived(coordinates?.zoom !== false && coordinates?.zoom != null ? 'none' : undefined);
204
162
 
205
163
  // ── Hover / tooltip ───────────────────────────────────────────────────────
206
164
  const sharedHover = getHover();
207
165
  const localHover = createHover();
208
- const hover = $derived(sharedHover ?? localHover);
166
+ const hoverStore = $derived(sharedHover ?? localHover);
167
+
168
+ /** Declared intent, defaulted — see {@link HoverConfig.pointMatching}. */
169
+ const pointMatchingStrategy = $derived(hover.pointMatching ?? 'nearest');
209
170
 
210
171
  /**
211
- * Which axes svelteplot's `<Pointer>` should search along derived from
212
- * the *active* strategy rather than taken as separate props. Passing
213
- * `undefined` for an axis collapses every point to the same coordinate on
214
- * that axis in the search quadtree, making the nearest-neighbor match
215
- * ignore it entirely: `'x'` (line/bar) searches x only, `'y'` (pyramid —
216
- * its category sits on y, and a bar's own magnitude on x shouldn't pull in
217
- * a *different* row just because that row's bar tip is nearer in x)
218
- * searches y only, and `'punctual'` (heatmap/geo's 2D grid) needs both.
219
- * Reactive to `hover.strategy`, so a caller overriding `tooltip.strategy`/a
220
- * hover marker's `strategy` away from the plot kind's default gets
221
- * correctly-dimensioned matching too.
172
+ * Which axes svelteplot's `<Pointer>` should search along, derived from
173
+ * the active strategy. `undefined` for an axis collapses every point to
174
+ * the same coordinate on that axis, excluding it from the match entirely.
222
175
  */
223
- const pointerMatchX = $derived(hover.strategy === 'y' ? undefined : getX);
224
- const pointerMatchY = $derived(hover.strategy === 'x' ? undefined : getY);
176
+ const pointerMatchX = $derived(hoverStore.strategy === 'y' ? undefined : getX);
177
+ const pointerMatchY = $derived(hoverStore.strategy === 'x' ? undefined : getY);
225
178
 
226
179
  $effect(() => {
227
180
  if (sharedHover) return;
228
- localHover.strategy = hoverStrategy ?? 'x';
229
- localHover.sync = hoverSync ?? false;
181
+ localHover.strategy = hover.strategy ?? 'x';
182
+ localHover.sync = hover.sync ?? false;
230
183
  });
231
184
 
232
185
  const ctrl = createHoverController<TData>({
233
- hover: () => hover,
234
- facetId: () => tooltip?.facetId ?? '',
235
- points: () => hoverPoints,
186
+ hover: () => hoverStore,
187
+ facetId: () => hover.tooltip?.facetId ?? '',
188
+ points: () => hover.points,
236
189
  getX: () => getX,
237
190
  getY: () => getY,
238
- label: () => label,
239
- seriesFilter: () => tooltip?.series,
191
+ label: () => hover.label,
192
+ seriesFilter: () => hover.tooltip?.series,
240
193
  width: () => width,
241
194
  height: () => height,
242
195
  });
243
196
 
244
197
  function onMove(e: PointerEvent) {
245
- if (excludeHover?.(e)) {
198
+ if (hover.exclude?.(e)) {
246
199
  ctrl.onLeave();
247
200
  return;
248
201
  }
249
202
  ctrl.onMove(e);
250
203
  }
204
+
205
+ const resolvedMarkers = $derived(
206
+ typeof markers === 'function' ? markers({ setHoverMatch: ctrl.onPointer, clearHoverMatch: ctrl.onLeave }) : markers,
207
+ );
208
+
209
+ /** `hover.crosshair === false` forces both off, overriding the strategy-implied default — see {@link HoverConfig.crosshair}. */
210
+ const effectiveImpliesCrosshairX = $derived(hover.crosshair === false ? false : ctrl.impliesCrosshairX);
211
+ const effectiveImpliesCrosshairY = $derived(hover.crosshair === false ? false : ctrl.impliesCrosshairY);
251
212
  </script>
252
213
 
253
214
  <!-- svelte-ignore a11y_no_static_element_interactions -->
@@ -256,40 +217,66 @@
256
217
  bind:this={containerEl}
257
218
  style:color={cfg.axis.color}
258
219
  style:touch-action={touchAction}
259
- onpointermove={hoverActive ? onMove : undefined}
260
- onpointerleave={hoverActive ? ctrl.onLeave : undefined}
220
+ onpointermove={hover.active ? onMove : undefined}
221
+ onpointerleave={hover.active ? ctrl.onLeave : undefined}
261
222
  >
262
- <Plot
223
+ <CoordinatesLayout
263
224
  {width}
264
225
  {height}
265
226
  margin={computedMargins}
266
227
  x={resolvedX}
267
228
  y={resolvedY}
268
- projection={projection as SveltePlotProps['projection']}
229
+ {coordinates}
230
+ {data}
231
+ {containerEl}
269
232
  >
270
- {@render children({
271
- matchedPoints: ctrl.points,
272
- ruleX: ctrl.ruleX,
273
- ruleY: ctrl.ruleY,
274
- impliesRuleX: ctrl.impliesRuleX,
275
- impliesRuleY: ctrl.impliesRuleY,
276
- numericMargins,
277
- })}
233
+ <StylesLayout {styles} {width} {height} {numericMargins} />
234
+ {#if scales !== undefined}
235
+ <ScalesLayout {data} {getX} {getY} {width} {height} {numericMargins} {scales} />
236
+ {/if}
237
+ <GeometryLayout
238
+ markers={resolvedMarkers}
239
+ seriesData={seriesData ?? []}
240
+ scopedGroups={scopedGroups ?? []}
241
+ matchedPoints={ctrl.points}
242
+ activePoint={ctrl.activePoint}
243
+ impliesCrosshairX={effectiveImpliesCrosshairX}
244
+ impliesCrosshairY={effectiveImpliesCrosshairY}
245
+ {width}
246
+ {height}
247
+ {numericMargins}
248
+ {data}
249
+ {geoAllFeatures}
250
+ tooltipAnchorX={hover.tooltip?.anchorX}
251
+ tooltipAnchorY={hover.tooltip?.anchorY}
252
+ >
253
+ {@render children?.({
254
+ matchedPoints: ctrl.points,
255
+ activePoint: ctrl.activePoint,
256
+ impliesCrosshairX: effectiveImpliesCrosshairX,
257
+ impliesCrosshairY: effectiveImpliesCrosshairY,
258
+ numericMargins,
259
+ setHoverMatch: ctrl.onPointer,
260
+ clearHoverMatch: ctrl.onLeave,
261
+ })}
262
+ </GeometryLayout>
278
263
 
279
- {#if hoverActive}
264
+ {#if hover.active && pointMatchingStrategy === 'nearest'}
280
265
  <Pointer {data} x={pointerMatchX} y={pointerMatchY} maxDistance={400} onupdate={ctrl.onPointer} />
281
266
  {/if}
282
- </Plot>
267
+ </CoordinatesLayout>
283
268
 
284
- {#if tooltip}
269
+ {#if hover.tooltip}
285
270
  <TooltipLayout
286
271
  visible={ctrl.showTooltip && ctrl.tooltipPosition != null}
287
272
  x={ctrl.tooltipPosition?.x ?? 0}
288
273
  y={ctrl.tooltipPosition?.y ?? 0}
289
274
  bounds={{ width, height }}
290
275
  rows={ctrl.matchedRows.map((p) => p.row)}
291
- format={tooltip.format}
292
- content={tooltip.content}
276
+ format={hover.tooltip.format}
277
+ content={hover.tooltip.content}
278
+ anchorX={hover.tooltip.anchorX}
279
+ anchorY={hover.tooltip.anchorY}
293
280
  />
294
281
  {/if}
295
282
  </div>
@@ -1,74 +1,84 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { AxisValue } from '../../types/plots/axis';
3
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
- import type { GeoProjectionConfig } from '../../types/plots/scales/geo';
5
- import type { HoverStrategy, HoverPoint } from '../../types/layout/tooltip';
6
- import type { MarginConfig, TooltipConfig } from '../../types/plots/props';
7
- declare function $$render<TData extends Record<string, unknown>>(): {
2
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
3
+ import type { CoordinatesConfig } from '../../types/layout/coordinates';
4
+ import type { StylesLayoutConfig } from '../../types/layout/styles';
5
+ import type { ScopedMarkerGroup } from '../../types/layout/geometry';
6
+ import type { HoverPoint } from '../../types/layout/tooltip';
7
+ import type { MarginConfig, HoverConfig } from '../../types/plots/props';
8
+ import type { MarkerConfig } from '../../types/markers/all';
9
+ import type { Series } from '../../types/plots/data/common';
10
+ import type { GeoFeature } from '../../types/plots/data/geo';
11
+ declare function $$render<TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData>(): {
8
12
  props: {
9
13
  width: number;
10
14
  height: number;
11
- /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching. */
15
+ /** Flat rows fed to svelteplot's `<Pointer>` for nearest-point matching, and to `CoordinatesLayout` for a `domain: 'data'` projection auto-fit. */
12
16
  data: TData[];
13
17
  getX: (d: TData) => AxisValue;
14
18
  getY: (d: TData) => AxisValue;
15
- hoverPoints: HoverPoint<TData>[];
16
- label: (row: TData) => string;
17
- tooltip?: TooltipConfig<TData>;
18
- /** Whether the pointer/hover/tooltip machinery should mount at all. */
19
- hoverActive: boolean;
20
- /** Resolved hover strategy for the *local* hover store (ignored once a shared store is in scope). */
21
- hoverStrategy?: HoverStrategy;
22
- /** Resolved sync flag for the *local* hover store (ignored once a shared store is in scope). */
23
- hoverSync?: boolean;
19
+ /** Every hover/tooltip prop this component's own hover machinery reads — see {@link HoverConfig}. */
20
+ hover: HoverConfig<TData>;
24
21
  /** Explicit `<Plot x>` override — bypasses `scales`-derived resolution (e.g. GeoPlot's fixed lon/lat domain). */
25
- x?: false | Partial<import("svelteplot/types/scale.js").XScaleOptions> | import("svelteplot/types/data.js").RawValue[] | undefined;
22
+ x?: false | import("svelteplot/types/data.js").RawValue[] | Partial<import("svelteplot/types/scale.js").XScaleOptions> | undefined;
26
23
  /** Explicit `<Plot y>` override — see `x`. */
27
24
  y?: false | import("svelteplot/types/data.js").RawValue[] | Partial<import("svelteplot/types/scale.js").YScaleOptions> | undefined;
28
25
  /** Scale config `x`/`y` are derived from when not given directly. */
29
26
  scales?: AxisBasedScalesConfig<TData>;
30
- /** Forwarded to `<Plot projection>` — GeoPlot only. */
31
- projection?: GeoProjectionConfig;
27
+ /** This plot kind's coordinate system config `GeoPlot` only (a projection + pan/zoom). */
28
+ coordinates?: CoordinatesConfig;
32
29
  /** Partial margin override — merged on top of the theme. */
33
30
  margins?: MarginConfig;
34
31
  /** Numeric fallback for a margin side that resolved to `'auto'` — see `numericMargin`'s doc comment. */
35
32
  marginEstimate?: Record<"top" | "right" | "bottom" | "left", number>;
36
33
  /** Boosts the bottom margin for a rotated x tick label — axis-having plot kinds only. */
37
34
  xTickRotate?: number;
35
+ /** This plot kind's own `styles` prop, forwarded to the Styles layer. */
36
+ styles?: StylesLayoutConfig;
38
37
  /**
39
- * When it returns `true` for a pointer event, that event is treated as a
40
- * leave instead of a move lets a nested layout (e.g. GeoLayout's inset
41
- * markers) opt specific screen regions out of this plot's own hover
42
- * matching without this component needing to know why.
38
+ * This plot kind's own markers array. Accepts a plain array, or a
39
+ * builder function for a plot kind whose own marker construction needs
40
+ * `setHoverMatch`/`clearHoverMatch`, only available once this
41
+ * component's own hover controller is set up.
43
42
  */
44
- excludeHover?: (e: PointerEvent) => boolean;
45
- /** Forwarded to the wrapper div's `touch-action` CSS — GeoPlot's zoom/pan only. */
46
- touchAction?: string;
47
- /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. GeoLayout's zoom/rotate). */
43
+ markers?: MarkerConfig[] | ((hover: {
44
+ setHoverMatch: (rows: TData[]) => void;
45
+ clearHoverMatch: () => void;
46
+ }) => MarkerConfig[]);
47
+ /** All resolved series, forwarded to `GeometryLayout` — axis-based kinds only. Independently generic over `TSeries` (defaults to `TData`). */
48
+ seriesData?: Series<TSeries>[];
49
+ /** Per-series `GeometryLayout` groups — axis-based kinds only, same `TSeries` as `seriesData`. */
50
+ scopedGroups?: ScopedMarkerGroup<TSeries>[];
51
+ /** `GeoPlot`'s own untouched, tagged features, forwarded to `GeometryLayout` — `inset` marker only. */
52
+ geoAllFeatures?: (GeoFeature<Record<string, unknown>> & {
53
+ __id: string;
54
+ })[];
55
+ /** The wrapper div's own element — bind for a nested layout that needs it for its own gesture handling (e.g. `CoordinatesLayout`'s zoom/rotate). */
48
56
  containerEl?: HTMLDivElement;
49
57
  /**
50
- * `matchedPoints` carries the source `row` alongside the display fields
51
- * needed by plot kinds whose semantic value isn't tied to a fixed
52
- * visual axis (e.g. `BarPlot`'s category, which can be drawn on either
53
- * axis depending on orientation), so a template can resolve it straight
54
- * off `row` instead of assuming which of `.x`/`.y` it landed on.
55
- * `impliesRuleX`/`impliesRuleY` tell the plot kind whether the active
56
- * strategy implies a crosshair rule on each axis by default.
57
- * `numericMargins` lets a nested layout align itself with the plot's own
58
- * content box without recomputing margins itself.
58
+ * `matchedPoints` carries the source `row` alongside the display fields,
59
+ * so a template can resolve it straight off `row` instead of assuming
60
+ * which visual axis it landed on. `activePoint` is the one matched point
61
+ * under the cursor. `numericMargins` lets a nested layout align itself
62
+ * with the plot's own content box. `setHoverMatch`/`clearHoverMatch` let
63
+ * a plot kind drive the match itself instead of svelteplot's
64
+ * distance-based `<Pointer>` see `hover.pointMatching`. When `markers`
65
+ * is given, this is `GeometryLayout`'s own `children` instead of raw
66
+ * plot content. Optional a plot kind whose every mark is a `markers`
67
+ * entry has nothing left to render here.
59
68
  */
60
- children: Snippet<[{
69
+ children?: Snippet<[{
61
70
  matchedPoints: HoverPoint<TData>[];
62
- ruleX: AxisValue | null;
63
- ruleY: AxisValue | null;
64
- impliesRuleX: boolean;
65
- impliesRuleY: boolean;
71
+ activePoint: HoverPoint<TData> | null;
72
+ impliesCrosshairX: boolean;
73
+ impliesCrosshairY: boolean;
66
74
  numericMargins: {
67
75
  top: number;
68
76
  right: number;
69
77
  bottom: number;
70
78
  left: number;
71
79
  };
80
+ setHoverMatch: (rows: TData[]) => void;
81
+ clearHoverMatch: () => void;
72
82
  }]>;
73
83
  };
74
84
  exports: {};
@@ -76,20 +86,20 @@ declare function $$render<TData extends Record<string, unknown>>(): {
76
86
  slots: {};
77
87
  events: {};
78
88
  };
79
- declare class __sveltets_Render<TData extends Record<string, unknown>> {
80
- props(): ReturnType<typeof $$render<TData>>['props'];
81
- events(): ReturnType<typeof $$render<TData>>['events'];
82
- slots(): ReturnType<typeof $$render<TData>>['slots'];
89
+ declare class __sveltets_Render<TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData> {
90
+ props(): ReturnType<typeof $$render<TData, TSeries>>['props'];
91
+ events(): ReturnType<typeof $$render<TData, TSeries>>['events'];
92
+ slots(): ReturnType<typeof $$render<TData, TSeries>>['slots'];
83
93
  bindings(): "containerEl";
84
94
  exports(): {};
85
95
  }
86
96
  interface $$IsomorphicComponent {
87
- new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
88
- $$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
89
- } & ReturnType<__sveltets_Render<TData>['exports']>;
90
- <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
91
- z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
97
+ new <TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData, TSeries>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData, TSeries>['props']>, ReturnType<__sveltets_Render<TData, TSeries>['events']>, ReturnType<__sveltets_Render<TData, TSeries>['slots']>> & {
98
+ $$bindings?: ReturnType<__sveltets_Render<TData, TSeries>['bindings']>;
99
+ } & ReturnType<__sveltets_Render<TData, TSeries>['exports']>;
100
+ <TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData>(internal: unknown, props: ReturnType<__sveltets_Render<TData, TSeries>['props']> & {}): ReturnType<__sveltets_Render<TData, TSeries>['exports']>;
101
+ z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
92
102
  }
93
103
  declare const BasePlotLayout: $$IsomorphicComponent;
94
- type BasePlotLayout<TData extends Record<string, unknown>> = InstanceType<typeof BasePlotLayout<TData>>;
104
+ type BasePlotLayout<TData extends Record<string, unknown>, TSeries extends Record<string, unknown> = TData> = InstanceType<typeof BasePlotLayout<TData, TSeries>>;
95
105
  export default BasePlotLayout;
@@ -4,22 +4,6 @@ export declare const AUTO_MARGIN_ESTIMATE: {
4
4
  readonly bottom: 30;
5
5
  readonly left: 40;
6
6
  };
7
- /**
8
- * GeoPlot's own margin default — a small, uniform gap on every side, unlike
9
- * `AUTO_MARGIN_ESTIMATE`'s asymmetric axis-tick-label estimate (extra room
10
- * on the left/bottom for y tick labels and an x-axis title). A map has no
11
- * axes to make room for, so borrowing that estimate just left GeoPlot with
12
- * a lopsided, oversized margin — most visibly a much taller gap under the
13
- * map than an `inset` marker's own default `margin` (see `insetLayout.ts`),
14
- * even though both are meant to read as the same edge gap. Matching this
15
- * constant to that 8px inset default keeps the two visually aligned.
16
- */
17
- export declare const GEO_MARGIN_ESTIMATE: {
18
- readonly top: 8;
19
- readonly right: 8;
20
- readonly bottom: 8;
21
- readonly left: 8;
22
- };
23
7
  /**
24
8
  * Best-effort numeric fallback for a margin side that resolved to `'auto'`.
25
9
  * Used for layout heuristics that need *a* number before svelteplot has
@@ -1,15 +1,4 @@
1
1
  export const AUTO_MARGIN_ESTIMATE = { top: 20, right: 20, bottom: 30, left: 40 };
2
- /**
3
- * GeoPlot's own margin default — a small, uniform gap on every side, unlike
4
- * `AUTO_MARGIN_ESTIMATE`'s asymmetric axis-tick-label estimate (extra room
5
- * on the left/bottom for y tick labels and an x-axis title). A map has no
6
- * axes to make room for, so borrowing that estimate just left GeoPlot with
7
- * a lopsided, oversized margin — most visibly a much taller gap under the
8
- * map than an `inset` marker's own default `margin` (see `insetLayout.ts`),
9
- * even though both are meant to read as the same edge gap. Matching this
10
- * constant to that 8px inset default keeps the two visually aligned.
11
- */
12
- export const GEO_MARGIN_ESTIMATE = { top: 8, right: 8, bottom: 8, left: 8 };
13
2
  /**
14
3
  * Best-effort numeric fallback for a margin side that resolved to `'auto'`.
15
4
  * Used for layout heuristics that need *a* number before svelteplot has