@fundar/data-chart-telling 1.18.0 → 1.19.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 (220) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +2 -1
  3. package/dist/configuration/config.svelte.js +1 -1
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.js +2 -1
  6. package/dist/layout/coordinates/CoordinatesLayout.svelte +3 -1
  7. package/dist/layout/geometry/GeometryLayout.svelte +57 -22
  8. package/dist/layout/geometry/ScaleFreeContext.d.ts +31 -0
  9. package/dist/layout/geometry/ScaleFreeContext.js +65 -0
  10. package/dist/layout/geometry/ScaleFreeMarks.svelte +14 -0
  11. package/dist/layout/geometry/ScaleFreeMarks.svelte.d.ts +7 -0
  12. package/dist/layout/geometry/ScaleGate.svelte.d.ts +8 -0
  13. package/dist/layout/geometry/ScaleGate.svelte.js +95 -0
  14. package/dist/layout/geometry/ScaleGateMarks.svelte +15 -0
  15. package/dist/layout/geometry/ScaleGateMarks.svelte.d.ts +7 -0
  16. package/dist/layout/hover/HoverShapes.d.ts +20 -8
  17. package/dist/layout/hover/HoverShapes.js +119 -59
  18. package/dist/layout/margins/Box.d.ts +4 -0
  19. package/dist/layout/margins/Box.js +14 -0
  20. package/dist/layout/margins/PlotMargins.svelte.js +26 -1
  21. package/dist/layout/plot/{Plot.svelte.d.ts → AbstractPlot.svelte.d.ts} +3 -3
  22. package/dist/layout/styles/ValueLabels.d.ts +109 -58
  23. package/dist/layout/styles/ValueLabels.js +299 -141
  24. package/dist/markers/MarkerKinds.d.ts +79 -1
  25. package/dist/markers/MarkerKinds.js +149 -23
  26. package/dist/markers/area/AreaMarker.svelte +1 -1
  27. package/dist/markers/bar/BarMarker.svelte +8 -6
  28. package/dist/markers/bollinger/BollingerMarker.svelte +1 -1
  29. package/dist/markers/box/BoxMarker.svelte +1 -1
  30. package/dist/markers/dot/DotRadius.d.ts +11 -0
  31. package/dist/markers/dot/DotRadius.js +21 -0
  32. package/dist/markers/labels/CurveLabel.svelte +56 -0
  33. package/dist/markers/labels/CurveLabel.svelte.d.ts +9 -0
  34. package/dist/markers/labels/LabelMerge.d.ts +11 -0
  35. package/dist/markers/labels/LabelMerge.js +87 -0
  36. package/dist/markers/labels/MarkerLabel.svelte +36 -0
  37. package/dist/markers/labels/MarkerLabel.svelte.d.ts +10 -0
  38. package/dist/markers/labels/MarkerLabels.d.ts +52 -0
  39. package/dist/markers/labels/MarkerLabels.js +97 -0
  40. package/dist/markers/labels/geometry/BoxGeometry.d.ts +8 -0
  41. package/dist/markers/labels/geometry/BoxGeometry.js +101 -0
  42. package/dist/markers/labels/geometry/CurveGeometry.d.ts +34 -0
  43. package/dist/markers/labels/geometry/CurveGeometry.js +193 -0
  44. package/dist/markers/labels/geometry/PointGeometry.d.ts +9 -0
  45. package/dist/markers/labels/geometry/PointGeometry.js +22 -0
  46. package/dist/markers/labels/layouts/BoxLayout.d.ts +10 -0
  47. package/dist/markers/labels/layouts/BoxLayout.js +49 -0
  48. package/dist/markers/labels/layouts/PathLayout.d.ts +11 -0
  49. package/dist/markers/labels/layouts/PathLayout.js +91 -0
  50. package/dist/markers/labels/layouts/PointLayout.d.ts +19 -0
  51. package/dist/markers/labels/layouts/PointLayout.js +94 -0
  52. package/dist/markers/regression/RegressionMarker.svelte +1 -1
  53. package/dist/markers/text/Covering.d.ts +13 -0
  54. package/dist/markers/text/Covering.js +29 -0
  55. package/dist/markers/text/Declutter.d.ts +24 -3
  56. package/dist/markers/text/Declutter.js +46 -3
  57. package/dist/markers/text/TextMarker.svelte +90 -17
  58. package/dist/markers/text/TextMarker.svelte.d.ts +1 -1
  59. package/dist/markers/tick/TickMarker.svelte +1 -1
  60. package/dist/markers/utils/MarkerChannels.d.ts +1 -1
  61. package/dist/markers/utils/MarkerChannels.js +3 -2
  62. package/dist/markers/violin/ViolinMarker.svelte +1 -1
  63. package/dist/markers/waffle/WaffleMarker.svelte +1 -1
  64. package/dist/phases/context/RowContext.d.ts +2 -0
  65. package/dist/phases/context/RowContext.js +2 -0
  66. package/dist/phases/markers/MarkerStyles.js +14 -11
  67. package/dist/phases/markers/MarkersPhase.d.ts +2 -3
  68. package/dist/phases/markers/MarkersPhase.js +17 -11
  69. package/dist/phases/statistics/MarkerGrouping.js +5 -32
  70. package/dist/phases/statistics/MarkerInheritance.d.ts +11 -0
  71. package/dist/phases/statistics/MarkerInheritance.js +25 -0
  72. package/dist/phases/statistics/MarkerOrientation.d.ts +26 -0
  73. package/dist/phases/statistics/MarkerOrientation.js +29 -0
  74. package/dist/phases/statistics/StatisticsPhase.js +15 -15
  75. package/dist/phases/transforms/TransformsPhase.js +5 -3
  76. package/dist/plots/correlation/heatmap/Plot.svelte +2 -2
  77. package/dist/plots/correlation/heatmap/Plot.svelte.d.ts +0 -1
  78. package/dist/plots/correlation/heatmap/defaults.js +14 -1
  79. package/dist/plots/correlation/heatmap/markers.d.ts +0 -6
  80. package/dist/plots/correlation/heatmap/markers.js +5 -9
  81. package/dist/plots/correlation/scatter/Plot.svelte +2 -2
  82. package/dist/plots/correlation/scatter/Plot.svelte.d.ts +0 -1
  83. package/dist/plots/correlation/scatter/defaults.js +9 -3
  84. package/dist/plots/correlation/scatter/markers.d.ts +0 -9
  85. package/dist/plots/correlation/scatter/markers.js +21 -28
  86. package/dist/plots/distribution/box/BoxSummaries.d.ts +5 -0
  87. package/dist/plots/distribution/box/BoxSummaries.js +9 -0
  88. package/dist/plots/distribution/box/Plot.svelte +2 -2
  89. package/dist/plots/distribution/box/Plot.svelte.d.ts +0 -1
  90. package/dist/plots/distribution/box/defaults.js +11 -0
  91. package/dist/plots/distribution/box/markers.js +10 -1
  92. package/dist/plots/distribution/density/Plot.svelte +2 -2
  93. package/dist/plots/distribution/density/Plot.svelte.d.ts +0 -1
  94. package/dist/plots/distribution/density/defaults.js +16 -0
  95. package/dist/plots/distribution/density/markers.js +5 -1
  96. package/dist/plots/distribution/histogram/Plot.svelte +2 -2
  97. package/dist/plots/distribution/histogram/Plot.svelte.d.ts +0 -1
  98. package/dist/plots/distribution/histogram/defaults.js +16 -11
  99. package/dist/plots/distribution/histogram/markers.js +3 -3
  100. package/dist/plots/distribution/pyramid/Plot.svelte +2 -2
  101. package/dist/plots/distribution/pyramid/Plot.svelte.d.ts +0 -1
  102. package/dist/plots/distribution/pyramid/defaults.js +9 -2
  103. package/dist/plots/distribution/pyramid/markers.d.ts +0 -8
  104. package/dist/plots/distribution/pyramid/markers.js +6 -16
  105. package/dist/plots/distribution/violin/Plot.svelte +2 -2
  106. package/dist/plots/distribution/violin/Plot.svelte.d.ts +0 -1
  107. package/dist/plots/distribution/violin/defaults.js +11 -0
  108. package/dist/plots/distribution/violin/markers.js +27 -3
  109. package/dist/plots/evolution/area/Plot.svelte +2 -2
  110. package/dist/plots/evolution/area/Plot.svelte.d.ts +0 -1
  111. package/dist/plots/evolution/area/defaults.js +4 -7
  112. package/dist/plots/evolution/area/markers.js +3 -1
  113. package/dist/plots/evolution/line/Plot.svelte +2 -2
  114. package/dist/plots/evolution/line/Plot.svelte.d.ts +0 -1
  115. package/dist/plots/evolution/line/defaults.js +4 -6
  116. package/dist/plots/evolution/line/markers.js +3 -1
  117. package/dist/plots/evolution/slope/Plot.svelte +2 -2
  118. package/dist/plots/evolution/slope/Plot.svelte.d.ts +0 -1
  119. package/dist/plots/evolution/slope/defaults.js +4 -3
  120. package/dist/plots/evolution/slope/markers.d.ts +4 -3
  121. package/dist/plots/evolution/slope/markers.js +16 -9
  122. package/dist/plots/evolution/sparkline/Plot.svelte +2 -2
  123. package/dist/plots/evolution/sparkline/Plot.svelte.d.ts +0 -1
  124. package/dist/plots/evolution/sparkline/defaults.js +1 -1
  125. package/dist/plots/evolution/sparkline/markers.js +3 -1
  126. package/dist/plots/generic/Plot.svelte +42 -0
  127. package/dist/plots/generic/Plot.svelte.d.ts +25 -0
  128. package/dist/plots/generic/defaults.d.ts +4 -0
  129. package/dist/plots/generic/defaults.js +7 -0
  130. package/dist/plots/map/geo/Plot.svelte +2 -2
  131. package/dist/plots/map/geo/Plot.svelte.d.ts +0 -1
  132. package/dist/plots/part-of-a-whole/treemap/Plot.svelte +2 -2
  133. package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +0 -1
  134. package/dist/plots/part-of-a-whole/treemap/defaults.js +2 -2
  135. package/dist/plots/ranking/bar/Plot.svelte +2 -2
  136. package/dist/plots/ranking/bar/Plot.svelte.d.ts +0 -1
  137. package/dist/plots/ranking/bar/defaults.js +13 -3
  138. package/dist/plots/ranking/bar/markers.js +10 -27
  139. package/dist/plots/ranking/bullet/Plot.svelte +2 -2
  140. package/dist/plots/ranking/bullet/Plot.svelte.d.ts +0 -1
  141. package/dist/plots/ranking/bullet/defaults.js +20 -0
  142. package/dist/plots/ranking/bullet/markers.js +7 -1
  143. package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +2 -9
  144. package/dist/plots/ranking/cleveland/ClevelandMarkers.js +65 -47
  145. package/dist/plots/ranking/cleveland/Plot.svelte +2 -2
  146. package/dist/plots/ranking/cleveland/Plot.svelte.d.ts +0 -1
  147. package/dist/plots/ranking/cleveland/defaults.js +33 -2
  148. package/dist/plots/ranking/dumbbell/Plot.svelte +2 -2
  149. package/dist/plots/ranking/dumbbell/Plot.svelte.d.ts +0 -1
  150. package/dist/plots/ranking/forest/Plot.svelte +2 -2
  151. package/dist/plots/ranking/forest/Plot.svelte.d.ts +0 -1
  152. package/dist/plots/ranking/lollipop/Plot.svelte +2 -2
  153. package/dist/plots/ranking/lollipop/Plot.svelte.d.ts +0 -1
  154. package/dist/types/layout/context.d.ts +2 -0
  155. package/dist/types/layout/styles/bullet.d.ts +4 -1
  156. package/dist/types/layout/styles/values.d.ts +51 -11
  157. package/dist/types/markers/axis/area.d.ts +5 -1
  158. package/dist/types/markers/axis/bar.d.ts +5 -2
  159. package/dist/types/markers/axis/bollinger.d.ts +2 -1
  160. package/dist/types/markers/axis/box.d.ts +2 -1
  161. package/dist/types/markers/axis/cell.d.ts +4 -1
  162. package/dist/types/markers/axis/line.d.ts +4 -1
  163. package/dist/types/markers/axis/rect.d.ts +4 -1
  164. package/dist/types/markers/axis/regression.d.ts +2 -1
  165. package/dist/types/markers/axis/tick.d.ts +2 -1
  166. package/dist/types/markers/axis/trail.d.ts +4 -1
  167. package/dist/types/markers/axis/violin.d.ts +2 -1
  168. package/dist/types/markers/axis/waffle.d.ts +5 -1
  169. package/dist/types/markers/basic/dot.d.ts +4 -1
  170. package/dist/types/markers/basic/text.d.ts +25 -21
  171. package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +5 -9
  172. package/dist/types/markers/channels/commonMarkerConfigWithOrientation.d.ts +5 -0
  173. package/dist/types/markers/channels/commonMarkerConfigWithOrientation.js +1 -0
  174. package/dist/types/markers/labels/box.d.ts +57 -0
  175. package/dist/types/markers/labels/box.js +1 -0
  176. package/dist/types/markers/labels/common.d.ts +15 -0
  177. package/dist/types/markers/labels/common.js +1 -0
  178. package/dist/types/markers/labels/path.d.ts +52 -0
  179. package/dist/types/markers/labels/path.js +1 -0
  180. package/dist/types/markers/labels/point.d.ts +40 -0
  181. package/dist/types/markers/labels/point.js +1 -0
  182. package/dist/types/markers/traits.d.ts +10 -0
  183. package/dist/types/phases/markers/phase.d.ts +1 -8
  184. package/dist/types/phases/statistics/phase.d.ts +8 -3
  185. package/dist/types/phases/styles/phase.d.ts +15 -3
  186. package/dist/types/phases/transforms/phase.d.ts +2 -0
  187. package/dist/types/plots/constants.d.ts +0 -12
  188. package/dist/types/plots/correlation/heatmap/props.d.ts +3 -2
  189. package/dist/types/plots/correlation/scatter/props.d.ts +3 -2
  190. package/dist/types/plots/distribution/box/props.d.ts +6 -2
  191. package/dist/types/plots/distribution/density/props.d.ts +2 -1
  192. package/dist/types/plots/distribution/histogram/props.d.ts +3 -2
  193. package/dist/types/plots/distribution/pyramid/props.d.ts +3 -2
  194. package/dist/types/plots/distribution/violin/props.d.ts +4 -1
  195. package/dist/types/plots/evolution/area/props.d.ts +3 -2
  196. package/dist/types/plots/evolution/line/props.d.ts +3 -2
  197. package/dist/types/plots/generic/props.d.ts +16 -0
  198. package/dist/types/plots/generic/props.js +1 -0
  199. package/dist/types/plots/generic/state.d.ts +4 -0
  200. package/dist/types/plots/generic/state.js +1 -0
  201. package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +4 -2
  202. package/dist/types/plots/ranking/bar/props.d.ts +3 -2
  203. package/dist/types/plots/ranking/cleveland/props.d.ts +3 -2
  204. package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -2
  205. package/dist/types/plots/ranking/forest/props.d.ts +5 -2
  206. package/dist/types/plots/ranking/lollipop/props.d.ts +3 -2
  207. package/docs/architecture.md +9 -9
  208. package/docs/markers.md +2 -4
  209. package/docs/plots/ranking/cleveland.md +1 -1
  210. package/docs/styles-and-segments.md +1 -14
  211. package/docs/value-labels.md +159 -0
  212. package/examples/BarValues.svelte +1 -1
  213. package/llms-full.txt +2 -2
  214. package/llms.txt +1 -0
  215. package/package.json +3 -1
  216. package/dist/markers/utils/BoxLabel.d.ts +0 -58
  217. package/dist/markers/utils/BoxLabel.js +0 -175
  218. package/dist/markers/utils/MarkerLabel.svelte +0 -33
  219. package/dist/markers/utils/MarkerLabel.svelte.d.ts +0 -10
  220. /package/dist/layout/plot/{Plot.svelte → AbstractPlot.svelte} +0 -0
