@fundar/data-chart-telling 0.0.25 → 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 +2 -0
  10. package/dist/configuration/themes/index.d.ts +48 -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 +29 -52
  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 +55 -37
  116. package/dist/plots/utils/declutter.js +79 -64
  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 +7 -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
@@ -34,14 +34,13 @@
34
34
  * that side, so a plain 50/50 blend would still ask the up-pass and
35
35
  * down-pass to pull the block symmetrically toward *both* edges before
36
36
  * `fitRunsWithinBounds` shifts/shrinks it back — the up-pass reaching for
37
- * room that was never really there. That round trip is exactly what makes
38
- * individual members travel far past their neighbours' original positions
39
- * (`laneAssignment`'s doc comment calls this out as the thing that forces an
40
- * unsafe, overlapping travel span), so a run near an edge leans toward
41
- * whichever pass already grows away from it instead the block still
42
- * expands to clear `minGap` everywhere, just mostly in the one direction
43
- * that was actually available, the way a person sliding books apart on a
44
- * shelf pinned against one wall would.
37
+ * room that was never really there, needlessly widening how far members end
38
+ * up travelling from their original positions (`laneAssignment`'s own lane
39
+ * count grows with exactly that travel distance see its doc comment) — so
40
+ * a run near an edge leans toward whichever pass already grows away from it
41
+ * instead the block still expands to clear `minGap` everywhere, just
42
+ * mostly in the one direction that was actually available, the way a person
43
+ * sliding books apart on a shelf pinned against one wall would.
45
44
  */
