@fundar/data-chart-telling 0.0.25 → 0.0.27

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 (198) hide show
  1. package/dist/charts/BaseChart.svelte +18 -10
  2. package/dist/charts/BaseChart.svelte.d.ts +4 -2
  3. package/dist/charts/bar/Chart.svelte +2 -0
  4. package/dist/charts/heatmap/Chart.svelte +5 -3
  5. package/dist/charts/heatmap/Chart.svelte.d.ts +1 -2
  6. package/dist/charts/line/Chart.svelte +2 -0
  7. package/dist/charts/pyramid/Chart.svelte +5 -3
  8. package/dist/charts/pyramid/Chart.svelte.d.ts +1 -2
  9. package/dist/charts/scatter/Chart.svelte +95 -0
  10. package/dist/charts/scatter/Chart.svelte.d.ts +28 -0
  11. package/dist/configuration/config.svelte.js +28 -0
  12. package/dist/configuration/themes/dark.js +4 -0
  13. package/dist/configuration/themes/index.d.ts +112 -0
  14. package/dist/index.d.ts +26 -16
  15. package/dist/index.js +11 -3
  16. package/dist/layout/coordinates/CoordinatesLayout.svelte +93 -0
  17. package/dist/layout/{plot/RuleLayout.svelte.d.ts → coordinates/CoordinatesLayout.svelte.d.ts} +13 -16
  18. package/dist/layout/coordinates/margins.d.ts +16 -0
  19. package/dist/layout/coordinates/margins.js +11 -0
  20. package/dist/{plots/geo → layout/coordinates}/projections/argentina.d.ts +1 -1
  21. package/dist/layout/{geo → coordinates}/resolveProjection.d.ts +1 -1
  22. package/dist/layout/coordinates/resolveProjection.js +24 -0
  23. package/dist/{plots/geo → layout/coordinates}/zoom.svelte.d.ts +1 -1
  24. package/dist/layout/facet/FacetIndicator.svelte +31 -0
  25. package/dist/layout/facet/FacetIndicator.svelte.d.ts +8 -0
  26. package/dist/layout/facet/FacetLayout.svelte +249 -24
  27. package/dist/layout/facet/FacetLayout.svelte.d.ts +7 -1
  28. package/dist/layout/geometry/GeometryLayout.svelte +231 -0
  29. package/dist/layout/geometry/GeometryLayout.svelte.d.ts +62 -0
  30. package/dist/layout/legend/ContinuousSection.svelte +1 -1
  31. package/dist/layout/legend/ContinuousSection.svelte.d.ts +1 -1
  32. package/dist/layout/legend/DiscreteSection.svelte +1 -1
  33. package/dist/layout/legend/DiscreteSection.svelte.d.ts +1 -1
  34. package/dist/layout/legend/LegendLayout.svelte +1 -1
  35. package/dist/layout/legend/LegendLayout.svelte.d.ts +1 -1
  36. package/dist/layout/legend/interaction.svelte.d.ts +9 -1
  37. package/dist/layout/legend/interaction.svelte.js +9 -1
  38. package/dist/layout/plot/BasePlotLayout.svelte +142 -155
  39. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +61 -51
  40. package/dist/layout/plot/margins.d.ts +0 -16
  41. package/dist/layout/plot/margins.js +0 -11
  42. package/dist/layout/{plot/AxisLayout.svelte → scales/ScalesLayout.svelte} +23 -16
  43. package/dist/layout/{plot/AxisLayout.svelte.d.ts → scales/ScalesLayout.svelte.d.ts} +4 -5
  44. package/dist/layout/scales/buildScale.d.ts +8 -0
  45. package/dist/layout/scales/buildScale.js +43 -0
  46. package/dist/layout/styles/StylesLayout.svelte +75 -0
  47. package/dist/layout/styles/StylesLayout.svelte.d.ts +15 -0
  48. package/dist/{plots/geo → layout/styles}/TileLayer.svelte +2 -2
  49. package/dist/{plots/geo → layout/styles}/TileLayer.svelte.d.ts +1 -1
  50. package/dist/layout/timeline/Timeline.svelte +1 -1
  51. package/dist/layout/timeline/Timeline.svelte.d.ts +1 -1
  52. package/dist/layout/timeline/TimelineLayout.svelte +1 -1
  53. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
  54. package/dist/layout/tooltip/Tooltip.svelte +16 -20
  55. package/dist/layout/tooltip/Tooltip.svelte.d.ts +5 -0
  56. package/dist/layout/tooltip/TooltipLayout.svelte +7 -1
  57. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +4 -0
  58. package/dist/layout/tooltip/controller.svelte.d.ts +4 -5
  59. package/dist/layout/tooltip/controller.svelte.js +42 -16
  60. package/dist/layout/tooltip/resolveTooltipPlacement.d.ts +24 -0
  61. package/dist/layout/tooltip/resolveTooltipPlacement.js +49 -0
  62. package/dist/layout/tooltip/utils.d.ts +27 -19
  63. package/dist/layout/tooltip/utils.js +16 -15
  64. package/dist/markers/BarMarker.svelte +92 -0
  65. package/dist/markers/BarMarker.svelte.d.ts +7 -0
  66. package/dist/markers/CellMarker.svelte +21 -0
  67. package/dist/markers/CellMarker.svelte.d.ts +7 -0
  68. package/dist/markers/ContourMarker.svelte +28 -0
  69. package/dist/markers/ContourMarker.svelte.d.ts +7 -0
  70. package/dist/markers/DeltaMarker.svelte +32 -39
  71. package/dist/markers/DeltaMarker.svelte.d.ts +4 -5
  72. package/dist/markers/DotMarker.svelte +35 -6
  73. package/dist/markers/DotMarker.svelte.d.ts +11 -2
  74. package/dist/markers/GeoFeaturesMarker.svelte +19 -0
  75. package/dist/markers/GeoFeaturesMarker.svelte.d.ts +7 -0
  76. package/dist/markers/GeoHoverMarker.svelte +25 -0
  77. package/dist/markers/GeoHoverMarker.svelte.d.ts +9 -0
  78. package/dist/markers/GraticuleMarker.svelte +18 -0
  79. package/dist/markers/GraticuleMarker.svelte.d.ts +7 -0
  80. package/dist/markers/HoverMarker.svelte +34 -35
  81. package/dist/markers/HoverMarker.svelte.d.ts +15 -9
  82. package/dist/markers/LineMarker.svelte +19 -0
  83. package/dist/markers/LineMarker.svelte.d.ts +7 -0
  84. package/dist/markers/LinkMarker.svelte +42 -0
  85. package/dist/markers/LinkMarker.svelte.d.ts +7 -0
  86. package/dist/markers/RuleMarker.svelte +24 -0
  87. package/dist/markers/RuleMarker.svelte.d.ts +10 -0
  88. package/dist/markers/SphereMarker.svelte +15 -0
  89. package/dist/markers/SphereMarker.svelte.d.ts +7 -0
  90. package/dist/markers/TextMarker.svelte +1 -1
  91. package/dist/markers/TextMarker.svelte.d.ts +1 -1
  92. package/dist/markers/VectorMarker.svelte +20 -0
  93. package/dist/markers/VectorMarker.svelte.d.ts +7 -0
  94. package/dist/markers/inset/InsetMarker.svelte +38 -45
  95. package/dist/markers/inset/InsetMarker.svelte.d.ts +6 -2
  96. package/dist/plots/bar/Plot.svelte +96 -87
  97. package/dist/plots/bar/Plot.svelte.d.ts +4 -5
  98. package/dist/plots/bar/ValueLabels.svelte +70 -24
  99. package/dist/plots/bar/ValueLabels.svelte.d.ts +11 -2
  100. package/dist/plots/bar/buildBarMarkers.d.ts +30 -0
  101. package/dist/plots/bar/buildBarMarkers.js +100 -0
  102. package/dist/plots/bar/layout.svelte.d.ts +1 -2
  103. package/dist/plots/bar/layout.svelte.js +1 -1
  104. package/dist/plots/geo/Plot.svelte +113 -387
  105. package/dist/plots/geo/Plot.svelte.d.ts +5 -4
  106. package/dist/plots/geo/buildGeoMarkers.d.ts +33 -0
  107. package/dist/plots/geo/buildGeoMarkers.js +47 -0
  108. package/dist/plots/heatmap/Plot.svelte +27 -52
  109. package/dist/plots/heatmap/Plot.svelte.d.ts +4 -5
  110. package/dist/plots/heatmap/ValueLabels.svelte +5 -5
  111. package/dist/plots/heatmap/ValueLabels.svelte.d.ts +3 -3
  112. package/dist/plots/heatmap/buildCellMarkers.d.ts +21 -0
  113. package/dist/plots/heatmap/buildCellMarkers.js +25 -0
  114. package/dist/plots/line/Plot.svelte +73 -141
  115. package/dist/plots/line/Plot.svelte.d.ts +4 -4
  116. package/dist/plots/line/ValueLabels.svelte +84 -85
  117. package/dist/plots/line/ValueLabels.svelte.d.ts +10 -1
  118. package/dist/plots/line/buildLineMarkers.d.ts +15 -0
  119. package/dist/plots/line/buildLineMarkers.js +57 -0
  120. package/dist/plots/pyramid/Plot.svelte +80 -121
  121. package/dist/plots/pyramid/Plot.svelte.d.ts +4 -5
  122. package/dist/plots/pyramid/ValueLabels.svelte +71 -25
  123. package/dist/plots/pyramid/ValueLabels.svelte.d.ts +11 -2
  124. package/dist/plots/pyramid/buildPyramidBarMarkers.d.ts +19 -0
  125. package/dist/plots/pyramid/buildPyramidBarMarkers.js +32 -0
  126. package/dist/plots/scatter/Plot.svelte +242 -0
  127. package/dist/{layout/plot/GridLayout.svelte.d.ts → plots/scatter/Plot.svelte.d.ts} +9 -7
  128. package/dist/plots/scatter/ValueLabels.svelte +149 -0
  129. package/dist/plots/{bar/BarSegments.svelte.d.ts → scatter/ValueLabels.svelte.d.ts} +17 -15
  130. package/dist/plots/scatter/buildScatterMarkers.d.ts +11 -0
  131. package/dist/plots/scatter/buildScatterMarkers.js +37 -0
  132. package/dist/plots/scatter/resolveRadiusPaddedDomain.d.ts +10 -0
  133. package/dist/plots/scatter/resolveRadiusPaddedDomain.js +23 -0
  134. package/dist/plots/utils/declutter.d.ts +55 -37
  135. package/dist/plots/utils/declutter.js +79 -64
  136. package/dist/plots/utils/geoAccessors.d.ts +1 -1
  137. package/dist/plots/utils/geoAccessors.js +1 -1
  138. package/dist/plots/utils/labelOverflow.svelte.d.ts +51 -0
  139. package/dist/plots/utils/labelOverflow.svelte.js +116 -0
  140. package/dist/plots/utils/legendDisabled.d.ts +1 -1
  141. package/dist/plots/utils/segments.d.ts +17 -10
  142. package/dist/plots/utils/segments.js +26 -16
  143. package/dist/types/charts/common.d.ts +3 -50
  144. package/dist/types/charts/props.d.ts +8 -8
  145. package/dist/types/configuration/styling.d.ts +26 -2
  146. package/dist/types/{plots/scales/geo.d.ts → layout/coordinates.d.ts} +33 -1
  147. package/dist/types/layout/facet.d.ts +41 -0
  148. package/dist/types/layout/geometry.d.ts +17 -0
  149. package/dist/types/layout/legend.d.ts +65 -1
  150. package/dist/types/{plots/axis.d.ts → layout/scales.d.ts} +11 -0
  151. package/dist/types/layout/styles.d.ts +115 -0
  152. package/dist/types/layout/timeline.d.ts +32 -0
  153. package/dist/types/layout/tooltip.d.ts +20 -1
  154. package/dist/types/markers/all.d.ts +11 -0
  155. package/dist/types/markers/all.js +1 -0
  156. package/dist/types/markers/base.d.ts +45 -0
  157. package/dist/types/markers/base.js +1 -0
  158. package/dist/types/markers/common.d.ts +64 -16
  159. package/dist/types/markers/geo.d.ts +75 -100
  160. package/dist/types/markers/line.d.ts +34 -0
  161. package/dist/types/markers/line.js +1 -0
  162. package/dist/types/markers/props.d.ts +1 -1
  163. package/dist/types/plots/data/common.d.ts +1 -1
  164. package/dist/types/plots/data/scatter.d.ts +13 -0
  165. package/dist/types/plots/data/scatter.js +1 -0
  166. package/dist/types/plots/props.d.ts +46 -74
  167. package/dist/types/plots/segments/common.d.ts +6 -12
  168. package/dist/types/plots/segments/config.d.ts +1 -1
  169. package/dist/types/plots/styling.d.ts +10 -0
  170. package/dist/{plots/utils → utils}/delta.d.ts +2 -2
  171. package/dist/{plots/utils → utils}/delta.js +1 -1
  172. package/dist/utils/edgeAwareAnchor.d.ts +37 -0
  173. package/dist/utils/edgeAwareAnchor.js +65 -0
  174. package/dist/utils/grouping.d.ts +1 -1
  175. package/dist/utils/interpolate.d.ts +2 -2
  176. package/package.json +3 -1
  177. package/dist/layout/geo/GeoLayout.svelte +0 -51
  178. package/dist/layout/geo/GeoLayout.svelte.d.ts +0 -24
  179. package/dist/layout/geo/resolveProjection.js +0 -48
  180. package/dist/layout/plot/GridLayout.svelte +0 -30
  181. package/dist/layout/plot/RuleLayout.svelte +0 -111
  182. package/dist/plots/bar/BarSegments.svelte +0 -69
  183. package/dist/types/charts/legend.d.ts +0 -60
  184. package/dist/types/plots/delta.d.ts +0 -19
  185. package/dist/types/plots/styles/common.d.ts +0 -52
  186. package/dist/types/plots/styles/geo.d.ts +0 -42
  187. /package/dist/{plots/geo → layout/coordinates}/projections/argentina.js +0 -0
  188. /package/dist/{plots/geo → layout/coordinates}/projections/fit.d.ts +0 -0
  189. /package/dist/{plots/geo → layout/coordinates}/projections/fit.js +0 -0
  190. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.d.ts +0 -0
  191. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.js +0 -0
  192. /package/dist/{plots/geo → layout/coordinates}/zoom.svelte.js +0 -0
  193. /package/dist/types/{charts/legend.js → layout/coordinates.js} +0 -0
  194. /package/dist/types/{plots/axis.js → layout/facet.js} +0 -0
  195. /package/dist/types/{plots/delta.js → layout/geometry.js} +0 -0
  196. /package/dist/types/{plots/scales/geo.js → layout/scales.js} +0 -0
  197. /package/dist/types/{plots/styles/common.js → layout/styles.js} +0 -0
  198. /package/dist/types/{plots/styles/geo.js → layout/timeline.js} +0 -0
