@fundar/data-chart-telling 1.18.0 → 1.20.0

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 (252) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +2 -1
  3. package/dist/configuration/config.svelte.js +1 -1
  4. package/dist/index.d.ts +4 -2
  5. package/dist/index.js +2 -1
  6. package/dist/layout/coordinates/CoordinatesLayout.svelte +3 -1
  7. package/dist/layout/facet/FacetLayout.svelte +54 -41
  8. package/dist/layout/geometry/GeometryLayout.svelte +57 -22
  9. package/dist/layout/geometry/ScaleFreeContext.d.ts +31 -0
  10. package/dist/layout/geometry/ScaleFreeContext.js +65 -0
  11. package/dist/layout/geometry/ScaleFreeMarks.svelte +14 -0
  12. package/dist/layout/geometry/ScaleFreeMarks.svelte.d.ts +7 -0
  13. package/dist/layout/geometry/ScaleGate.svelte.d.ts +8 -0
  14. package/dist/layout/geometry/ScaleGate.svelte.js +95 -0
  15. package/dist/layout/geometry/ScaleGateMarks.svelte +15 -0
  16. package/dist/layout/geometry/ScaleGateMarks.svelte.d.ts +7 -0
  17. package/dist/layout/hover/HoverShapes.d.ts +20 -8
  18. package/dist/layout/hover/HoverShapes.js +119 -59
  19. package/dist/layout/legend/ContinuousSection.svelte +10 -0
  20. package/dist/layout/legend/LegendState.svelte.d.ts +13 -1
  21. package/dist/layout/legend/LegendState.svelte.js +32 -0
  22. package/dist/layout/margins/Box.d.ts +4 -0
  23. package/dist/layout/margins/Box.js +14 -0
  24. package/dist/layout/margins/PlotMargins.svelte.js +26 -1
  25. package/dist/layout/plot/{Plot.svelte → AbstractPlot.svelte} +83 -6
  26. package/dist/layout/plot/{Plot.svelte.d.ts → AbstractPlot.svelte.d.ts} +4 -4
  27. package/dist/layout/plot/PublicPlotContext.d.ts +5 -0
  28. package/dist/layout/plot/PublicPlotContext.js +7 -0
  29. package/dist/layout/styles/ValueLabels.d.ts +109 -58
  30. package/dist/layout/styles/ValueLabels.js +299 -141
  31. package/dist/markers/MarkerKinds.d.ts +79 -1
  32. package/dist/markers/MarkerKinds.js +149 -23
  33. package/dist/markers/area/AreaMarker.svelte +1 -1
  34. package/dist/markers/band/BandMarker.svelte +2 -1
  35. package/dist/markers/bar/BarMarker.svelte +8 -6
  36. package/dist/markers/bollinger/BollingerMarker.svelte +1 -1
  37. package/dist/markers/box/BoxMarker.svelte +1 -1
  38. package/dist/markers/dot/DotRadius.d.ts +11 -0
  39. package/dist/markers/dot/DotRadius.js +21 -0
  40. package/dist/markers/geo/GeoHoverMarker.svelte +14 -11
  41. package/dist/markers/labels/CurveLabel.svelte +56 -0
  42. package/dist/markers/labels/CurveLabel.svelte.d.ts +9 -0
  43. package/dist/markers/labels/LabelMerge.d.ts +11 -0
  44. package/dist/markers/labels/LabelMerge.js +87 -0
  45. package/dist/markers/labels/MarkerLabel.svelte +36 -0
  46. package/dist/markers/labels/MarkerLabel.svelte.d.ts +10 -0
  47. package/dist/markers/labels/MarkerLabels.d.ts +52 -0
  48. package/dist/markers/labels/MarkerLabels.js +97 -0
  49. package/dist/markers/labels/geometry/BoxGeometry.d.ts +8 -0
  50. package/dist/markers/labels/geometry/BoxGeometry.js +101 -0
  51. package/dist/markers/labels/geometry/CurveGeometry.d.ts +34 -0
  52. package/dist/markers/labels/geometry/CurveGeometry.js +193 -0
  53. package/dist/markers/labels/geometry/PointGeometry.d.ts +9 -0
  54. package/dist/markers/labels/geometry/PointGeometry.js +22 -0
  55. package/dist/markers/labels/layouts/BoxLayout.d.ts +10 -0
  56. package/dist/markers/labels/layouts/BoxLayout.js +49 -0
  57. package/dist/markers/labels/layouts/PathLayout.d.ts +11 -0
  58. package/dist/markers/labels/layouts/PathLayout.js +91 -0
  59. package/dist/markers/labels/layouts/PointLayout.d.ts +19 -0
  60. package/dist/markers/labels/layouts/PointLayout.js +94 -0
  61. package/dist/markers/regression/RegressionMarker.svelte +1 -1
  62. package/dist/markers/text/Covering.d.ts +13 -0
  63. package/dist/markers/text/Covering.js +29 -0
  64. package/dist/markers/text/Declutter.d.ts +24 -3
  65. package/dist/markers/text/Declutter.js +46 -3
  66. package/dist/markers/text/TextMarker.svelte +90 -17
  67. package/dist/markers/text/TextMarker.svelte.d.ts +1 -1
  68. package/dist/markers/tick/TickMarker.svelte +1 -1
  69. package/dist/markers/utils/MarkerChannels.d.ts +1 -1
  70. package/dist/markers/utils/MarkerChannels.js +3 -2
  71. package/dist/markers/violin/ViolinMarker.svelte +1 -1
  72. package/dist/markers/waffle/WaffleMarker.svelte +1 -1
  73. package/dist/phases/context/RowContext.d.ts +2 -0
  74. package/dist/phases/context/RowContext.js +4 -2
  75. package/dist/phases/context/rowContextKey.d.ts +2 -0
  76. package/dist/phases/context/rowContextKey.js +2 -0
  77. package/dist/phases/markers/MarkerStyles.js +14 -11
  78. package/dist/phases/markers/MarkersPhase.d.ts +2 -3
  79. package/dist/phases/markers/MarkersPhase.js +17 -11
  80. package/dist/phases/sharedScales/SharedStepScales.d.ts +10 -0
  81. package/dist/phases/sharedScales/SharedStepScales.js +43 -0
  82. package/dist/phases/statistics/MarkerGrouping.js +5 -32
  83. package/dist/phases/statistics/MarkerInheritance.d.ts +11 -0
  84. package/dist/phases/statistics/MarkerInheritance.js +25 -0
  85. package/dist/phases/statistics/MarkerOrientation.d.ts +26 -0
  86. package/dist/phases/statistics/MarkerOrientation.js +29 -0
  87. package/dist/phases/statistics/StatisticsPhase.js +15 -15
  88. package/dist/phases/transforms/TransformsPhase.js +5 -3
  89. package/dist/plots/correlation/heatmap/Plot.svelte +2 -2
  90. package/dist/plots/correlation/heatmap/Plot.svelte.d.ts +0 -1
  91. package/dist/plots/correlation/heatmap/defaults.js +14 -1
  92. package/dist/plots/correlation/heatmap/markers.d.ts +0 -6
  93. package/dist/plots/correlation/heatmap/markers.js +5 -9
  94. package/dist/plots/correlation/scatter/Plot.svelte +2 -2
  95. package/dist/plots/correlation/scatter/Plot.svelte.d.ts +0 -1
  96. package/dist/plots/correlation/scatter/defaults.js +9 -3
  97. package/dist/plots/correlation/scatter/markers.d.ts +0 -9
  98. package/dist/plots/correlation/scatter/markers.js +21 -28
  99. package/dist/plots/distribution/box/BoxSummaries.d.ts +5 -0
  100. package/dist/plots/distribution/box/BoxSummaries.js +9 -0
  101. package/dist/plots/distribution/box/Plot.svelte +2 -2
  102. package/dist/plots/distribution/box/Plot.svelte.d.ts +0 -1
  103. package/dist/plots/distribution/box/defaults.js +11 -0
  104. package/dist/plots/distribution/box/markers.js +10 -1
  105. package/dist/plots/distribution/density/Plot.svelte +2 -2
  106. package/dist/plots/distribution/density/Plot.svelte.d.ts +0 -1
  107. package/dist/plots/distribution/density/defaults.js +16 -0
  108. package/dist/plots/distribution/density/markers.js +5 -1
  109. package/dist/plots/distribution/histogram/Plot.svelte +2 -2
  110. package/dist/plots/distribution/histogram/Plot.svelte.d.ts +0 -1
  111. package/dist/plots/distribution/histogram/defaults.js +16 -11
  112. package/dist/plots/distribution/histogram/markers.js +3 -3
  113. package/dist/plots/distribution/pyramid/Plot.svelte +2 -2
  114. package/dist/plots/distribution/pyramid/Plot.svelte.d.ts +0 -1
  115. package/dist/plots/distribution/pyramid/defaults.js +9 -2
  116. package/dist/plots/distribution/pyramid/markers.d.ts +0 -8
  117. package/dist/plots/distribution/pyramid/markers.js +6 -16
  118. package/dist/plots/distribution/violin/Plot.svelte +2 -2
  119. package/dist/plots/distribution/violin/Plot.svelte.d.ts +0 -1
  120. package/dist/plots/distribution/violin/defaults.js +11 -0
  121. package/dist/plots/distribution/violin/markers.js +27 -3
  122. package/dist/plots/evolution/area/Plot.svelte +2 -2
  123. package/dist/plots/evolution/area/Plot.svelte.d.ts +0 -1
  124. package/dist/plots/evolution/area/defaults.js +4 -7
  125. package/dist/plots/evolution/area/markers.js +3 -1
  126. package/dist/plots/evolution/line/Plot.svelte +2 -2
  127. package/dist/plots/evolution/line/Plot.svelte.d.ts +0 -1
  128. package/dist/plots/evolution/line/defaults.js +4 -6
  129. package/dist/plots/evolution/line/markers.js +3 -1
  130. package/dist/plots/evolution/slope/Plot.svelte +2 -2
  131. package/dist/plots/evolution/slope/Plot.svelte.d.ts +0 -1
  132. package/dist/plots/evolution/slope/defaults.js +4 -3
  133. package/dist/plots/evolution/slope/markers.d.ts +4 -3
  134. package/dist/plots/evolution/slope/markers.js +16 -9
  135. package/dist/plots/evolution/sparkline/Plot.svelte +2 -2
  136. package/dist/plots/evolution/sparkline/Plot.svelte.d.ts +0 -1
  137. package/dist/plots/evolution/sparkline/defaults.js +1 -1
  138. package/dist/plots/evolution/sparkline/markers.js +3 -1
  139. package/dist/plots/generic/Plot.svelte +42 -0
  140. package/dist/plots/generic/Plot.svelte.d.ts +25 -0
  141. package/dist/plots/generic/defaults.d.ts +4 -0
  142. package/dist/plots/generic/defaults.js +7 -0
  143. package/dist/plots/map/geo/Core.svelte +29 -6
  144. package/dist/plots/map/geo/Plot.svelte +40 -12
  145. package/dist/plots/map/geo/Plot.svelte.d.ts +0 -1
  146. package/dist/plots/map/geo/phases/data.d.ts +2 -1
  147. package/dist/plots/map/geo/phases/data.js +8 -2
  148. package/dist/plots/map/geo/phases/scales.js +6 -2
  149. package/dist/plots/map/geo/slices.d.ts +27 -3
  150. package/dist/plots/map/geo/slices.js +58 -5
  151. package/dist/plots/part-of-a-whole/treemap/Plot.svelte +2 -2
  152. package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +0 -1
  153. package/dist/plots/part-of-a-whole/treemap/defaults.js +2 -2
  154. package/dist/plots/ranking/bar/Plot.svelte +2 -2
  155. package/dist/plots/ranking/bar/Plot.svelte.d.ts +0 -1
  156. package/dist/plots/ranking/bar/defaults.js +13 -3
  157. package/dist/plots/ranking/bar/markers.js +12 -29
  158. package/dist/plots/ranking/bullet/Plot.svelte +2 -2
  159. package/dist/plots/ranking/bullet/Plot.svelte.d.ts +0 -1
  160. package/dist/plots/ranking/bullet/defaults.js +20 -0
  161. package/dist/plots/ranking/bullet/markers.js +7 -1
  162. package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +2 -9
  163. package/dist/plots/ranking/cleveland/ClevelandMarkers.js +65 -47
  164. package/dist/plots/ranking/cleveland/Plot.svelte +2 -2
  165. package/dist/plots/ranking/cleveland/Plot.svelte.d.ts +0 -1
  166. package/dist/plots/ranking/cleveland/defaults.js +33 -2
  167. package/dist/plots/ranking/dumbbell/Plot.svelte +2 -2
  168. package/dist/plots/ranking/dumbbell/Plot.svelte.d.ts +0 -1
  169. package/dist/plots/ranking/forest/Plot.svelte +2 -2
  170. package/dist/plots/ranking/forest/Plot.svelte.d.ts +0 -1
  171. package/dist/plots/ranking/lollipop/Plot.svelte +2 -2
  172. package/dist/plots/ranking/lollipop/Plot.svelte.d.ts +0 -1
  173. package/dist/types/layout/context.d.ts +2 -0
  174. package/dist/types/layout/plot.d.ts +2 -0
  175. package/dist/types/layout/styles/bullet.d.ts +4 -1
  176. package/dist/types/layout/styles/geo.d.ts +3 -3
  177. package/dist/types/layout/styles/values.d.ts +51 -11
  178. package/dist/types/markers/axis/area.d.ts +5 -1
  179. package/dist/types/markers/axis/bar.d.ts +5 -2
  180. package/dist/types/markers/axis/bollinger.d.ts +2 -1
  181. package/dist/types/markers/axis/box.d.ts +2 -1
  182. package/dist/types/markers/axis/cell.d.ts +4 -1
  183. package/dist/types/markers/axis/line.d.ts +4 -1
  184. package/dist/types/markers/axis/rect.d.ts +4 -1
  185. package/dist/types/markers/axis/regression.d.ts +2 -1
  186. package/dist/types/markers/axis/tick.d.ts +2 -1
  187. package/dist/types/markers/axis/trail.d.ts +4 -1
  188. package/dist/types/markers/axis/violin.d.ts +2 -1
  189. package/dist/types/markers/axis/waffle.d.ts +5 -1
  190. package/dist/types/markers/basic/dot.d.ts +4 -1
  191. package/dist/types/markers/basic/text.d.ts +25 -21
  192. package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +5 -9
  193. package/dist/types/markers/channels/commonMarkerConfigWithOrientation.d.ts +5 -0
  194. package/dist/types/markers/channels/commonMarkerConfigWithOrientation.js +1 -0
  195. package/dist/types/markers/labels/box.d.ts +57 -0
  196. package/dist/types/markers/labels/box.js +1 -0
  197. package/dist/types/markers/labels/common.d.ts +15 -0
  198. package/dist/types/markers/labels/common.js +1 -0
  199. package/dist/types/markers/labels/path.d.ts +52 -0
  200. package/dist/types/markers/labels/path.js +1 -0
  201. package/dist/types/markers/labels/point.d.ts +40 -0
  202. package/dist/types/markers/labels/point.js +1 -0
  203. package/dist/types/markers/traits.d.ts +10 -0
  204. package/dist/types/phases/markers/phase.d.ts +1 -8
  205. package/dist/types/phases/statistics/phase.d.ts +8 -3
  206. package/dist/types/phases/styles/phase.d.ts +15 -3
  207. package/dist/types/phases/transforms/phase.d.ts +2 -0
  208. package/dist/types/plots/constants.d.ts +0 -12
  209. package/dist/types/plots/correlation/heatmap/props.d.ts +3 -2
  210. package/dist/types/plots/correlation/scatter/props.d.ts +3 -2
  211. package/dist/types/plots/data/geo.d.ts +19 -5
  212. package/dist/types/plots/distribution/box/props.d.ts +6 -2
  213. package/dist/types/plots/distribution/density/props.d.ts +2 -1
  214. package/dist/types/plots/distribution/histogram/props.d.ts +3 -2
  215. package/dist/types/plots/distribution/pyramid/props.d.ts +3 -2
  216. package/dist/types/plots/distribution/violin/props.d.ts +4 -1
  217. package/dist/types/plots/evolution/area/props.d.ts +3 -2
  218. package/dist/types/plots/evolution/line/props.d.ts +3 -2
  219. package/dist/types/plots/generic/props.d.ts +16 -0
  220. package/dist/types/plots/generic/props.js +1 -0
  221. package/dist/types/plots/generic/state.d.ts +4 -0
  222. package/dist/types/plots/generic/state.js +1 -0
  223. package/dist/types/plots/map/geo/props.d.ts +9 -3
  224. package/dist/types/plots/map/geo/state.d.ts +3 -1
  225. package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +4 -2
  226. package/dist/types/plots/ranking/bar/props.d.ts +3 -2
  227. package/dist/types/plots/ranking/cleveland/props.d.ts +3 -2
  228. package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -2
  229. package/dist/types/plots/ranking/forest/props.d.ts +5 -2
  230. package/dist/types/plots/ranking/lollipop/props.d.ts +3 -2
  231. package/dist/utils/ConfigEquality.d.ts +11 -0
  232. package/dist/utils/ConfigEquality.js +37 -0
  233. package/docs/architecture.md +9 -9
  234. package/docs/facets.md +12 -0
  235. package/docs/geo.md +35 -7
  236. package/docs/legend.md +6 -0
  237. package/docs/markers.md +2 -4
  238. package/docs/plots/ranking/bar.md +1 -1
  239. package/docs/plots/ranking/cleveland.md +1 -1
  240. package/docs/state.md +1 -1
  241. package/docs/styles-and-segments.md +1 -14
  242. package/docs/timeline.md +1 -1
  243. package/docs/transforms-and-statistics.md +1 -1
  244. package/docs/value-labels.md +159 -0
  245. package/examples/BarValues.svelte +1 -1
  246. package/llms-full.txt +5 -5
  247. package/llms.txt +1 -0
  248. package/package.json +3 -1
  249. package/dist/markers/utils/BoxLabel.d.ts +0 -58
  250. package/dist/markers/utils/BoxLabel.js +0 -175
  251. package/dist/markers/utils/MarkerLabel.svelte +0 -33
  252. package/dist/markers/utils/MarkerLabel.svelte.d.ts +0 -10