@@ -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. */
@@ -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;
@@ -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
  }
@@ -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
@@ -1,38 +1,28 @@
1
- import { ProcessedPositions } from '../../../phases/transforms/ProcessedPositions';
2
1
  import { SERIES_KEY } from '../../../phases/transforms/TransformsPhase';
3
2
  import { ValueLabels } from '../../../layout/styles/ValueLabels';
4
3
  /**
5
- * ScatterPlot's `styles.values` as one `'text'` marker over the same
6
- * processed rows its dots draw, each labelled with its own `y` value. A label
7
- * sits beside its point and flips to the other side where its text would
8
- * cross the plot's edge (`edgeAware`).
4
+ * ScatterPlot's `styles.values` as labels of its dots: each labelled row with its own value, by default on a free
5
+ * spot around its point (`declutter: 'around'`) - a label with none is left out - or around its rim (`'path'`).
9
6
  */
10
- export function buildValueLabels(labels, state) {
11
- const { processedData } = state;
12
- if (!processedData?.data.length)
7
+ function buildValueLabels(labels, state, env) {
8
+ const rows = new Set(state.labelled ?? []);
9
+ if (rows.size === 0)
13
10
  return [];
14
- const { font, format } = labels;
15
- const position = ProcessedPositions.of(processedData);
16
11
  const nameOf = (d) => String(d[SERIES_KEY]);
17
- const valueOf = ValueLabels.valueOf(state);
12
+ const label = labels.label(env, (d) => labels.text(d), {
13
+ fill: 'currentColor',
14
+ seriesColor: (d) => state.colorForSeries?.(nameOf(d)),
15
+ fontSize: 11,
16
+ fontWeight: 500
17
+ });
18
+ if (labels.layout !== 'point')
19
+ return [label];
18
20
  return [
19
21
  {
20
- type: 'text',
21
- data: processedData.data,
22
- x: position.x,
23
- y: position.y,
24
- text: (d) => format(Number(valueOf(d)), d, nameOf(d)),
25
- styles: {
26
- ...font,
27
- fill: font?.fill ?? 'currentColor',
28
- fontSize: font?.fontSize ?? 11,
29
- fontWeight: font?.fontWeight ?? 500,
30
- dx: font?.dx ?? 8,
31
- dy: font?.dy ?? 0,
32
- textAnchor: labels.textAnchor ?? 'start',
33
- lineAnchor: font?.lineAnchor ?? 'middle'
34
- },
35
- edgeAware: true
22
+ ...label,
23
+ select: (row) => rows.has(row),
24
+ // A label on its own dot stays there; one beside it moves around it to a free spot.
25
+ declutter: labels.side === 'on' ? undefined : 'around'
36
26
  }
37
27
  ];
38
28
  }
@@ -126,5 +116,8 @@ export function buildMarks(state, env) {
126
116
  });