@@ -1,16 +1,13 @@
1
1
  <script lang="ts" generics="TData extends Record<string, unknown>">
2
- import { AxisX, AxisY } from 'svelteplot';
2
+ import { AxisX, AxisY, GridX, GridY } from 'svelteplot';
3
3
  import { getConfiguration } from '../../configuration/config.svelte';
4
- import type { AxisValue } from '../../types/plots/axis';
5
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
6
5
 
7
6
  /**
8
- * Axis ticks/titles for the plot kinds that have an x/y axis pair
9
- * (line/bar/heatmap/pyramid) reads the scale/tick config off `scales`,
10
- * against the plot's own resolved margins. Not used by `GeoPlot`, whose
11
- * position comes from a `d3-geo` projection instead (see `GeoLayout`).
12
- * `AxisX`/`AxisY` themselves inherit their actual scale from svelteplot's
13
- * `<Plot>` context — this only supplies their cosmetic/tick-count props.
7
+ * Axis ticks/titles and grid lines for plot kinds with an x/y axis pair
8
+ * (line/bar/heatmap/pyramid). Not used by `GeoPlot` (a map has no x/y axis
9
+ * pair). The scale itself is resolved by `buildScale` and applied via
10
+ * `BasePlotLayout`'s `<Plot x y>` this only renders the cosmetics.
14
11
  */