@@ -16,7 +16,7 @@
16
16
 
17
17
  const plot = usePlot();
18
18
  const cfg = $derived(getConfiguration());
19
- const isHorizontal = $derived(plot.axes?.isHorizontal ?? false);
19
+ const isHorizontal = $derived(marker.isHorizontal ?? plot.axes?.isHorizontal ?? false);
20
20
  const categoryScale = $derived(
21
21
  isHorizontal ? plot.svelteplot.scales.y : plot.svelteplot.scales.x
22
22
  );
@@ -12,7 +12,7 @@
12
12
  const events = MarkerEvents.use(() => marker);
13
13
 
14
14
  const plot = usePlot();
15
- const isHorizontal = $derived(plot.axes?.isHorizontal ?? false);
15
+ const isHorizontal = $derived(marker.isHorizontal ?? plot.axes?.isHorizontal ?? false);
16
16
  const WaffleMark = $derived(isHorizontal ? WaffleX : WaffleY);
17
17
 
18
18
  const cfg = $derived(getConfiguration());
@@ -19,6 +19,8 @@ export declare class RowContext {
19
19
  * for it, with the row itself. A row no plot drew reads as its series alone.
20
20
  */
21
21
  static of<TContext extends PlotRowContext = PlotRowContext>(row: unknown): TContext;
22
+ /** The key a row's context is kept under: no scale reads it, so a copy of a row with a new context is no new row to them. */
23
+ static readonly key: symbol;
22
24
  /** Keeps `context` on `row`, for {@link of} to read back. */
23
25
  static tag(row: ProcessedRow, context: PlotRowContext): void;
24
26
  /** Whether `row` carries a context already. */
@@ -1,10 +1,10 @@
1
+ import { ROW_CONTEXT_KEY } from './rowContextKey';
1
2
  import { SERIES_ID_KEY, SERIES_KEY } from '../transforms/TransformsPhase';
2
3
  import { ProcessedPositions } from '../transforms/ProcessedPositions';
3
4
  import { PlotAxes } from '../data/PlotAxes';
4
5
  import { resolveAccessor } from '../../utils/accessors';
5
6
  import { seriesIdOf } from '../../utils/grouping';
6
- /** Where a row's context is kept on it: under a symbol, which survives the copies svelteplot makes of a row. */
7
- const KEY = Symbol('dct-row-context');
7
+ const KEY = ROW_CONTEXT_KEY;
8
8
  /**
9
9
  * The context of the rows a plot draws ({@link PlotRowContext}): worked out once per row by the "context" phase,
10
10
  * kept on the row itself, and read back from it - or from any copy of it - with {@link of}.
@@ -24,6 +24,8 @@ export class RowContext {
24
24
  const series = record[SERIES_KEY];
25
25
  return { ...(typeof series === 'string' ? { series } : {}), row };
26
26
  }
27
+ /** The key a row's context is kept under: no scale reads it, so a copy of a row with a new context is no new row to them. */
28
+ static key = KEY;
27
29
  /** Keeps `context` on `row`, for {@link of} to read back. */
28
30
  static tag(row, context) {
29
31
  row[KEY] = context;
@@ -0,0 +1,2 @@
1
+ /** Where a row's context is kept on it: under a symbol, which survives the copies svelteplot makes of a row. */
2
+ export declare const ROW_CONTEXT_KEY: symbol;
@@ -0,0 +1,2 @@
1
+ /** Where a row's context is kept on it: under a symbol, which survives the copies svelteplot makes of a row. */
2
+ export const ROW_CONTEXT_KEY = Symbol('dct-row-context');
@@ -1,6 +1,7 @@
1
1
  import { MarkerChannels } from '../../markers/utils/MarkerChannels';
2
2
  import { RowStyle } from '../../markers/utils/RowStyle';
3
3
  import { CssColor } from '../../utils/color/CssColor';
4
+ import { MarkerLabels } from '../../markers/labels/MarkerLabels';
4
5
  /**
5
6
  * A plot's markers with their styles as svelteplot reads them, before any is drawn: a named line style
6
7
  * (`strokeDasharray: 'dashed'`) is the SVG dash pattern it stands for, wherever in a marker's config it is
@@ -33,28 +34,30 @@ export class MarkerStyles {
33
34
  * that read best on the shape's old one reads best on its new one.
34
35
  */
35
36
  static restyle(marker, plot = {}) {
36
- const label = marker.label;
37
- const fill = label?.styles?.fill;
38
- const fresh = typeof fill === 'function' && MarkerStyles.#contrasts.has(fill)
39
- ? { ...marker, label: { ...label, styles: { ...label.styles, fill: undefined } } }
40
- : marker;
37
+ const fresh = MarkerLabels.map(marker, (label) => {
38
+ const fill = label.styles?.fill;
39
+ return typeof fill === 'function' && MarkerStyles.#contrasts.has(fill)
40
+ ? { ...label, styles: { ...label.styles, fill: undefined } }
41
+ : label;
42
+ });
41
43
  return MarkerStyles.resolveAll([fresh], plot)[0];
42
44
  }
43
- /** The marker with its label inside its shape written in the candidate that reads best on each row's color. */
45
+ /** Writes each label on its shape in the color that reads best on it. */
44
46
  static #contrasted(marker, plot) {
45
47
  const { cfg } = plot;
46
48
  if (!cfg || !MarkerStyles.#isPlain(marker))
47
49
  return marker;
48
- const label = marker.label;
49
- if (!MarkerStyles.#isPlain(label) || label.outside || label.styles?.fill !== undefined) {
50
+ const unset = (label) => MarkerStyles.#isPlain(label) &&
51
+ MarkerLabels.isOnShape(marker, label) &&
52
+ label.styles?.fill === undefined;
53
+ if (!MarkerLabels.list(marker).some(unset))
50
54
  return marker;
51
- }
52
55
  const colorOf = MarkerStyles.#shapeColor(marker, marker.data ?? plot.rows ?? [], cfg);
53
56
  if (!colorOf)
54
57
  return marker;
55
58
  const candidates = cfg.text.label.contrast;
56
59
  const opacityOf = MarkerStyles.#opacity(marker.styles?.fillOpacity);
57
- // What the label is read on: the shape's color, blended with the plot's background as far as it is see-through.
60
+ // The shape's color as seen: blended with the background by its opacity.
58
61
  const fill = (row) => {
59
62
  const color = colorOf(row);
60
63
  if (!color)
@@ -63,7 +66,7 @@ export class MarkerStyles {
63
66
  return CssColor.contrastText(seen, candidates);
64
67
  };
65
68
  MarkerStyles.#contrasts.add(fill);
66
- return { ...marker, label: { ...label, styles: { ...label.styles, fill } } };
69
+ return MarkerLabels.map(marker, (label) => unset(label) ? { ...label, styles: { ...label.styles, fill } } : label);
67
70
  }
68
71
  /** The opacity each row's shape is drawn with: a number, the field of the rows a string names, a function of the row. */
69
72
  static #opacity(style) {
@@ -2,9 +2,8 @@ import type { AxisPlotEnv, AxisPlotState, MarkersStageOptions, PhaseStage } from
2
2
  /**
3
3
  * The shared "markers" phase: everything the plot draws, bottom to top - the
4
4
  * plot kind's own marks, then what the "statistics" phase left in
5
- * `state.markers` (the author's markers and the plot's statistics), then its
6
- * value labels when `styles.values.show` turns them on, each with what every
7
- * label shares ({@link ValueLabels.hooks}), and every style as svelteplot reads it ({@link MarkerStyles}).
5
+ * `state.markers` (the author's markers and the plot's statistics), then the
6
+ * `'point'` labels of all of them, and every style as svelteplot reads it ({@link MarkerStyles}).
8
7
  */
9
8
  export declare class MarkersPhase<TState extends AxisPlotState<any, any, any, any>, TEnv extends AxisPlotEnv> implements PhaseStage<TState, TEnv> {
10
9
  #private;
@@ -1,11 +1,12 @@
1
1
  import { MarkerChannels } from '../../markers/utils/MarkerChannels';
2
2
  import { MarkerStyles } from './MarkerStyles';
3
+ import { MarkerLabels } from '../../markers/labels/MarkerLabels';
4
+ import { PlotAxes } from '../data/PlotAxes';
3
5
  /**
4
6
  * The shared "markers" phase: everything the plot draws, bottom to top - the
5
7
  * plot kind's own marks, then what the "statistics" phase left in
6
- * `state.markers` (the author's markers and the plot's statistics), then its
7
- * value labels when `styles.values.show` turns them on, each with what every
8
- * label shares ({@link ValueLabels.hooks}), and every style as svelteplot reads it ({@link MarkerStyles}).
8
+ * `state.markers` (the author's markers and the plot's statistics), then the
9
+ * `'point'` labels of all of them, and every style as svelteplot reads it ({@link MarkerStyles}).
9
10
  */
10
11
  export class MarkersPhase {
11
12
  phase = 'markers';
@@ -15,16 +16,21 @@ export class MarkersPhase {
15
16
  }
16
17
  run = (state, env) => {
17
18
  const options = this.#options;
18
- const labels = state.valueLabels;
19
- const valueLabels = labels?.show && options.valueLabels
20
- ? options.valueLabels(labels, state, env).map((marker) => {
21
- const { styles, ...hooks } = labels.hooks(env);
22
- return { ...hooks, ...marker, styles: { ...styles, ...marker.styles } };
23
- })
24
- : [];
19
+ const rows = state.processedData?.data;
20
+ const positioned = MarkerChannels.inheritPosition(MarkerChannels.inheritAll([...options.marks(state, env), ...(state.markers ?? [])], state.scales), state.processedData);
21
+ // Point labels draw last, merged across markers so they declutter together.
22
+ const { markers, labels: pointLabels } = MarkerLabels.extractPoints(positioned, {
23
+ rows,
24
+ valueAxis: PlotAxes.valueOf(state),
25
+ isHorizontal: state.axes?.isHorizontal,
26
+ gap: env.cfg.text?.label.gap
27
+ });
25
28
  return {
26
29
  ...state,
27
- markers: MarkerStyles.resolveAll(MarkerChannels.inheritPosition(MarkerChannels.inheritAll([...options.marks(state, env), ...(state.markers ?? []), ...valueLabels], state.scales), state.processedData), { cfg: env.cfg, rows: state.processedData?.data })
30
+ markers: MarkerStyles.resolveAll([...markers, ...pointLabels], {
31
+ cfg: env.cfg,
32
+ rows
33
+ })
28
34
  };
29
35
  };
30
36
  }
@@ -0,0 +1,10 @@
1
+ export declare const SharedStepScales: {
2
+ /**
3
+ * The domains the slices of a timeline share, when its steps each draw their own rows: a step's rows
4
+ * stack, aggregate or bin among themselves, so the extent the whole run reads across every step is
5
+ * wider than any step's. `whole` is the shared scales read over every row, and `steps` the same over each
6
+ * step's rows. A numeric (or date) extent becomes the widest any step reaches; a band or categorical
7
+ * domain stays the whole run's, which holds every category.
8
+ */
9
+ over(whole: Record<string, object> | undefined, bands: string[], steps: (Record<string, object> | undefined)[]): Record<string, object> | undefined;
10
+ };
@@ -0,0 +1,43 @@
1
+ /** A channel's numeric extent, or a date one - `undefined` for any other domain. */
2
+ function extentOf(domain) {
3
+ if (domain?.length !== 2)
4
+ return undefined;
5
+ const [lo, hi] = domain.map((v) => (v instanceof Date ? +v : v));
6
+ return typeof lo === 'number' && typeof hi === 'number' ? [lo, hi] : undefined;
7
+ }
8
+ export const SharedStepScales = {
9
+ /**
10
+ * The domains the slices of a timeline share, when its steps each draw their own rows: a step's rows
11
+ * stack, aggregate or bin among themselves, so the extent the whole run reads across every step is
12
+ * wider than any step's. `whole` is the shared scales read over every row, and `steps` the same over each
13
+ * step's rows. A numeric (or date) extent becomes the widest any step reaches; a band or categorical
14
+ * domain stays the whole run's, which holds every category.
15
+ */
16
+ over(whole, bands, steps) {
17
+ if (!whole)
18
+ return whole;
19
+ const out = {};
20
+ for (const [panel, scales] of Object.entries(whole)) {
21
+ out[panel] = { ...scales };
22
+ for (const [channel, scale] of Object.entries(scales)) {
23
+ if (!scale || bands.includes(channel))
24
+ continue;
25
+ const own = extentOf(scale.domain);
26
+ if (!own)
27
+ continue;
28
+ const extents = steps
29
+ .map((step) => extentOf(step?.[panel]?.[channel]?.domain))
30
+ .filter((extent) => extent !== undefined);
31
+ if (!extents.length)
32
+ continue;
33
+ const lo = Math.min(...extents.map(([a]) => a));
34
+ const hi = Math.max(...extents.map(([, b]) => b));
35
+ out[panel][channel] = {
36
+ ...scale,
37
+ domain: scale.domain[0] instanceof Date ? [new Date(lo), new Date(hi)] : [lo, hi]
38
+ };
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+ };
@@ -1,33 +1,4 @@
1
- /** Marker kinds that split their rows into separate shapes per `grouping`. */
2
- const GROUPING_KINDS = new Set([
3
- 'area',
4
- 'density',
5
- 'line',
6
- 'hull',
7
- 'trail',
8
- 'delaunayLink',
9
- 'delaunayMesh',
10
- 'voronoi',
11
- 'voronoiMesh'
12
- ]);
13
- /** Marker kinds whose per-row `color` resolves through the plot's own color scale. */
14
- const COLOR_KINDS = new Set([
15
- 'area',
16
- 'bar',
17
- 'cell',
18
- 'density',
19
- 'delaunayLink',
20
- 'delaunayMesh',
21
- 'dot',
22
- 'hull',
23
- 'line',
24
- 'rect',
25
- 'tick',
26
- 'trail',
27
- 'voronoi',
28
- 'voronoiMesh',
29
- 'waffle'
30
- ]);
1
+ import { MarkerKinds } from '../../markers/MarkerKinds';
31
2
  /** Kinds that draw only a line, so their flat color is `stroke` alone. */
32
3
  const LINE_KINDS = new Set(['line', 'regression', 'bollinger', 'delta']);
33
4
  /** Flat color style keys - when a marker sets one, it keeps that color instead of inheriting one. */
@@ -86,9 +57,11 @@ export class MarkerGrouping {
86
57
  if (grouping === undefined)
87
58
  return MarkerGrouping.withSeriesColor(m, plot.defaultColor);
88
59
  const updates = {};
89
- if (GROUPING_KINDS.has(m.type) && m.grouping === undefined)
60
+ if (MarkerKinds.inherits(m.type, 'grouping') && m.grouping === undefined)
90
61
  updates.grouping = grouping;
91
- if (COLOR_KINDS.has(m.type) && m.color === undefined && !MarkerGrouping.#hasFlatColor(m)) {
62
+ if (MarkerKinds.inherits(m.type, 'color') &&
63
+ m.color === undefined &&
64
+ !MarkerGrouping.#hasFlatColor(m)) {
92
65
  updates.color = plot.color ?? plot.grouping;
93
66
  }
94
67
  return Object.keys(updates).length ? { ...m, ...updates } : marker;
@@ -0,0 +1,11 @@
1
+ import type { InheritedChannel } from '../../types/markers/traits';
2
+ /** How a plot's position and value channels reach the markers its author adds. */
3
+ export declare class MarkerInheritance {
4
+ /**
5
+ * `marker` with the plot's `x`/`y` on each axis it sets no position for (`x`, `x1` or `x2`...), and the
6
+ * plot's other channels it sets none of, as far as its kind inherits them.
7
+ */
8
+ static inherit<T extends {
9
+ type: string;
10
+ }>(marker: T, plot: Partial<Record<InheritedChannel, unknown>>): T;
11
+ }
@@ -0,0 +1,25 @@
1
+ import { MarkerKinds } from '../../markers/MarkerKinds';
2
+ /** The value channels a marker takes as they are; `grouping` and `color` are `MarkerGrouping`'s. */
3
+ const VALUES = ['opacity', 'symbol', 'r', 'length'];
4
+ /** How a plot's position and value channels reach the markers its author adds. */
5
+ export class MarkerInheritance {
6
+ /**
7
+ * `marker` with the plot's `x`/`y` on each axis it sets no position for (`x`, `x1` or `x2`...), and the
8
+ * plot's other channels it sets none of, as far as its kind inherits them.
9
+ */
10
+ static inherit(marker, plot) {
11
+ const own = marker;
12
+ const takes = (channel) => plot[channel] !== undefined && MarkerKinds.inherits(own.type, channel);
13
+ const taken = {};
14
+ for (const axis of ['x', 'y']) {
15
+ const unset = [axis, `${axis}1`, `${axis}2`].every((key) => own[key] === undefined);
16
+ if (unset && takes(axis))
17
+ taken[axis] = plot[axis];
18
+ }
19
+ for (const channel of VALUES) {
20
+ if (own[channel] === undefined && takes(channel))
21
+ taken[channel] = plot[channel];
22
+ }
23
+ return Object.keys(taken).length ? { ...own, ...taken } : marker;
24
+ }
25
+ }
@@ -0,0 +1,26 @@
1
+ type Marker = {
2
+ type: string;
3
+ isHorizontal?: boolean;
4
+ data?: unknown[];
5
+ } & Record<string, unknown>;
6
+ type AxisScales = {
7
+ x?: {
8
+ type?: string;
9
+ };
10
+ y?: {
11
+ type?: string;
12
+ };
13
+ };
14
+ /** Which way an oriented marker runs when its plot has no position channels to say it. */
15
+ export declare class MarkerOrientation {
16
+ /** `marker` with `isHorizontal` taken from its own channels, unless it sets one or can't tell. */
17
+ static orient<T extends {
18
+ type: string;
19
+ }>(marker: T, rows: unknown[], scales?: AxisScales): T;
20
+ /**
21
+ * A band (`x1`/`x2` or `y1`/`y2`) runs across its own axis; two channels run as a plot's
22
+ * would; a single channel is the measured value.
23
+ */
24
+ static of(marker: Marker, rows: unknown[], scales?: AxisScales): boolean | undefined;
25
+ }
26
+ export {};
@@ -0,0 +1,29 @@
1
+ import { MarkerKinds } from '../../markers/MarkerKinds';
2
+ import { PlotAxes } from '../data/PlotAxes';
3
+ /** Which way an oriented marker runs when its plot has no position channels to say it. */
4
+ export class MarkerOrientation {
5
+ /** `marker` with `isHorizontal` taken from its own channels, unless it sets one or can't tell. */
6
+ static orient(marker, rows, scales) {
7
+ const own = marker;
8
+ if (own.isHorizontal !== undefined || !MarkerKinds.of(own.type)?.oriented)
9
+ return marker;
10
+ const isHorizontal = MarkerOrientation.of(own, rows, scales);
11
+ return isHorizontal === undefined ? marker : { ...own, isHorizontal };
12
+ }
13
+ /**
14
+ * A band (`x1`/`x2` or `y1`/`y2`) runs across its own axis; two channels run as a plot's
15
+ * would; a single channel is the measured value.
16
+ */
17
+ static of(marker, rows, scales) {
18
+ const set = (...keys) => keys.some((key) => marker[key] !== undefined);
19
+ const [xBand, yBand] = [set('x1', 'x2'), set('y1', 'y2')];
20
+ if (xBand !== yBand)
21
+ return xBand;
22
+ const [x, y] = [set('x'), set('y')];
23
+ if (x && y) {
24
+ const data = marker.data ?? rows;
25
+ return PlotAxes.build({ series: [{ x: marker.x, y: marker.y, data }], scales }).isHorizontal;
26
+ }
27
+ return x !== y ? x : undefined;
28
+ }
29
+ }
@@ -1,9 +1,9 @@
1
1
  import { AuthorMarkers } from './AuthorMarkers';
2
2
  import { Statistics } from './Statistics';
3
+ import { MarkerInheritance } from './MarkerInheritance';
4
+ import { MarkerOrientation } from './MarkerOrientation';
3
5
  import { PANEL_KEY, FacetPanels } from '../facet/FacetPanels';
4
6
  import { PlotAxes } from '../data/PlotAxes';
5
- /** Marker kinds that draw from a channel pair, so they take the plot's `x`/`y` when the author leaves one out. */
6
- const POSITIONED_KINDS = new Set(['line', 'area', 'dot']);
7
7
  /**
8
8
  * The "statistics" phase every axis-based plot shares: seeds `state.markers`
9
9
  * with the plot-level `statistics` resolved against every series, followed by
@@ -38,23 +38,23 @@ export class StatisticsPhase {
38
38
  #resolve(state) {
39
39
  const options = this.#options;
40
40
  const { resolvedSeries = [], flat = [], colorForSeries, statistics, authorMarkers = [] } = state;
41
- const { inheritGrouping = true, inheritPositions = false } = options;
41
+ const { inheritGrouping = true } = options;
42
42
  const defaultColor = options.defaultColor
43
43
  ? options.defaultColor(state)
44
44
  : resolvedSeries[0] && colorForSeries?.(resolvedSeries[0].name);
45
45
  const onProcessed = authorMarkers.map((marker) => AuthorMarkers.withProcessedRows(marker, state.processedData));
46
- const positioned = inheritPositions
47
- ? onProcessed.map((marker) => {
48
- if (!POSITIONED_KINDS.has(marker.type))
49
- return marker;
50
- const own = marker;
51
- return {
52
- ...own,
53
- x: own.x ?? resolvedSeries[0]?.x ?? state.x,
54
- y: own.y ?? resolvedSeries[0]?.y ?? state.y
55
- };
56
- })
57
- : onProcessed;
46
+ const unpositioned = !state.axes?.x && !state.axes?.y;
47
+ const positioned = onProcessed.map((marker) => {
48
+ const inherited = MarkerInheritance.inherit(marker, {
49
+ x: resolvedSeries[0]?.x ?? state.x,
50
+ y: resolvedSeries[0]?.y ?? state.y,
51
+ opacity: state.opacity,
52
+ symbol: state.symbol,
53
+ r: state.r,
54
+ length: state.length
55
+ });
56
+ return unpositioned ? MarkerOrientation.orient(inherited, flat, state.scales) : inherited;
57
+ });
58
58
  // A series setting no value of its own (a histogram's) measures its processed rows: its bins.
59
59
  const valueAxis = PlotAxes.valueOf(state);
60
60
  const measured = state.processedData && resolvedSeries.some((s) => s[valueAxis] === undefined)
@@ -1,3 +1,4 @@
1
+ import { ValueLabels } from '../../layout/styles/ValueLabels';
1
2
  import { Transforms } from './Transforms';
2
3
  import { ValueSelection } from './ValueSelection';
3
4
  import { ProcessedPositions } from './ProcessedPositions';
@@ -129,17 +130,18 @@ export class TransformsPhase {
129
130
  #labelled(state) {
130
131
  const options = this.#options.labels;
131
132
  const values = state.values;
132
- if (!options || !(values?.show ?? options.show))
133
+ if (!options || !ValueLabels.shows(values, options.show))
133
134
  return {};
134
135
  const processed = state.processedData;
135
- const valueAxis = PlotAxes.valueOf(state);
136
+ const measure = options.measure?.(state);
137
+ const valueAxis = measure ? 'y' : PlotAxes.valueOf(state);
136
138
  const select = values?.select ?? options.select;
137
139
  const selections = ValueSelection.transforms(select, values?.series, valueAxis);
138
140
  const position = ProcessedPositions.of(processed, valueAxis);
139
141
  const skip = options.skip?.(state);
140
142
  const channels = {
141
143
  x: position.x,
142
- y: position.y,
144
+ y: measure ?? position.y,
143
145
  group: options.group === 'category'
144
146
  ? position[PlotAxes.categoryOf(state)]
145
147
  : SERIES_ID_KEY
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" generics="TData extends Record<string, unknown>">
2
- import Plot from '../../../layout/plot/Plot.svelte';
2
+ import AbstractPlot from '../../../layout/plot/AbstractPlot.svelte';
3
3
  import { buildHeatmapDefaults } from './defaults';
4
4
  import type {
5
5
  HeatmapPlotProps,
@@ -44,7 +44,7 @@
44
44
  });
45
45
  </script>
46
46
 
47
- <Plot
47
+ <AbstractPlot
48
48
  core={core as HeatmapPlotCoreProps<TData>}
49
49
  defaults={buildHeatmapDefaults}
50
50
  bind:margins
@@ -1,4 +1,3 @@
1
- import Plot from '../../../layout/plot/Plot.svelte';
2
1
  import type { HeatmapPlotProps } from '../../../types/plots/correlation/heatmap/props';
3
2
  declare function $$render<TData extends Record<string, unknown>>(): {
4
3
  props: HeatmapPlotProps<TData>;
@@ -85,7 +85,20 @@ export function buildHeatmapDefaults() {
85
85
  defaults: () => ({ x: { labelAnchor: 'right' } }),
86
86
  extend: extendWithColorScales
87
87
  },
88
- styles: { values: { show: true } },
88
+ transforms: {
89
+ // A cell's value is its color: `'min'` and `'max'` compare that.
90
+ labels: {
91
+ show: true,
92
+ measure: (state) => state.channels?.color.accessor
93
+ }
94
+ },
95
+ styles: {
96
+ values: {
97
+ show: true,
98
+ layout: 'box',
99
+ layouts: { path: { edge: 'top', side: 'inside' }, point: { anchor: 'top', side: 'above' } }
100
+ }
101
+ },
89
102
  markers: { marks: buildMarks },
90
103
  hover: {
91
104
  // A cell writes its own value: the hover only restyles the cells - the hovered one outlined.
@@ -1,9 +1,3 @@
1
1
  import type { HeatmapPlotState, HeatmapPlotEnv } from '../../../types/plots/correlation/heatmap/state';
2
2
  import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
3
- /**
4
- * HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
5
- * bucket - svelteplot's opacity/numeric scale machinery requires
6
- * `fillOpacity`/`strokeWidth` to be constant per mark instance, unlike
7
- * `fill`, which stays a per-row accessor.
8
- */
9
3
  export declare function buildMarks<TData extends Record<string, unknown>>(state: HeatmapPlotState<TData>, env: HeatmapPlotEnv): AxisBasedMarkersConfig[];
@@ -8,12 +8,6 @@ function readChannel(channel) {
8
8
  return resolveAccessor(channel);
9
9
  }
10
10
  const OPACITY_STEPS = 32;
11
- /**
12
- * HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
13
- * bucket - svelteplot's opacity/numeric scale machinery requires
14
- * `fillOpacity`/`strokeWidth` to be constant per mark instance, unlike
15
- * `fill`, which stays a per-row accessor.
16
- */
17
11
  export function buildMarks(state, env) {
18
12
  const { segments, channels, resolveColor, resolveOpacity } = state;
19
13
  const { range } = env;
@@ -111,9 +105,11 @@ export function buildMarks(state, env) {
111
105
  inset: 0.5
112
106
  },
113
107
  hover: 'style',
114
- // Each cell writes its own value, in the middle of it.
115
- label: labels?.shapeLabel(env, (d) => labels.format(Number(getColor(d)), d), {
116
- fontWeight: 600
108
+ // Each cell writes its own value, in the middle of it by default.
109
+ label: labels?.label(env, (d) => labels.text(d), {
110
+ fill: 'currentColor',
111
+ fontWeight: 600,
112
+ labelled: state.labelled
117
113
  })
118
114
  }));
119
115
  return markers;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" generics="TData extends Record<string, unknown>">
2
- import Plot from '../../../layout/plot/Plot.svelte';
2
+ import AbstractPlot from '../../../layout/plot/AbstractPlot.svelte';
3
3
  import { buildScatterDefaults } from './defaults';
4
4
  import type {
5
5
  ScatterPlotProps,
@@ -27,7 +27,7 @@
27
27
  }: ScatterPlotProps<TData> = $props();
28
28
  </script>
29
29
 
30
- <Plot
30
+ <AbstractPlot
31
31
  core={core as ScatterPlotCoreProps<TData>}
32
32
  defaults={buildScatterDefaults}
33
33
  bind:margins
@@ -1,4 +1,3 @@
1
- import Plot from '../../../layout/plot/Plot.svelte';
2
1
  import type { ScatterPlotProps } from '../../../types/plots/correlation/scatter/props';
3
2
  declare function $$render<TData extends Record<string, unknown>>(): {
4
3
  props: ScatterPlotProps<TData>;
@@ -1,6 +1,6 @@
1
1
  import { ProcessedPositions } from '../../../phases/transforms/ProcessedPositions';
2
2
  import { HoverDefaults } from '../../../layout/hover/HoverDefaults';
3
- import { buildMarks, buildValueLabels } from './markers';
3
+ import { buildMarks } from './markers';
4
4
  import { Margins } from '../../../layout/margins/Margins';
5
5
  import { MarkerAxisValues } from '../../../phases/scales/MarkerAxisValues';
6
6
  /** Stand-in max render radius for a channel-bound `r` (svelteplot's own auto sqrt scale). */
@@ -68,9 +68,15 @@ export function buildScatterDefaults() {
68
68
  base: { flat: 'dot' }
69
69
  }
70
70
  },
71
- transforms: { groups: 'runs' },
71
+ transforms: { groups: 'runs', labels: {} },
72
72
  scales: { fit: fitDomains },
73
- markers: { marks: buildMarks, valueLabels: buildValueLabels },
73
+ styles: {
74
+ values: {
75
+ layout: 'point',
76
+ layouts: { point: { side: 'above', gap: 8 }, path: { side: 'outside' } }
77
+ }
78
+ },
79
+ markers: { marks: buildMarks },
74
80
  hover: {
75
81
  strategy: 'punctual',
76
82
  // A crosshair through the hovered point, and its label - the point is a dot already.
@@ -1,14 +1,5 @@
1
1
  import type { ScatterPlotState, ScatterPlotEnv } from '../../../types/plots/correlation/scatter/state';
2
- import type { TextMarkerConfig } from '../../../types/markers/common';
3
- import { ValueLabels } from '../../../layout/styles/ValueLabels';
4
2
  import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
5
- /**
6
- * ScatterPlot's `styles.values` as one `'text'` marker over the same
7
- * processed rows its dots draw, each labelled with its own `y` value. A label
8
- * sits beside its point and flips to the other side where its text would
9
- * cross the plot's edge (`edgeAware`).
10
- */
11
- export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData>): TextMarkerConfig[];
12
3
  /**
13
4
  * ScatterPlot's "markers" phase: one `'dot'` marker per visual segment of the
14
5
  * rows the "transforms" phase left, in their own order, so a reordering