127
117
  }
128
118
  });
129
- return markers;
119
+ const labels = state.valueLabels?.show
120
+ ? buildValueLabels(state.valueLabels, state, env)
121
+ : undefined;
122
+ return ValueLabels.onMarkers(markers, 'dot', labels);
130
123
  }
@@ -1,6 +1,7 @@
1
1
  import type { HoverPosition, ProcessedRow } from '../../../types/phases';
2
2
  import type { BoxPlotState, BoxSummaryRow } from '../../../types/plots/distribution/box/state';
3
3
  import type { BoxSummary } from '../../../types/plots/distribution/box/props';
4
+ import type { PointGeometryOf } from '../../../types/markers/labels/point';
4
5
  /**
5
6
  * The boxes a box plot draws, one per series, category and facet panel - the same quartiles and whiskers
6
7
  * svelteplot's `BoxX`/`BoxY` compute, worked out once as each value's context ({@link context}), so its hover and
@@ -18,6 +19,10 @@ export declare class BoxSummaries {
18
19
  * Its own context is the box's, with the median for its value, and where it sits for `x` and `y`.
19
20
  */
20
21
  static of<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): BoxSummaryRow[];
22
+ /** Where each part of a box its value labels can write sits: beside the box, at the height of its value. */
23
+ static partPlacements<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): Record<string, Record<string, unknown>>;
24
+ /** Where a box's part label sits: at its category, level with the part's value, beside its band. */
25
+ static labelPoint<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): (value: symbol) => PointGeometryOf;
21
26
  /** Where a box's summary sits for hover: at its category, level with its median. */
22
27
  static position<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): HoverPosition;
23
28
  /**