46
45
  export function declutter1D(items, minGap, bounds) {
47
46
  const sorted = [...items].sort((a, b) => a.pos - b.pos);
@@ -137,41 +136,60 @@ function fitRunsWithinBounds(positions, connected, [lo, hi]) {
137
136
  }
138
137
  /**
139
138
  * Groups `declutter1D`'s own `connected` flags into runs (maximal stretches
140
- * of consecutive connected pairs), then assigns each run member a lane by
141
- * *mirrored* positionthe run's top and bottom member share lane 0, the
142
- * next-in-from-top and next-in-from-bottom share lane 1, and so on toward
143
- * the run's centre (an odd-sized run's exact middle member gets the
144
- * innermost lane alone) — but only after checking that a proposed pair's
145
- * *travel spans* (`[min(original, final), max(original, final)]`, the pixel
146
- * range a leader line's vertical segment actually sweeps through) don't
147
- * overlap. A pair that would collide is split into two lanes of its own
148
- * instead of one shared one, so correctness never depends on the pairing
149
- * being right — only on this check.
139
+ * of consecutive connected pairs), then gives every run member its own,
140
+ * unique laneno two members ever share one, deliberately (see below)
141
+ * ordered so that a leader line drawn through lane `rank` never crosses, or
142
+ * even draws visually flush against, one drawn through any other lane of
143
+ * the same run.
150
144
  *
151
- * Mirrored position is still the *first* thing tried, not span-overlap
152
- * colouring picking lanes on its own, because minimizing lane count isn't
153
- * actually the goal here a run of `n` safely-non-overlapping members could
154
- * often be greedily packed into far fewer than `ceil(n / 2)` lanes, but
155
- * doing that defeats the point: it's what visually fans a pileup's leader
156
- * lines apart in the first place. Mirrored pairing keeps that fan-out
157
- * exactly where it's safe (the common case for `declutter1D`'s own output),
158
- * and only degrades never breakswhere it isn't.
145
+ * Every member's leader line is the same three-segment elbow: a horizontal
146
+ * stub from the data point out to its lane, a vertical run up/down that
147
+ * lane to the label's height, then a final horizontal run in to the shared
148
+ * label column. Two such elbows, `i` (in the tighter of the two lanes) and
149
+ * `j` (further out), are safe from crossing *specifically* when neither of
150
+ * these can happen:
151
+ * - `j`'s own stub (at `j`'s original height) reaches out past `i`'s lane
152
+ * while `i`'s vertical run passes through that height `j`'s stub would
153
+ * cut across `i`'s vertical segment.
154
+ * - `i`'s final run (at `i`'s label height) reaches past `i`'s own lane out
155
+ * to the shared column, and in doing so crosses `j`'s lane while `j`'s
156
+ * vertical run passes through *that* height — `i`'s own final segment
157
+ * would cut across `j`'s vertical segment.
159
158
  *
160
- * A run whose members still end up with genuinely overlapping travel spans
161
- * (heavy, edge-adjacent compression) has no lane assignment that's both
162
- * crossing-free *and* touch-free under this shared-`px`/shared-`labelX`
163
- * elbow geometry sharing draws two segments on top of each other, splitting
164
- * draws an actual crossing. `declutter1D`'s bounds-aware margin bias (see its
165
- * own doc comment) exists specifically to keep that from happening in the
166
- * first place, by keeping a run's displacement mostly one-directional when
167
- * it sits close to an edge, rather than papering over it here.
159
+ * Whether `i` can safely take the tighter lane depends on both members'
160
+ * *actual* pixel geometry not simply on which one moved further — so each
161
+ * pair is checked directly per {@link saferNearer} rather than inferred from
162
+ * a single sort key. A pair with a clean, provable answer either way (the
163
+ * overwhelmingly common case for `declutter1D`'s own output) is ordered by
164
+ * that answer; a pair with no clean answer (both directions equally
165
+ * "unsafe," or, symmetrically, both fine) falls back to the same *travel
166
+ * distance* heuristic used before this per-pair check existed furthest
167
+ * `|final - original|` first — which is what decides ties and keeps the
168
+ * "furthest traveller gets the tightest lane, so it starts bending
169
+ * immediately rather than reaching across everyone else's leader lines
170
+ * first" intent for every pair the geometry itself doesn't already settle.
171
+ *
172
+ * No sharing, ever, is the other half of this: two members can only have
173
+ * non-overlapping travel spans (and so pass the old, now-removed,
174
+ * share-a-lane check) by sitting far apart along the axis — but "far apart"
175
+ * is exactly the case with the most room to spare, so it never needed the
176
+ * lane it would save by sharing, while any pair close enough to actually be
177
+ * short on room has spans close enough together that sharing draws their
178
+ * two leader lines only a couple pixels apart — visually indistinguishable
179
+ * from one continuous line touching the next label down. A run of `n`
180
+ * members uses exactly `n` lanes; letting the lane count grow is the price
181
+ * of a fan that never has two leader lines touching *or* crossing, and
182
+ * `ValueLabels`' own auto-margin measurement (see its doc comment) already
183
+ * grows `margins.right` to fit however many lanes that turns out to be, so
184
+ * nothing has to be guessed up front.
168
185
  *
169
186
  * `rank` is the lane index (0-indexed) to drive a leader line's routing:
170
187
  * `rank` 0 bends right off the data point (a short first stub); higher
171
188
  * ranks travel further before bending (right up to the shared label
172
- * column). `lanesUsed` is the run's own total lane count (shared by every
173
- * member); `runSize` is the run's member count, for callers that just need
174
- * to know whether a given member belongs to a real run at all.
189
+ * column). `lanesUsed` is the run's own total lane count always equal to
190
+ * `runSize` — shared by every member; `runSize` is the run's member count,
191
+ * for callers that just need to know whether a given member belongs to a
192
+ * real run at all.
175
193
  */
176
194
  export function laneAssignment(originals, finals, connected) {
177
195
  const n = connected.length + 1;
@@ -181,7 +199,15 @@ export function laneAssignment(originals, finals, connected) {
181
199
  const b = finals[k];
182
200
  return a < b ? [a, b] : [b, a];
183
201
  };
184
- const overlaps = (a, b) => a[0] < b[1] && b[0] < a[1];
202
+ const distance = (k) => Math.abs(finals[k] - originals[k]);
203
+ /** Whether lane-`i`-nearer-than-`j` is provably crossing-free — see this function's doc comment. */
204
+ function saferNearer(i, j) {
205
+ const [loI, hiI] = span(i);
206
+ const [loJ, hiJ] = span(j);
207
+ const jStubCutsI = originals[j] >= loI && originals[j] <= hiI;
208
+ const iFinalCutsJ = finals[i] >= loJ && finals[i] <= hiJ;
209
+ return !jStubCutsI && !iFinalCutsJ;
210
+ }
185
211
  let i = 0;
186
212
  while (i < n) {
187
213
  let j = i;
@@ -189,32 +215,21 @@ export function laneAssignment(originals, finals, connected) {
189
215
  j++;
190
216
  const runSize = j - i + 1;
191
217
  if (runSize > 1) {
192
- const rankOf = new Array(runSize);
193
- let lo = 0;
194
- let hi = runSize - 1;
195
- let nextLane = 0;
196
- while (lo <= hi) {
197
- if (lo === hi) {
198
- rankOf[lo] = nextLane;
199
- nextLane += 1;
200
- }
201
- else if (!overlaps(span(i + lo), span(i + hi))) {
202
- rankOf[lo] = nextLane;
203
- rankOf[hi] = nextLane;
204
- nextLane += 1;
205
- }
206
- else {
207
- rankOf[lo] = nextLane;
208
- rankOf[hi] = nextLane + 1;
209
- nextLane += 2;
210
- }
211
- lo += 1;
212
- hi -= 1;
213
- }
214
- const lanesUsed = nextLane;
215
- for (let k = 0; k < runSize; k++) {
216
- out[i + k] = { rank: rankOf[k], lanesUsed, runSize };
217
- }
218
+ // Members of this run, ordered so each is provably safe to place
219
+ // nearer than the next whenever that's decidable, furthest-travelled
220
+ // first otherwise (ties keep their original — i.e. top-to-bottom
221
+ // order, since `sort` is stable). Position in this order *is* the
222
+ // rank: no sharing.
223
+ const order = Array.from({ length: runSize }, (_, k) => i + k).sort((a, b) => {
224
+ const aNearer = saferNearer(a, b);
225
+ const bNearer = saferNearer(b, a);
226
+ if (aNearer !== bNearer)
227
+ return aNearer ? -1 : 1;
228
+ return distance(b) - distance(a);
229
+ });
230
+ order.forEach((k, rank) => {
231
+ out[k] = { rank, lanesUsed: runSize, runSize };
232
+ });
218
233
  }
219
234
  i = j + 1;
220
235
  }
@@ -36,7 +36,7 @@ export declare function geometryPartCentroids(geometry: GeoJSON.Geometry): [numb
36
36
  * based on a per-part predicate, reassembling a geometry of the same type
37
37
  * from whatever survives — `null` if nothing does. Used to let a single
38
38
  * feature's geometry be split between the main map and an `inset` marker
39
- * (see `GeoDataProps.excludeParts`/`GeoInsetMarker.featurePartsFilter`)
39
+ * (see `GeoDataProps.excludeParts`/`GeoInsetMarkerConfig.featurePartsFilter`)
40
40
  * without preprocessing the source data: e.g. a province whose `MultiPolygon`
41
41
  * spans both a mainland part and a far-flung part can have the main map
42
42
  * exclude the latter while an inset shows only it.
@@ -102,7 +102,7 @@ export function geometryPartCentroids(geometry) {
102
102
  * based on a per-part predicate, reassembling a geometry of the same type
103
103
  * from whatever survives — `null` if nothing does. Used to let a single
104
104
  * feature's geometry be split between the main map and an `inset` marker
105
- * (see `GeoDataProps.excludeParts`/`GeoInsetMarker.featurePartsFilter`)
105
+ * (see `GeoDataProps.excludeParts`/`GeoInsetMarkerConfig.featurePartsFilter`)
106
106
  * without preprocessing the source data: e.g. a province whose `MultiPolygon`
107
107
  * spans both a mainland part and a far-flung part can have the main map
108
108
  * exclude the latter while an inset shows only it.
@@ -1,8 +1,9 @@
1
- import type { AxisValue } from '../../types/plots/axis';
1
+ import type { AxisValue } from '../../types/layout/scales';
2
2
  import type { Series } from '../../types/plots/data/common';
3
3
  import type { LineSegmentStyle } from '../../types/plots/segments/common';
4
4
  import type { Segment, VisualGroup } from '../../types/plots/segments/config';
5
5
  import type { SegmentsConfig } from '../../types/plots/props';
6
+ import type { ScopedMarkerGroup } from '../../types/layout/geometry';
6
7
  /**
7
8
  * Returns the flat {@link Segment} list for one series.
8
9
  * Series-specific segments are added before `'default'` segments so that
@@ -11,15 +12,6 @@ import type { SegmentsConfig } from '../../types/plots/props';
11
12
  export declare function resolveSegmentsForSeries<TStyle>(segments: SegmentsConfig<TStyle>, seriesName: string): Segment<TStyle>[];
12
13
  /** Returns true when `xVal` falls inside any of a segment's x ranges. */
13
14
  export declare function matchesSegmentX<TStyle>(xVal: AxisValue, seg: Segment<TStyle>): boolean;
14
- /**
15
- * For each segment in `segs`, returns the subset of `data` rows whose x value
16
- * (from `xAccessor`) falls inside any of the segment's x ranges. Rows are
17
- * not deduplicated across segments — a row that matches multiple segments
18
- * appears in each resulting array.
19
- *
20
- * Used by component and hover markers with the `'segment'` strategy.
21
- */
22
- export declare function getDataForSegments<TData, TStyle>(data: TData[], xAccessor: (d: TData) => AxisValue, segs: Segment<TStyle>[]): TData[][];
23
15
  /**
24
16
  * Warns about obvious configuration mistakes: multiple catch-all segments
25
17
  * (no `areas`) in the same series list. Since catch-alls are evaluated last
@@ -38,3 +30,18 @@ export declare function validateSegments<TStyle>(segments: SegmentsConfig<TStyle
38
30
  * aren't duplicated as an extra, separately-styled mark.
39
31
  */
40
32
  export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments: SegmentsConfig<TStyle>, connect?: boolean): VisualGroup<S, TStyle>[];
33
+ /**
34
+ * Builds the `ScopedMarkerGroup[]` a `Plot.svelte` hands to `GeometryLayout`
35
+ * for rendering `series`-scoped `hover`/`component` markers — one group per
36
+ * series, carrying its resolved segments' match predicates and colors.
37
+ * `colorFor` resolves a series' own default color; `segmentColorFor` reads
38
+ * whichever field counts as "color" out of that plot kind's segment style
39
+ * shape (e.g. `fill` vs `stroke.stroke`), since that's the only part of this
40
+ * that isn't shared across plot kinds.
41
+ */
42
+ export declare function buildScopedMarkerGroups<TData extends Record<string, unknown>, S extends Series<any>, TStyle>(args: {
43
+ groupedSeries: VisualGroup<S, TStyle>[];
44
+ segments: SegmentsConfig<TStyle>;
45
+ colorFor: (series: S, seriesIndex: number) => string;
46
+ segmentColorFor: (style: TStyle | undefined, defaultColor: string) => string;
47
+ }): ScopedMarkerGroup<TData>[];
@@ -35,22 +35,6 @@ function areaMatchesXY(xVal, yVal, area) {
35
35
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
36
  (area.y.to === undefined || yVal <= area.y.to);
37
37
  }
38
- /**
39
- * For each segment in `segs`, returns the subset of `data` rows whose x value
40
- * (from `xAccessor`) falls inside any of the segment's x ranges. Rows are
41
- * not deduplicated across segments — a row that matches multiple segments
42
- * appears in each resulting array.
43
- *
44
- * Used by component and hover markers with the `'segment'` strategy.
45
- */
46
- export function getDataForSegments(data, xAccessor, segs) {
47
- return segs.map((seg) => {
48
- const areas = seg.areas;
49
- if (!areas || areas.length === 0)
50
- return data;
51
- return data.filter((d) => areas.some((area) => areaMatchesX(xAccessor(d), area)));
52
- });
53
- }
54
38
  /**
55
39
  * Warns about obvious configuration mistakes: multiple catch-all segments
56
40
  * (no `areas`) in the same series list. Since catch-alls are evaluated last
@@ -209,3 +193,29 @@ export function buildGroupedSeries(seriesList, segments, connect = true) {
209
193
  visualSegments: resolveSeriesSegments(s, resolveSegmentsForSeries(segments, s.name), connect),
210
194
  }));
211
195
  }
196
+ /**
197
+ * Builds the `ScopedMarkerGroup[]` a `Plot.svelte` hands to `GeometryLayout`
198
+ * for rendering `series`-scoped `hover`/`component` markers — one group per
199
+ * series, carrying its resolved segments' match predicates and colors.
200
+ * `colorFor` resolves a series' own default color; `segmentColorFor` reads
201
+ * whichever field counts as "color" out of that plot kind's segment style
202
+ * shape (e.g. `fill` vs `stroke.stroke`), since that's the only part of this
203
+ * that isn't shared across plot kinds.
204
+ */
205
+ export function buildScopedMarkerGroups(args) {
206
+ const { groupedSeries, segments, colorFor, segmentColorFor } = args;
207
+ return groupedSeries.map((group, seriesIndex) => {
208
+ const defaultColor = colorFor(group.series, seriesIndex);
209
+ const xFor = resolveAccessor(group.series.x);
210
+ const segs = resolveSegmentsForSeries(segments, group.series.name);
211
+ return {
212
+ name: group.series.name,
213
+ data: group.series.data,
214
+ color: defaultColor,
215
+ segments: segs.map((seg) => ({
216
+ match: (row) => matchesSegmentX(xFor(row), seg),
217
+ color: segmentColorFor(seg.style, defaultColor),
218
+ })),
219
+ };
220
+ });
221
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { AxisValue } from '../plots/axis';
2
+ import type { AxisValue } from '../layout/scales';
3
3
  import type { Accessor } from '../plots/data/common';
4
4
  import type { EasingName, EasingFn } from './interpolate';
5
5
  export type TimeValue = number;
@@ -1,10 +1,9 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { AxisValue } from '../plots/axis';
2
+ import type { AxisValue, AxisBasedScalesConfig } from '../layout/scales';
3
3
  import type { Accessor } from '../plots/data/common';
4
- import type { AxisBasedScalesConfig } from '../plots/props';
5
- import type { BasePlotStyles } from '../plots/styles/common';
4
+ import type { BasePlotStylesConfig } from '../layout/styles';
6
5
  import type { SegmentsConfig, MarginConfig } from '../plots/props';
7
- import type { Marker } from '../markers/common';
6
+ import type { AxisBasedMarkersConfig } from '../markers/common';
8
7
  import type { TooltipProp } from '../layout/tooltip';
9
8
  import type { LegendSection } from './legend';
10
9
  import type { FacetConfig, TimelineConfig } from './common';
@@ -30,9 +29,9 @@ export type ChartProps<TRow extends Record<string, unknown>, TSegmentStyle exten
30
29
  x: Accessor<TRow, AxisValue>;
31
30
  y: Accessor<TRow, AxisValue>;
32
31
  z?: Accessor<TRow, unknown>;
33
- styles?: BasePlotStyles;
32
+ styles?: BasePlotStylesConfig;
34
33
  segments?: SegmentsConfig<TSegmentStyle>;
35
- markers?: Marker[];
34
+ markers?: AxisBasedMarkersConfig[];
36
35
  scales?: AxisBasedScalesConfig<TRow>;
37
36
  margins?: MarginConfig;
38
37
  facet?: FacetConfig<TRow>;
@@ -1,4 +1,4 @@
1
- import type { StrokeStyle, DotStyle, FontStyle } from '../plots/styling';
1
+ import type { StrokeStyle, DotStyle, FontStyle, AreaStyle } from '../plots/styling';
2
2
  export type TextStyle = {
3
3
  /** font-family */
4
4
  font: string;
@@ -101,17 +101,22 @@ export type ChartConfig = {
101
101
  connectorWidth: number;
102
102
  };
103
103
  };
104
+ /** Default scatter point geometry. */
105
+ scatter: {
106
+ dotRadius: number;
107
+ };
104
108
  /** Timeline gutter styling. */
105
109
  timeline: {
106
110
  axisColor: string;
107
111
  activeColor: string;
108
112
  thickness: number;
109
113
  };
110
- /** In-SVG hover highlight (crosshair rule, dot, and value label). */
114
+ /** In-SVG hover highlight: crosshair rule, dot + value label, and geo feature restyle. */
111
115
  hover: {
112
116
  rule: StrokeStyle;
113
117
  dot: DotStyle;
114
118
  font: FontStyle;
119
+ area: AreaStyle;
115
120
  };
116
121
  };
117
122
  /** Deep-partial input accepted by `setConfiguration`. */
@@ -1,10 +1,10 @@
1
+ import type { GeoProjection } from 'd3-geo';
1
2
  /**
2
3
  * The projection names svelteplot's `<Plot projection>` accepts as a string
3
4
  * (svelteplot itself doesn't export this as a type — defined here so authors
4
5
  * get autocomplete/type-checking over the full supported set).
5
6
  */
6
7
  export type GeoProjectionName = 'albers-usa' | 'albers' | 'azimuthal-equal-area' | 'azimuthal-equidistant' | 'conic-conformal' | 'conic-equal-area' | 'conic-equidistant' | 'equal-earth' | 'equirectangular' | 'gnomonic' | 'identity' | 'reflect-y' | 'mercator' | 'orthographic' | 'stereographic' | 'transverse-mercator';
7
- import type { GeoProjection } from 'd3-geo';
8
8
  /**
9
9
  * What a custom projection factory (e.g. `argentinaBicontinental` under
10
10
  * `$lib`'s geo projections) must return — just enough of d3's `GeoProjection`
@@ -42,3 +42,35 @@ export type GeoProjectionConfig = GeoProjectionName | {
42
42
  */
43
43
  domain?: GeoJSON.GeoJsonObject | GeoJSON.FeatureCollection | GeoJSON.Feature | 'data';
44
44
  };
45
+ /**
46
+ * `scales` prop for GeoPlot — the geo analog of `AxisBasedScalesConfig`.
47
+ * A map has no x/y/z axis pair, so this only configures the choropleth
48
+ * colour domain (the coordinate system itself is `coordinates.projection`).
49
+ */
50
+ export type GeoScalesConfig = {
51
+ /** Continuous choropleth value domain — mirrors heatmap's `scales.z.domain`. */
52
+ color?: {
53
+ domain?: [number, number];
54
+ };
55
+ };
56
+ export type GeoZoomConfig = {
57
+ min?: number;
58
+ max?: number;
59
+ initial?: {
60
+ x?: number;
61
+ y?: number;
62
+ k?: number;
63
+ };
64
+ };
65
+ /**
66
+ * `coordinates` prop for GeoPlot — the only plot kind with a coordinate
67
+ * system other than implicit cartesian. Bundles the projection (a `GoG`
68
+ * Coordinates concern) and pan/zoom (navigating within that coordinate
69
+ * system), resolved together by `CoordinatesLayout`.
70
+ */
71
+ export type CoordinatesConfig = {
72
+ /** Which map projection to draw with. Defaults to `'equal-earth'`. */
73
+ projection?: GeoProjectionConfig;
74
+ /** `true`/config enables drag-pan + scroll-zoom. Omitted/`false` = static map. */
75
+ zoom?: boolean | GeoZoomConfig;
76
+ };
@@ -0,0 +1,17 @@
1
+ /** One resolved segment for scoped-marker rendering — see {@link ScopedMarkerGroup}. */
2
+ export type ScopedMarkerSegment<TData> = {
3
+ match: (row: TData) => boolean;
4
+ color: string;
5
+ };
6
+ /**
7
+ * One series' resolved context for rendering its `series`-scoped `'hover'`/
8
+ * `'component'` markers — built by each `Plot.svelte`, consumed by
9
+ * `GeometryLayout`. Keeps `GeometryLayout` itself agnostic of each plot
10
+ * kind's segment style shape (e.g. `fill` vs `stroke.stroke`).
11
+ */
12
+ export type ScopedMarkerGroup<TData> = {
13
+ name: string;
14
+ data: TData[];
15
+ color: string;
16
+ segments: ScopedMarkerSegment<TData>[];
17
+ };
@@ -53,3 +53,14 @@ export type AxisScale<TData = Record<string, unknown>> = {
53
53
  seriesLayout?: 'overlap' | 'grouped' | 'stacked';
54
54
  seriesPadding?: number;
55
55
  };
56
+ /**
57
+ * Per-axis scale overrides, passed as a single `scales` prop to every
58
+ * axis-based plot kind (`GeoPlot` uses `GeoScalesConfig` instead — a map has
59
+ * no x/y/z axis pair). `TData` threads through to {@link AxisScale.sort} on
60
+ * each axis.
61
+ */
62
+ export type AxisBasedScalesConfig<TData = Record<string, unknown>> = {
63
+ x?: AxisScale<TData>;
64
+ y?: AxisScale<TData>;
65
+ z?: AxisScale<TData>;
66
+ };
@@ -0,0 +1,126 @@
1
+ import type { ValueAnchor } from '../plots/constants';
2
+ import type { FontStyle, StrokeStyle, AreaStyle } from '../plots/styling';
3
+ import type { GeoFeature } from '../plots/data/geo';
4
+ /**
5
+ * Controls if and how value labels are rendered on a plot. The high-level
6
+ * `anchor` sets sensible defaults for placement; `fontStyle` (`dx`, `dy`,
7
+ * `textAnchor`, `lineAnchor`, `lineHeight`, `rotate`, `class`, `textClass`,
8
+ * `fill`, …) overrides those defaults field-by-field when provided — mirrors
9
+ * {@link DeltaConfig}'s `fontStyle`/`strokeStyle` split, so label text and
10
+ * connector-line styling never mix into one flat bag of props.
11
+ */
12
+ export type ValuesStyle = {
13
+ show?: boolean;
14
+ anchor?: ValueAnchor;
15
+ /** Called with the numeric value and the series name. Return a string to render. */
16
+ format?: (value: number, seriesName: string) => string;
17
+ /** Text styling for the label itself. */
18
+ fontStyle?: FontStyle;
19
+ /**
20
+ * Style for the leader line drawn from a label displaced to clear an
21
+ * overlap with another series' label, back to its real data point.
22
+ * Labels only ever move when they'd otherwise collide — this is a no-op
23
+ * style until that happens. Falls back to a neutral grey, thin, solid
24
+ * stroke — deliberately not the series' own colour or stroke width, since
25
+ * matching the data line reads as more data rather than as a leader,
26
+ * right where several lines are already converging. Unset fields keep
27
+ * that fallback.
28
+ */
29
+ strokeStyle?: StrokeStyle;
30
+ };
31
+ /**
32
+ * Color overrides for plots that derive their palette from data.
33
+ * Each plot uses only the fields relevant to its kind; unused fields are
34
+ * silently ignored.
35
+ *
36
+ * - `min` / `max` — sequential colour ramp endpoints (heatmap).
37
+ * - `left` / `right` — diverging bar sides (pyramid).
38
+ */
39
+ export type ColorsStyle = {
40
+ min?: string;
41
+ max?: string;
42
+ left?: string;
43
+ right?: string;
44
+ };
45
+ /**
46
+ * Decorative border around the plot's own content box — svelteplot's
47
+ * `Frame` mark, purely cosmetic (drawn from the box geometry, never from
48
+ * data). `true` enables it with svelteplot's own defaults; an object
49
+ * enables it and overrides its look field-by-field. Insets pull the frame
50
+ * in from the content box's own edges, in pixels.
51
+ */
52
+ export type FrameStyle = AreaStyle & {
53
+ inset?: number;
54
+ insetTop?: number;
55
+ insetRight?: number;
56
+ insetBottom?: number;
57
+ insetLeft?: number;
58
+ };
59
+ /** Author-facing `styles.frame` prop — see {@link FrameStyle}. */
60
+ export type FrameProp = boolean | FrameStyle;
61
+ /**
62
+ * Top-level rendering style overrides shared by the non-geo plot kinds.
63
+ * Passed as the `styles` prop. Each plot reads only the fields it cares
64
+ * about.
65
+ */
66
+ export type BasePlotStylesConfig = {
67
+ values?: ValuesStyle;
68
+ colors?: ColorsStyle;
69
+ /** Decorative border around the plot's content box — see {@link FrameStyle}. Available on every plot kind. */
70
+ frame?: FrameProp;
71
+ };
72
+ export type GeoTileLayerConfig = {
73
+ /** XYZ tile URL template, e.g. `'https://tile.openstreetmap.org/{z}/{x}/{y}.png'`. */
74
+ url: string;
75
+ attribution?: string;
76
+ opacity?: number;
77
+ minZoom?: number;
78
+ maxZoom?: number;
79
+ /** Tile edge length in pixels. Defaults to `256`. */
80
+ tileSize?: number;
81
+ };
82
+ /**
83
+ * `styles` prop for GeoPlot — the geo analog of `BasePlotStylesConfig`.
84
+ * Unlike the other plot kinds, GeoPlot's styling needs (base map, choropleth
85
+ * ramp, tile background, click handling) are unlike anything x/y plots need,
86
+ * so this is its own type rather than a reuse of `BasePlotStylesConfig`.
87
+ */
88
+ export type GeoStylesConfig<TProps extends Record<string, unknown> = Record<string, unknown>> = {
89
+ /** Base map fill, used when a feature isn't styled by `segments` or the choropleth ramp. */
90
+ fill?: string;
91
+ fillOpacity?: number;
92
+ stroke?: string;
93
+ strokeWidth?: number;
94
+ /** Continuous choropleth ramp endpoints — used when `value` is provided on data. Mirrors heatmap's `styles.colors`. */
95
+ colors?: ColorsStyle;
96
+ /** Raster XYZ tile basemap under the vector geometry. Mercator-family projections only. */
97
+ tileLayer?: GeoTileLayerConfig;
98
+ /** Fired when a base-map feature is clicked. */
99
+ onFeatureClick?: (feature: GeoFeature<TProps>, event: Event) => void;
100
+ /** Decorative border around the plot's content box — see {@link FrameStyle}. Available on every plot kind. */
101
+ frame?: FrameProp;
102
+ };
103
+ /**
104
+ * `GeoTileLayerConfig`, plus `features` — never author-facing (the public
105
+ * `styles.tileLayer` shape, {@link GeoTileLayerConfig}, doesn't declare it).
106
+ * `GeoPlot` tacks it onto its own internal copy of `styles` so `StylesLayout`
107
+ * can compute the tile layer's auto-fit bounds without needing raw geometry
108
+ * threaded through as a separate prop.
109
+ */
110
+ type GeoTileLayerConfigWithFeatures = GeoTileLayerConfig & {
111
+ features?: {
112
+ geometry: GeoJSON.Geometry;
113
+ }[];
114
+ };
115
+ /**
116
+ * The minimal slice of a plot kind's own `styles` prop `StylesLayout` reads —
117
+ * every concrete `styles` type ({@link BasePlotStylesConfig},
118
+ * {@link GeoStylesConfig}) structurally satisfies this whether or not it
119
+ * declares `tileLayer` at all (a missing optional field type-checks fine).
120
+ */
121
+ export type StylesLayoutConfig = {
122
+ tileLayer?: GeoTileLayerConfigWithFeatures;
123
+ frame?: FrameProp;
124
+ [key: string]: unknown;
125
+ };
126
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { AxisValue } from '../plots/axis';
2
+ import type { AxisValue } from './scales';
3
3
  /**
4
4
  * How the hover highlight finds related points within a facet.
5
5
  * - `'x'` all points sharing the hovered x (default for line / bar).
@@ -7,6 +7,21 @@ import type { AxisValue } from '../plots/axis';
7
7
  * - `'punctual'` exact XY match — one cell at a time (default for heatmap).
8
8
  */
9
9
  export type HoverStrategy = 'x' | 'y' | 'punctual';
10
+ /**
11
+ * Preferred horizontal placement of the floating tooltip relative to the
12
+ * cursor: `'right'`/`'left'` offset it clear of the cursor (so it never
13
+ * covers the hovered point); `'middle'` centers it on the cursor's x, same
14
+ * as the plot's own vertical `'middle'`. Falls back toward the opposite
15
+ * side (via `'middle'`) when the preferred side would overflow the plot's
16
+ * bounds — see {@link resolveTooltipPlacement}. System default: `'right'`.
17
+ */
18
+ export type TooltipAnchorX = 'left' | 'middle' | 'right';
19
+ /**
20
+ * Preferred vertical placement of the floating tooltip relative to the
21
+ * cursor — same fallback behavior as {@link TooltipAnchorX}, along the other
22
+ * axis. System default: `'bottom'`.
23
+ */
24
+ export type TooltipAnchorY = 'top' | 'middle' | 'bottom';
10
25
  /** Author-facing tooltip option on the chart kinds. `true` = enable with defaults. */
11
26
  export type TooltipOptions<TRow extends Record<string, unknown>> = {
12
27
  /** Matching strategy. Defaults to the chart kind's own default. */
@@ -24,6 +39,10 @@ export type TooltipOptions<TRow extends Record<string, unknown>> = {
24
39
  content?: Snippet<[{
25
40
  rows: TRow[];
26
41
  }]>;
42
+ /** Preferred horizontal anchor — see {@link TooltipAnchorX}. */
43
+ anchorX?: TooltipAnchorX;
44
+ /** Preferred vertical anchor — see {@link TooltipAnchorY}. */
45
+ anchorY?: TooltipAnchorY;
27
46
  };
28
47
  /** Either a bare on/off flag or the full option object. */
29
48
  export type TooltipProp<TRow extends Record<string, unknown>> = boolean | TooltipOptions<TRow>;
@@ -0,0 +1,11 @@
1
+ import type { AxisBasedMarkersConfig } from './common';
2
+ import type { GeoMarkersConfig, GeoFeaturesMarkerConfig } from './geo';
3
+ import type { LineMarkersConfig } from './line';
4
+ /**
5
+ * Every marker kind `GeometryLayout`/`BasePlotLayout` can route to — the
6
+ * union of every plot kind's own `markers` prop type, plus
7
+ * `GeoFeaturesMarkerConfig` (built internally, never author-facing). Not
8
+ * used as any `Plot.svelte`'s own `TMarker` — see each kind's own precise
9
+ * type instead.
10
+ */
11
+ export type MarkerConfig = AxisBasedMarkersConfig | LineMarkersConfig | GeoMarkersConfig | GeoFeaturesMarkerConfig;