15
12
  let {
16
13
  data,
@@ -35,13 +32,7 @@
35
32
  const xValues = $derived(data.map(getX));
36
33
  const yValues = $derived(data.map(getY));
37
34
 
38
- /**
39
- * Caps the requested tick count to the axis's own number of distinct
40
- * values, but never above what the pixel width would naturally request —
41
- * so a narrow domain (e.g. 5 whole years) never gets subdivided into
42
- * fractional ticks (2020, 2020.5, 2021, …) just to fill the space. Skipped
43
- * whenever the caller already controls tick density directly.
44
- */
35
+ /** Caps tick count to the axis's distinct value count, never above what fits the pixel span. Skipped once the caller sets tick density directly. */
45
36
  function autoTickCount(scale: AxisBasedScalesConfig<TData>['x'], values: AxisValue[], span: number, spacing: number, min: number): number | undefined {
46
37
  if (scale?.ticks != null || scale?.tickSpacing != null || scale?.interval != null) return undefined;
47
38
  const uniqueCount = new Set(values.map(String)).size;
@@ -93,3 +84,19 @@
93
84
  opacity="0.8"
94
85
  >{verticalYLabel.text}</text>
95
86
  {/if}
87
+ {#if scales?.x?.grid}
88
+ <GridX
89
+ stroke={typeof scales.x.grid === 'object' ? scales.x.grid.stroke : undefined}
90
+ strokeWidth={typeof scales.x.grid === 'object' ? scales.x.grid.strokeWidth : undefined}
91
+ strokeOpacity={typeof scales.x.grid === 'object' ? scales.x.grid.strokeOpacity : undefined}
92
+ strokeDasharray={typeof scales.x.grid === 'object' ? scales.x.grid.strokeDasharray : undefined}
93
+ />
94
+ {/if}
95
+ {#if scales?.y?.grid}
96
+ <GridY
97
+ stroke={typeof scales.y.grid === 'object' ? scales.y.grid.stroke : undefined}
98
+ strokeWidth={typeof scales.y.grid === 'object' ? scales.y.grid.strokeWidth : undefined}
99
+ strokeOpacity={typeof scales.y.grid === 'object' ? scales.y.grid.strokeOpacity : undefined}
100
+ strokeDasharray={typeof scales.y.grid === 'object' ? scales.y.grid.strokeDasharray : undefined}
101
+ />
102
+ {/if}
@@ -1,5 +1,4 @@
1
- import type { AxisValue } from '../../types/plots/axis';
2
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
1
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
3
2
  declare function $$render<TData extends Record<string, unknown>>(): {
4
3
  props: {
5
4
  data: TData[];
@@ -34,6 +33,6 @@ interface $$IsomorphicComponent {
34
33
  <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
35
34
  z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
36
35
  }
37
- declare const AxisLayout: $$IsomorphicComponent;
38
- type AxisLayout<TData extends Record<string, unknown>> = InstanceType<typeof AxisLayout<TData>>;
39
- export default AxisLayout;
36
+ declare const ScalesLayout: $$IsomorphicComponent;
37
+ type ScalesLayout<TData extends Record<string, unknown>> = InstanceType<typeof ScalesLayout<TData>>;
38
+ export default ScalesLayout;
@@ -0,0 +1,8 @@
1
+ import type { AxisValue, AxisScale } from '../../types/layout/scales';
2
+ /**
3
+ * Resolves an author-facing {@link AxisScale} into what svelteplot's
4
+ * `<Plot x>`/`<Plot y>` accepts — strips `grid`/`seriesLayout`/
5
+ * `seriesPadding`/a vertical `label`, since `ScalesLayout` renders those
6
+ * itself.
7
+ */
8
+ export declare function buildScale<TData>(scale: AxisScale<TData> | undefined, rows: TData[], getAxisValue: (d: TData) => AxisValue): AxisScale<TData> | undefined;
@@ -0,0 +1,43 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ /** One representative row per unique axis value (first occurrence in `rows`). */
3
+ function uniqueRows(rows, getAxisValue) {
4
+ const seen = new SvelteSet();
5
+ const out = [];
6
+ for (const row of rows) {
7
+ const v = getAxisValue(row);
8
+ if (seen.has(v))
9
+ continue;
10
+ seen.add(v);
11
+ out.push(row);
12
+ }
13
+ return out;
14
+ }
15
+ /** svelteplot's `sort` option only does a fixed ascending sort; a custom `scale.sort` comparator is resolved into an explicit `domain` instead, which svelteplot honors as-is. */
16
+ function sortedScale(scale, rows, getAxisValue) {
17
+ const sort = scale?.sort;
18
+ if (!sort || scale?.domain)
19
+ return scale;
20
+ const reps = uniqueRows(rows, getAxisValue);
21
+ if (reps.length === 0 || !reps.every((r) => typeof getAxisValue(r) === 'string'))
22
+ return scale;
23
+ const domain = [...reps].sort(sort).map(getAxisValue);
24
+ return { ...scale, domain };
25
+ }
26
+ /**
27
+ * Resolves an author-facing {@link AxisScale} into what svelteplot's
28
+ * `<Plot x>`/`<Plot y>` accepts — strips `grid`/`seriesLayout`/
29
+ * `seriesPadding`/a vertical `label`, since `ScalesLayout` renders those
30
+ * itself.
31
+ */
32
+ export function buildScale(scale, rows, getAxisValue) {
33
+ const sorted = sortedScale(scale, rows, getAxisValue);
34
+ if (!sorted)
35
+ return sorted;
36
+ const out = { ...sorted };
37
+ delete out.grid;
38
+ delete out.seriesLayout;
39
+ delete out.seriesPadding;
40
+ if (scale?.labelOrientation === 'vertical')
41
+ delete out.label;
42
+ return out;
43
+ }
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { Frame } from 'svelteplot';
3
+ import TileLayer from './TileLayer.svelte';
4
+ import { geometryLonLatBounds } from '../../plots/utils/geoAccessors';
5
+ import type { StylesLayoutConfig } from '../../types/layout/styles';
6
+
7
+ /**
8
+ * Non-data cosmetic embellishments driven by a plot kind's own `styles`
9
+ * prop. Composed by `BasePlotLayout` for every plot kind, nested inside
10
+ * `CoordinatesLayout` so embellishments pan/zoom together with whatever
11
+ * they're placed alongside: the raster basemap `tileLayer` (`GeoPlot`
12
+ * only — other kinds' `styles` simply don't declare it) and the `frame`
13
+ * border, available on every plot kind.
14
+ */
15
+ let {
16
+ styles,
17
+ width,
18
+ height,
19
+ numericMargins,
20
+ }: {
21
+ styles?: StylesLayoutConfig;
22
+ width: number;
23
+ height: number;
24
+ numericMargins: { top: number; right: number; bottom: number; left: number };
25
+ } = $props();
26
+
27
+ const frame = $derived(styles?.frame);
28
+ const frameStyle = $derived(typeof frame === 'object' ? frame : undefined);
29
+
30
+ /** Auto-fits `TileLayer` to the tile-layer feature set `GeoPlot` tacks onto `styles.tileLayer.features` — see `StylesLayoutConfig`'s own doc comment. */
31
+ const dataLonLatBounds = $derived.by((): [number, number, number, number] | null => {
32
+ const features = styles?.tileLayer?.features;
33
+ if (!features?.length) return null;
34
+ let minLon = Infinity;
35
+ let maxLon = -Infinity;
36
+ let minLat = Infinity;
37
+ let maxLat = -Infinity;
38
+ for (const f of features) {
39
+ const b = geometryLonLatBounds(f.geometry);
40
+ if (!b) continue;
41
+ if (b[0] < minLon) minLon = b[0];
42
+ if (b[2] > maxLon) maxLon = b[2];
43
+ if (b[1] < minLat) minLat = b[1];
44
+ if (b[3] > maxLat) maxLat = b[3];
45
+ }
46
+ return minLon === Infinity ? null : [minLon, minLat, maxLon, maxLat];
47
+ });
48
+ </script>
49
+
50
+ {#if styles?.tileLayer}
51
+ <TileLayer
52
+ config={styles.tileLayer}
53
+ left={numericMargins.left}
54
+ top={numericMargins.top}
55
+ width={Math.max(0, width - numericMargins.left - numericMargins.right)}
56
+ height={Math.max(0, height - numericMargins.top - numericMargins.bottom)}
57
+ {dataLonLatBounds}
58
+ />
59
+ {/if}
60
+ {#if frame}
61
+ <Frame
62
+ fill={frameStyle?.fill}
63
+ fillOpacity={frameStyle?.fillOpacity}
64
+ stroke={frameStyle?.stroke}
65
+ strokeWidth={frameStyle?.strokeWidth}
66
+ strokeOpacity={frameStyle?.strokeOpacity}
67
+ strokeDasharray={frameStyle?.strokeDasharray}
68
+ borderRadius={frameStyle?.borderRadius}
69
+ inset={frameStyle?.inset}
70
+ insetTop={frameStyle?.insetTop}
71
+ insetRight={frameStyle?.insetRight}
72
+ insetBottom={frameStyle?.insetBottom}
73
+ insetLeft={frameStyle?.insetLeft}
74
+ />
75
+ {/if}
@@ -0,0 +1,15 @@
1
+ import type { StylesLayoutConfig } from '../../types/layout/styles';
2
+ type $$ComponentProps = {
3
+ styles?: StylesLayoutConfig;
4
+ width: number;
5
+ height: number;
6
+ numericMargins: {
7
+ top: number;
8
+ right: number;
9
+ bottom: number;
10
+ left: number;
11
+ };
12
+ };
13
+ declare const StylesLayout: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type StylesLayout = ReturnType<typeof StylesLayout>;
15
+ export default StylesLayout;
@@ -7,8 +7,8 @@
7
7
  tileUrl,
8
8
  toProjectionLike,
9
9
  type StreamingProjection,
10
- } from '../utils/tiles';
11
- import type { GeoTileLayerConfig } from '../../types/plots/styles/geo';
10
+ } from '../../plots/utils/tiles';
11
+ import type { GeoTileLayerConfig } from '../../types/layout/styles';
12
12
 
13
13
  /**
14
14
  * Renders an XYZ raster tile basemap under the vector geometry. Mounted as
@@ -1,4 +1,4 @@
1
- import type { GeoTileLayerConfig } from '../../types/plots/styles/geo';
1
+ import type { GeoTileLayerConfig } from '../../types/layout/styles';
2
2
  type $$ComponentProps = {
3
3
  config: GeoTileLayerConfig;
4
4
  left: number;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { Plot, RuleX, RuleY, Text } from 'svelteplot';
3
- import type { Orientation, TimeValue } from '../../types/charts/common';
3
+ import type { Orientation, TimeValue } from '../../types/layout/timeline';
4
4
 
5
5
  /**
6
6
  * Purely visual time axis: a baseline rule plus the ordered step labels with
@@ -1,4 +1,4 @@
1
- import type { Orientation, TimeValue } from '../../types/charts/common';
1
+ import type { Orientation, TimeValue } from '../../types/layout/timeline';
2
2
  type $$ComponentProps = {
3
3
  currentValue: TimeValue;
4
4
  values: TimeValue[];
@@ -7,7 +7,7 @@
7
7
  TimeValue,
8
8
  TimelineChildSnippet,
9
9
  TimelineInterpolation,
10
- } from '../../types/charts/common';
10
+ } from '../../types/layout/timeline';
11
11
  import type { Accessor } from '../../types/plots/data/common';
12
12
 
13
13
  /**
@@ -1,4 +1,4 @@
1
- import type { Orientation, TimeValue, TimelineChildSnippet, TimelineInterpolation } from '../../types/charts/common';
1
+ import type { Orientation, TimeValue, TimelineChildSnippet, TimelineInterpolation } from '../../types/layout/timeline';
2
2
  import type { Accessor } from '../../types/plots/data/common';
3
3
  declare function $$render<TRow extends Record<string, unknown>>(): {
4
4
  props: {
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
+ import { resolveTooltipPlacement } from './resolveTooltipPlacement';
4
+ import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
3
5
 
4
6
  /**
5
7
  * Presentational tooltip positioned at `(x, y)` within a `position: relative`
@@ -7,10 +9,11 @@
7
9
  * {@link ChartContainer} stamps from the config, so it themes for free. Plots
8
10
  * that implement hovering can render this with their own content snippet.
9
11
  *
10
- * Placement is boundary-aware: it prefers sitting centered above the point,
11
- * but flips below when it would overflow the top and shifts horizontally
12
- * when it would overflow the left/right, so it always stays clear of the
13
- * `bounds` box (typically the plot's own width/height).
12
+ * Placement is boundary-aware: it prefers sitting `anchorX`/`anchorY` of the
13
+ * point (default: to the right and below clear of the cursor so the
14
+ * tooltip never covers what's being hovered), falling back toward the
15
+ * opposite side (via the centered position) when the preferred side would
16
+ * overflow the `bounds` box — see `resolveTooltipPlacement`.
14
17
  */
15
18
  let {
16
19
  x,
@@ -20,6 +23,8 @@
20
23
  color,
21
24
  bounds,
22
25
  plain = false,
26
+ anchorX = 'right',
27
+ anchorY = 'bottom',
23
28
  children,
24
29
  }: {
25
30
  x: number;
@@ -36,28 +41,19 @@
36
41
  * so a custom card can match its own corners via `--dct-tooltip-radius`.
37
42
  */
38
43
  plain?: boolean;
44
+ /** Preferred horizontal anchor relative to the cursor. Defaults to `'right'`. */
45
+ anchorX?: TooltipAnchorX;
46
+ /** Preferred vertical anchor relative to the cursor. Defaults to `'bottom'`. */
47
+ anchorY?: TooltipAnchorY;
39
48
  children: Snippet;
40
49
  } = $props();
41
50
 
42
- const GAP = 10;
43
- const EDGE_MARGIN = 6;
44
-
45
51
  let boxWidth = $state(0);
46
52
  let boxHeight = $state(0);
47
53
 
48
- const placement = $derived.by(() => {
49
- const boundsWidth = bounds?.width ?? Infinity;
50
- const boundsHeight = bounds?.height ?? Infinity;
51
-
52
- let top = y - GAP - boxHeight;
53
- if (top < EDGE_MARGIN) top = y + GAP;
54
- top = Math.min(Math.max(top, EDGE_MARGIN), Math.max(EDGE_MARGIN, boundsHeight - boxHeight - EDGE_MARGIN));
55
-
56
- let left = x - boxWidth / 2;
57
- left = Math.min(Math.max(left, EDGE_MARGIN), Math.max(EDGE_MARGIN, boundsWidth - boxWidth - EDGE_MARGIN));
58
-
59
- return { left, top };
60
- });
54
+ const placement = $derived(
55
+ resolveTooltipPlacement({ x, y, boxWidth, boxHeight, bounds, anchorX, anchorY }),
56
+ );
61
57
  </script>
62
58
 
63
59
  {#if visible}
@@ -1,4 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
2
3
  type $$ComponentProps = {
3
4
  x: number;
4
5
  y: number;
@@ -17,6 +18,10 @@ type $$ComponentProps = {
17
18
  * so a custom card can match its own corners via `--dct-tooltip-radius`.
18
19
  */
19
20
  plain?: boolean;
21
+ /** Preferred horizontal anchor relative to the cursor. Defaults to `'right'`. */
22
+ anchorX?: TooltipAnchorX;
23
+ /** Preferred vertical anchor relative to the cursor. Defaults to `'bottom'`. */
24
+ anchorY?: TooltipAnchorY;
20
25
  children: Snippet;
21
26
  };
22
27
  declare const Tooltip: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts" generics="TRow extends Record<string, unknown>">
2
2
  import type { Snippet } from 'svelte';
3
3
  import Tooltip from './Tooltip.svelte';
4
+ import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
4
5
 
5
6
  /**
6
7
  * The "resolve + render" step every hoverable plot repeats verbatim:
@@ -20,6 +21,8 @@
20
21
  rows,
21
22
  format,
22
23
  content,
24
+ anchorX,
25
+ anchorY,
23
26
  }: {
24
27
  visible: boolean;
25
28
  x: number;
@@ -31,11 +34,14 @@
31
34
  format: (row: TRow) => string;
32
35
  /** Custom tooltip body; replaces the joined `format` text when given. */
33
36
  content?: Snippet<[{ rows: TRow[] }]>;
37
+ /** Preferred anchor — forwarded to `Tooltip` as-is; `undefined` falls through to its own system default. */
38
+ anchorX?: TooltipAnchorX;
39
+ anchorY?: TooltipAnchorY;
34
40
  } = $props();
35
41
  </script>
36
42
 
37
43
  {#if visible}
38
- <Tooltip {x} {y} {bounds} plain={!!content}>
44
+ <Tooltip {x} {y} {bounds} plain={!!content} {anchorX} {anchorY}>
39
45
  {#if content}
40
46
  {@render content({ rows })}
41
47
  {:else}
@@ -1,4 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
2
3
  declare function $$render<TRow extends Record<string, unknown>>(): {
3
4
  props: {
4
5
  visible: boolean;
@@ -16,6 +17,9 @@ declare function $$render<TRow extends Record<string, unknown>>(): {
16
17
  content?: Snippet<[{
17
18
  rows: TRow[];
18
19
  }]>;
20
+ /** Preferred anchor — forwarded to `Tooltip` as-is; `undefined` falls through to its own system default. */
21
+ anchorX?: TooltipAnchorX;
22
+ anchorY?: TooltipAnchorY;
19
23
  };
20
24
  exports: {};
21
25
  bindings: "";
@@ -1,5 +1,5 @@
1
1
  import type { HoverStore, HoverPoint } from '../../types/layout/tooltip';
2
- import type { AxisValue } from '../../types/plots/axis';
2
+ import type { AxisValue } from '../../types/layout/scales';
3
3
  /**
4
4
  * The plot-agnostic hover state machine shared by every plot. Given reactive
5
5
  * accessors for the current hover store, this facet's id, and the local points,
@@ -38,10 +38,9 @@ export declare function createHoverController<T extends Record<string, unknown>>
38
38
  row: T;
39
39
  }[];
40
40
  readonly matchedRows: HoverPoint<T>[];
41
- readonly ruleX: AxisValue | null;
42
- readonly ruleY: AxisValue | null;
43
- readonly impliesRuleX: boolean;
44
- readonly impliesRuleY: boolean;
41
+ readonly activePoint: HoverPoint<T> | null;
42
+ readonly impliesCrosshairX: boolean;
43
+ readonly impliesCrosshairY: boolean;
45
44
  readonly showTooltip: boolean;
46
45
  readonly tooltipPosition: {
47
46
  x: number;
@@ -1,4 +1,4 @@
1
- import { eq, matchByStrategy, impliesRuleX, impliesRuleY, filterBySeries, } from './utils';
1
+ import { eq, matchByStrategy, impliesCrosshairX, impliesCrosshairY, filterBySeries, } from './utils';
2
2
  /**
3
3
  * The plot-agnostic hover state machine shared by every plot. Given reactive
4
4
  * accessors for the current hover store, this facet's id, and the local points,
@@ -35,13 +35,42 @@ export function createHoverController(args) {
35
35
  // resolve a semantic value off the source row directly — e.g. a bar
36
36
  // plot's category, regardless of which visual axis it's drawn on.
37
37
  const highlight = $derived(matched.map(({ x, y, label, series, color, dx, dy, row }) => ({ x, y, label, series, color, dx, dy, row })));
38
- // Raw hover position — available whenever there are matched points.
39
- // BasePlotLayout gates actual display with impliesRuleX/Y + the marker's ruleX/Y props.
40
- const ruleX = $derived(matched.length > 0 ? args.hover().activeX : null);
41
- const ruleY = $derived(matched.length > 0 ? args.hover().activeY : null);
38
+ /**
39
+ * The specific matched point that was actually hovered found by the
40
+ * shared store's `activeSeries` (set from whichever row was hit), falling
41
+ * back to the first match when there's no series concept (punctual
42
+ * matches) or nothing tags a series. Consumers read `.x`/`.y` off this
43
+ * for the crosshair rule position — every point in `matched` already
44
+ * shares the "implied" axis's value by construction of `matchByStrategy`,
45
+ * and picking the actively-hovered one is what makes forcing a rule on
46
+ * the *other* axis (e.g. `ruleY: true` under an `'x'`-strategy chart)
47
+ * point at the row under the cursor rather than an arbitrary matched
48
+ * sibling. `BasePlotLayout` gates actual display with
49
+ * `impliesCrosshairX`/`Y` + the marker's own `ruleX`/`ruleY` props.
50
+ *
51
+ * `activeSeries` alone can't disambiguate two matched rows that share the
52
+ * same (or absent) series tag — e.g. an ungrouped bar/line chart where two
53
+ * categories tie on the matched axis value. `onPointer` sets `activeX`/
54
+ * `activeY` straight from the hit row, so when more than one series-tied
55
+ * candidate remains, narrowing to the one whose own x/y equals the hit
56
+ * row's picks the actual row under the cursor instead of an arbitrary tie.
57
+ */
58
+ const activePoint = $derived.by(() => {
59
+ if (matched.length === 0)
60
+ return null;
61
+ const h = args.hover();
62
+ const bySeries = matched.filter((p) => p.series === h.activeSeries);
63
+ const candidates = bySeries.length > 0 ? bySeries : matched;
64
+ if (candidates.length === 1)
65
+ return candidates[0];
66
+ const { activeX, activeY } = h;
67
+ if (activeX == null || activeY == null)
68
+ return candidates[0];
69
+ return candidates.find((p) => eq(p.x, activeX) && eq(p.y, activeY)) ?? candidates[0];
70
+ });
42
71
  // Whether the current strategy implies a crosshair rule on each axis by default.
43
- const _impliesRuleX = $derived(impliesRuleX(args.hover().strategy));
44
- const _impliesRuleY = $derived(impliesRuleY(args.hover().strategy));
72
+ const _impliesCrosshairX = $derived(impliesCrosshairX(args.hover().strategy));
73
+ const _impliesCrosshairY = $derived(impliesCrosshairY(args.hover().strategy));
45
74
  const showTooltip = $derived.by(() => {
46
75
  if (matchedRows.length === 0)
47
76
  return false;
@@ -102,17 +131,14 @@ export function createHoverController(args) {
102
131
  get matchedRows() {
103
132
  return matchedRows;
104
133
  },
105
- get ruleX() {
106
- return ruleX;
107
- },
108
- get ruleY() {
109
- return ruleY;
134
+ get activePoint() {
135
+ return activePoint;
110
136
  },
111
- get impliesRuleX() {
112
- return _impliesRuleX;
137
+ get impliesCrosshairX() {
138
+ return _impliesCrosshairX;
113
139
  },
114
- get impliesRuleY() {
115
- return _impliesRuleY;
140
+ get impliesCrosshairY() {
141
+ return _impliesCrosshairY;
116
142
  },
117
143
  get showTooltip() {
118
144
  return showTooltip;
@@ -0,0 +1,24 @@
1
+ import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
2
+ /**
3
+ * Resolves a floating tooltip's top-left pixel position relative to a cursor
4
+ * point, honoring a preferred anchor per axis and falling back toward the
5
+ * opposite side (via the centered position) when the preferred side would
6
+ * push it past `bounds`. Used by `Tooltip.svelte` for both the default box
7
+ * and any custom `content` snippet — both go through the same placement.
8
+ */
9
+ export declare function resolveTooltipPlacement(args: {
10
+ x: number;
11
+ y: number;
12
+ boxWidth: number;
13
+ boxHeight: number;
14
+ /** Box the tooltip must stay within — usually the hosting plot's width/height. */
15
+ bounds?: {
16
+ width: number;
17
+ height: number;
18
+ };
19
+ anchorX: TooltipAnchorX;
20
+ anchorY: TooltipAnchorY;
21
+ }): {
22
+ left: number;
23
+ top: number;
24
+ };
@@ -0,0 +1,49 @@
1
+ const GAP = 10;
2
+ const EDGE_MARGIN = 6;
3
+ /** Anchors tried in order for a given preference — the opposite side is always the last resort. */
4
+ function candidateOrder(anchor) {
5
+ if (anchor === 'before')
6
+ return ['before', 'middle', 'after'];
7
+ if (anchor === 'after')
8
+ return ['after', 'middle', 'before'];
9
+ return ['middle'];
10
+ }
11
+ function axisStart(anchor, pos, size) {
12
+ if (anchor === 'before')
13
+ return pos - GAP - size;
14
+ if (anchor === 'after')
15
+ return pos + GAP;
16
+ return pos - size / 2;
17
+ }
18
+ function fits(start, size, boundsSize) {
19
+ return start >= EDGE_MARGIN && start + size <= boundsSize - EDGE_MARGIN;
20
+ }
21
+ /** Resolves one axis: tries the preferred anchor, then its fallbacks in order, then clamps as a last resort. */
22
+ function resolveAxis(pos, size, boundsSize, anchor) {
23
+ const order = candidateOrder(anchor);
24
+ for (const candidate of order) {
25
+ const start = axisStart(candidate, pos, size);
26
+ if (fits(start, size, boundsSize))
27
+ return start;
28
+ }
29
+ const fallbackStart = axisStart(order[order.length - 1], pos, size);
30
+ const maxStart = Math.max(EDGE_MARGIN, boundsSize - size - EDGE_MARGIN);
31
+ return Math.min(Math.max(fallbackStart, EDGE_MARGIN), maxStart);
32
+ }
33
+ /**
34
+ * Resolves a floating tooltip's top-left pixel position relative to a cursor
35
+ * point, honoring a preferred anchor per axis and falling back toward the
36
+ * opposite side (via the centered position) when the preferred side would
37
+ * push it past `bounds`. Used by `Tooltip.svelte` for both the default box
38
+ * and any custom `content` snippet — both go through the same placement.
39
+ */
40
+ export function resolveTooltipPlacement(args) {
41
+ const boundsWidth = args.bounds?.width ?? Infinity;
42
+ const boundsHeight = args.bounds?.height ?? Infinity;
43
+ const xAnchor = args.anchorX === 'right' ? 'after' : args.anchorX === 'left' ? 'before' : 'middle';
44
+ const yAnchor = args.anchorY === 'bottom' ? 'after' : args.anchorY === 'top' ? 'before' : 'middle';
45
+ return {
46
+ left: resolveAxis(args.x, args.boxWidth, boundsWidth, xAnchor),
47
+ top: resolveAxis(args.y, args.boxHeight, boundsHeight, yAnchor),
48
+ };
49
+ }