@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,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,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,28 @@ 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
+ const activePoint = $derived.by(() => {
52
+ if (matched.length === 0)
53
+ return null;
54
+ const series = args.hover().activeSeries;
55
+ return matched.find((p) => p.series === series) ?? matched[0];
56
+ });
42
57
  // 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));
58
+ const _impliesCrosshairX = $derived(impliesCrosshairX(args.hover().strategy));
59
+ const _impliesCrosshairY = $derived(impliesCrosshairY(args.hover().strategy));
45
60
  const showTooltip = $derived.by(() => {
46
61
  if (matchedRows.length === 0)
47
62
  return false;
@@ -102,17 +117,14 @@ export function createHoverController(args) {
102
117
  get matchedRows() {
103
118
  return matchedRows;
104
119
  },
105
- get ruleX() {
106
- return ruleX;
107
- },
108
- get ruleY() {
109
- return ruleY;
120
+ get activePoint() {
121
+ return activePoint;
110
122
  },
111
- get impliesRuleX() {
112
- return _impliesRuleX;
123
+ get impliesCrosshairX() {
124
+ return _impliesCrosshairX;
113
125
  },
114
- get impliesRuleY() {
115
- return _impliesRuleY;
126
+ get impliesCrosshairY() {
127
+ return _impliesCrosshairY;
116
128
  },
117
129
  get showTooltip() {
118
130
  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
+ }
@@ -1,6 +1,5 @@
1
- import type { AxisValue } from '../../types/plots/axis';
2
- import type { Marker, HoverMarkerConfig } from '../../types/markers/common';
3
- import type { HoverStore, HoverStrategy, HoverPoint, TooltipProp, TooltipOptions } from '../../types/layout/tooltip';
1
+ import type { AxisValue } from '../../types/layout/scales';
2
+ import type { HoverStore, HoverStrategy, HoverPoint, TooltipProp, TooltipOptions, TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
4
3
  /** Value equality that also handles `Date` (compared by timestamp). */
5
4
  export declare function eq(a: AxisValue, b: AxisValue): boolean;
6
5
  /**
@@ -9,9 +8,9 @@ export declare function eq(a: AxisValue, b: AxisValue): boolean;
9
8
  */
10
9
  export declare function matchByStrategy<T>(points: HoverPoint<T>[], hover: HoverStore): HoverPoint<T>[];
11
10
  /** Whether the strategy implies an x crosshair rule by default. */
12
- export declare function impliesRuleX(strategy: HoverStrategy): boolean;
11
+ export declare function impliesCrosshairX(strategy: HoverStrategy): boolean;
13
12
  /** Whether the strategy implies a y crosshair rule by default. */
14
- export declare function impliesRuleY(strategy: HoverStrategy): boolean;
13
+ export declare function impliesCrosshairY(strategy: HoverStrategy): boolean;
15
14
  /** Normalize the author-facing prop into a resolved object. */
16
15
  export declare function resolveTooltip<TRow extends Record<string, unknown>>(tooltip: TooltipProp<TRow> | undefined, defaultStrategy: HoverStrategy): {
17
16
  enabled: boolean;
@@ -20,22 +19,31 @@ export declare function resolveTooltip<TRow extends Record<string, unknown>>(too
20
19
  format?: (row: TRow) => string;
21
20
  series?: string[];
22
21
  content?: TooltipOptions<TRow>['content'];
22
+ anchorX?: TooltipAnchorX;
23
+ anchorY?: TooltipAnchorY;
23
24
  };
24
25
  /**
25
- * Resolve the strategy that should drive the shared hover store:
26
- * the tooltip's own `strategy` wins when tooltip text is enabled, else the first
27
- * hover marker's `strategy`, else the chart kind's default.
26
+ * Resolve the strategy that should drive the shared hover store: the
27
+ * tooltip's own `strategy` wins, else the first hover marker's `strategy`,
28
+ * else the chart kind's default. Generic over the caller's own marker union,
29
+ * like {@link findHoverMarker}.
28
30
  */
29
- export declare function resolveHoverStrategy(tooltipStrategy: HoverStrategy | undefined, markers: Marker[] | undefined, defaultStrategy: HoverStrategy): HoverStrategy;
30
- /**
31
- * Resolve whether the hover should be broadcast across facets:
32
- * the tooltip's own `sync` wins when tooltip text is enabled, else the first
33
- * hover marker's `sync`, else false.
34
- */
35
- export declare function resolveHoverSync(tooltipSync: boolean | undefined, markers: Marker[] | undefined): boolean;
36
- /** Whether any marker in the list is a hover marker. */
37
- export declare function hasHoverMarker(markers: Marker[] | undefined): boolean;
38
- /** First hover marker in the list, if any. */
39
- export declare function findHoverMarker(markers: Marker[] | undefined): HoverMarkerConfig | undefined;
31
+ export declare function resolveHoverStrategy<T extends {
32
+ type: string;
33
+ }>(tooltipStrategy: HoverStrategy | undefined, markers: T[] | undefined, defaultStrategy: HoverStrategy): HoverStrategy;
34
+ /** Resolve whether the hover should be broadcast across facets. Generic see {@link resolveHoverStrategy}. */
35
+ export declare function resolveHoverSync<T extends {
36
+ type: string;
37
+ }>(tooltipSync: boolean | undefined, markers: T[] | undefined): boolean;
38
+ /** Whether any marker in the list is a hover marker — axis-based (`'hover'`) or geo (`'geo-hover'`). */
39
+ export declare function hasHoverMarker(markers: {
40
+ type: string;
41
+ }[] | undefined): boolean;
42
+ /** First hover marker in the list, if any — generic over the caller's own marker union. */
43
+ export declare function findHoverMarker<T extends {
44
+ type: string;
45
+ }>(markers: T[] | undefined): Extract<T, {
46
+ type: 'hover';
47
+ }> | undefined;
40
48
  /** Narrow matched points down to a named subset of series (no-op if `series` is empty/undefined). */
41
49
  export declare function filterBySeries<T>(points: HoverPoint<T>[], series?: string[]): HoverPoint<T>[];