@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
@@ -1,13 +1,30 @@
1
1
  import { MarkerStyles } from '../../phases/markers/MarkerStyles';
2
2
  import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
3
+ import { RowStyle } from '../../markers/utils/RowStyle';
3
4
  /**
4
5
  * The look the plot's hover markers give the shapes of every marker linked to the hover (`hover: true` or
5
6
  * `'style'`): the hovered ones take `active`, the others `others`, each over the shape's own style. A shape
6
7
  * the legend dims keeps its own fill and opacities; one the hover cannot reach (hidden, omitted) is untouched.
8
+ *
9
+ * The marker stays one marker over all its rows, whatever is hovered: each style the hover sets is a function of
10
+ * the row that asks the hover how it reaches it. Moving the pointer then only draws the marker's shapes again in
11
+ * their new look - their rows and options do not change, so svelteplot never works the plot's scales out again
12
+ * (it does so from every mark's data and options, and a style with no scale is never read for them).
7
13
  */
8
14
  export class HoverShapes {
9
15
  /** What the legend owns on a dimmed shape. */
10
- static #LEGEND_KEYS = ['fill', 'fillOpacity', 'strokeOpacity'];
16
+ static #LEGEND_KEYS = new Set(['fill', 'fillOpacity', 'strokeOpacity']);
17
+ /** The fields that draw a shape's outline. */
18
+ static #OUTLINE_KEYS = [
19
+ 'stroke',
20
+ 'strokeWidth',
21
+ 'strokeOpacity',
22
+ 'strokeDasharray'
23
+ ];
24
+ /** The fields that hold an opacity, fully opaque unless set. */
25
+ static #OPACITY_KEYS = new Set(['fillOpacity', 'strokeOpacity']);
26
+ /** The fields that hold a color: a string is the field of the rows it names, else the color itself. */
27
+ static #COLOR_KEYS = new Set(['fill', 'stroke']);
11
28
  /** A shape style's field as a `dot` marker names it. */
12
29
  static #DOT_KEYS = {
13
30
  fill: 'dotFill',
@@ -18,8 +35,15 @@ export class HoverShapes {
18
35
  strokeDasharray: 'dotStrokeDasharray'
19
36
  };
20
37
  #rules;
38
+ /** The fields any rule sets, in either role. */
39
+ #fields;
21
40
  constructor(markers) {
22
41
  this.#rules = markers.filter((m) => m.type === 'hover' && !!(m.active || m.others));
42
+ this.#fields = [
43
+ ...new Set(this.#rules.flatMap((rule) => [rule.active, rule.others].flatMap((style) => Object.entries(style ?? {})
44
+ .filter(([, value]) => value !== undefined)
45
+ .map(([name]) => name))))
46
+ ];
23
47
  }
24
48
  /** Whether any hover marker restyles shapes. */
25
49
  get any() {
@@ -31,73 +55,109 @@ export class HoverShapes {
31
55
  return hover === true || hover === 'style';
32
56
  }
33
57
  /**
34
- * `marker` in parts, each drawing the rows the hover styles alike - the shapes it does not style first, the
35
- * hovered ones last, so their outline is on top. A marker the hover does not style is one part, as it is.
58
+ * `marker` with each style the hover sets as a function of the row: the hovered rows take `active`, the others
59
+ * `others`, and while nothing is hovered every row its own style. A marker the hover does not style is returned
60
+ * as it is. `rows` are what the marker draws without `data` of its own.
61
+ *
62
+ * The outline `active` sets is not the shape's own: {@link outline} draws it over every shape, whole.
36
63
  */
37
- split(marker, rows, reach) {
38
- const whole = [{ key: '', marker }];
64
+ styled(marker, rows, reach) {
65
+ return this.#styled(marker, rows, reach, false);
66
+ }
67
+ /** {@link styled}, with `active`'s outline on the shapes themselves when `outlined`. */
68
+ #styled(marker, rows, reach, outlined) {
39
69
  if (!this.any || !HoverShapes.links(marker))
40
- return whole;
70
+ return marker;
71
+ const own = { ...marker.styles };
41
72
  const data = (marker.data ?? rows);
42
- const buckets = new Map();
43
- for (const row of data) {
44
- const key = this.#signature(row, reach);
45
- const bucket = buckets.get(key) ?? buckets.set(key, []).get(key);
46
- bucket.push(row);
73
+ const isDot = marker.type === 'dot';
74
+ const styles = { ...own };
75
+ for (const field of this.#fields) {
76
+ const name = isDot ? HoverShapes.#DOT_KEYS[field] : field;
77
+ const base = HoverShapes.#reader(field, own[name], data);
78
+ styles[name] = (row, index) => {
79
+ const value = this.#hovered(field, row, index, reach, outlined);
80
+ return value === undefined ? base(row, index) : value;
81
+ };
47
82
  }
48
- if (buckets.size === 1 && buckets.has(''))
49
- return whole;
50
- const plain = (key) => !key.includes('a');
51
- return [...buckets.entries()]
52
- .sort(([a], [b]) => Number(plain(b)) - Number(plain(a)))
53
- .map(([key, part]) => ({
54
- key,
55
- marker: key === '' ? { ...marker, data: part } : this.#restyled(marker, part, key, reach)
56
- }));
83
+ return MarkerStyles.restyle({ ...marker, styles }, { cfg: reach.cfg, rows: [...data] });
84
+ }
85
+ /** Whether any rule outlines the hovered shapes: an `active` that sets one of the stroke's fields. */
86
+ get outlines() {
87
+ return this.#rules.some((rule) => HoverShapes.#outlining(rule));
57
88
  }
58
89
  /**
59
- * How the hover styles `row`: for each rule, `a` (active), `o` (other) or `-` (not at all), and `~` when the
60
- * legend dims it. `''` while no rule styles it.
90
+ * The outline of `marker`'s hovered shapes, to draw over every shape of the plot: the marker over its hovered
91
+ * rows only, with no fill, no label and no pointer of its own. A shape is drawn in its marker's order, so its
92
+ * neighbours drawn after it would cover part of its outline - a treemap's tile, a heatmap's cell with no gap.
93
+ * `undefined` when no rule outlines the shapes or nothing of the marker is hovered.
61
94
  */
62
- #signature(row, reach) {
63
- const own = row[SERIES_KEY];
64
- const roles = this.#rules.map((rule) => {
65
- if (rule.series !== undefined && own !== rule.series)
66
- return '-';
67
- const role = reach.roleOf(row, rule.series);
68
- if (role === 'active')
69
- return rule.active ? 'a' : '-';
70
- if (role === 'other')
71
- return rule.others ? 'o' : '-';
72
- return '-';
73
- });
74
- if (roles.every((role) => role === '-'))
75
- return '';
76
- return roles.join('') + (reach.dimmed(row) ? '~' : '');
95
+ outline(marker, rows, reach) {
96
+ if (!this.outlines || !HoverShapes.links(marker))
97
+ return undefined;
98
+ const rules = this.#rules.filter((rule) => HoverShapes.#outlining(rule));
99
+ const data = (marker.data ?? rows).filter((row) => rules.some((rule) => (rule.series === undefined || row?.[SERIES_KEY] === rule.series) &&
100
+ reach.roleOf(row, rule.series) === 'active'));
101
+ if (data.length === 0)
102
+ return undefined;
103
+ const styled = this.#styled(marker, rows, reach, true);
104
+ const isDot = marker.type === 'dot';
105
+ const unfilled = isDot
106
+ ? { dotFill: 'none', dotFillOpacity: 0 }
107
+ : { fill: 'none', fillOpacity: 0 };
108
+ const rest = Object.fromEntries(Object.entries(styled).filter(([key]) => key !== 'label' && key !== 'hover' && !key.startsWith('on')));
109
+ return {
110
+ ...rest,
111
+ data,
112
+ styles: { ...styled.styles, ...unfilled }
113
+ };
77
114
  }
78
- /** `marker` over `rows`, with the styles `key` stands for over its own. */
79
- #restyled(marker, rows, key, reach) {
80
- const dimmed = key.endsWith('~');
81
- const styles = this.#rules.reduce((merged, rule, i) => {
82
- const role = key[i];
83
- const style = role === 'a' ? rule.active : role === 'o' ? rule.others : undefined;
84
- return {
85
- ...merged,
86
- ...HoverShapes.#named(HoverShapes.#defined(style, dimmed), marker)
87
- };
88
- }, { ...marker.styles });
89
- return MarkerStyles.restyle({ ...marker, data: rows, styles }, { cfg: reach.cfg, rows });
115
+ static #outlining(rule) {
116
+ const active = rule.active ?? {};
117
+ return HoverShapes.#OUTLINE_KEYS.some((key) => active[key] !== undefined);
90
118
  }
91
- /** `style` as `marker` names its fields: a `dot` marker's are its `dot` ones (`dotFill`...). */
92
- static #named(style, marker) {
93
- if (marker.type !== 'dot')
94
- return style;
95
- const keys = HoverShapes.#DOT_KEYS;
96
- return Object.fromEntries(Object.entries(style).map(([name, value]) => [keys[name] ?? name, value]));
119
+ /**
120
+ * What the hover sets `field` to on `row` - the last rule that reaches the row and sets it wins - or `undefined`
121
+ * when none does: the row is not hovered, or the legend dims it and `field` is the legend's. An outline `active`
122
+ * sets is left to the outline drawn over every shape, unless `outlined`.
123
+ */
124
+ #hovered(field, row, index, reach, outlined) {
125
+ let value;
126
+ let dimmed;
127
+ for (const rule of this.#rules) {
128
+ if (rule.series !== undefined && row?.[SERIES_KEY] !== rule.series)
129
+ continue;
130
+ const role = reach.roleOf(row, rule.series);
131
+ const style = role === 'active' ? rule.active : role === 'other' ? rule.others : undefined;
132
+ const own = style?.[field];
133
+ if (own === undefined)
134
+ continue;
135
+ if (role === 'active' &&
136
+ !outlined &&
137
+ HoverShapes.#OUTLINE_KEYS.includes(field) &&
138
+ HoverShapes.#outlining(rule))
139
+ continue;
140
+ dimmed ??= HoverShapes.#LEGEND_KEYS.has(field) && reach.dimmed(row);
141
+ if (dimmed)
142
+ return undefined;
143
+ value = typeof own === 'function' ? own(row, index) : own;
144
+ }
145
+ return value;
97
146
  }
98
- /** `style`'s set values, without what the legend owns on a dimmed shape. */
99
- static #defined(style, dimmed) {
100
- return Object.fromEntries(Object.entries(style ?? {}).filter(([name, value]) => value !== undefined &&
101
- !(dimmed && HoverShapes.#LEGEND_KEYS.includes(name))));
147
+ /** How the shape's own `style` for `field` reads on a row, as the marker would draw it. */
148
+ static #reader(field, style, rows) {
149
+ if (typeof style === 'function')
150
+ return style;
151
+ // An opacity is a channel of the mark, and svelteplot leaves out a row whose channel reads nothing.
152
+ if (style === undefined && HoverShapes.#OPACITY_KEYS.has(field))
153
+ return () => 1;
154
+ if (typeof style !== 'string' || field === 'strokeDasharray')
155
+ return () => style;
156
+ if (HoverShapes.#COLOR_KEYS.has(field)) {
157
+ const color = RowStyle.colorOf(style, rows);
158
+ return color ? (row) => color(row) : () => style;
159
+ }
160
+ // A number (an opacity, a width) a string names the field of.
161
+ return (row) => row?.[style];
102
162
  }
103
163
  }
@@ -37,6 +37,10 @@ export declare class Box {
37
37
  get empty(): boolean;
38
38
  /** How far this box reaches out from its origin (the point it sits around), in whole pixels. */
39
39
  get reach(): Margins;
40
+ /** Whether this box and `other` share any area. */
41
+ intersects(other: Box): boolean;
42
+ /** Whether this box lies wholly inside `bounds`. */
43
+ inside(bounds: Box): boolean;
40
44
  /** This box grown by `px` on every side. */
41
45
  pad(px: number): Box;
42
46
  /**
@@ -61,6 +61,20 @@ export class Box {
61
61
  get reach() {
62
62
  return this.overflow(Box.size(0, 0));
63
63
  }
64
+ /** Whether this box and `other` share any area. */
65
+ intersects(other) {
66
+ return (this.left < other.right &&
67
+ other.left < this.right &&
68
+ this.top < other.bottom &&
69
+ other.top < this.bottom);
70
+ }
71
+ /** Whether this box lies wholly inside `bounds`. */
72
+ inside(bounds) {
73
+ return (this.left >= bounds.left &&
74
+ this.right <= bounds.right &&
75
+ this.top >= bounds.top &&
76
+ this.bottom <= bounds.bottom);
77
+ }
64
78
  /** This box grown by `px` on every side. */
65
79
  pad(px) {
66
80
  return new Box(this.x - px, this.y - px, this.width + 2 * px, this.height + 2 * px);
@@ -1,4 +1,5 @@
1
1
  import { getContext, setContext } from 'svelte';
2
+ import { get } from 'svelte/store';
2
3
  import { Margins } from './Margins';
3
4
  import { Box } from './Box';
4
5
  import { CellAlignment } from '../grid/GridAlignment.svelte';
@@ -27,6 +28,17 @@ export class PlotMargins {
27
28
  #settled;
28
29
  /** What svelteplot's own `<Plot>` lays the plot out by, once {@link follow} is handed it. */
29
30
  #plot = $state.raw();
31
+ /** The room svelteplot's axes ask of each side, whether or not it is `'auto'`, once {@link follow} reads it. */
32
+ #autoMargins = $state.raw();
33
+ #auto = $derived.by(() => {
34
+ const maps = this.#autoMargins;
35
+ if (!maps)
36
+ return {};
37
+ return Object.fromEntries(Margins.SIDES.flatMap((side) => {
38
+ const values = [...maps[side].values()];
39
+ return values.length ? [[side, Math.max(...values)]] : [];
40
+ }));
41
+ });
30
42
  constructor(inputs) {
31
43
  this.#inputs = inputs;
32
44
  setContext(PLOT_MARGINS_KEY, this);
@@ -124,6 +136,10 @@ export class PlotMargins {
124
136
  */
125
137
  follow(plot) {
126
138
  this.#plot = plot;
139
+ const stores = getContext('svelteplot/autoMargins');
140
+ if (stores) {
141
+ this.#autoMargins = Object.fromEntries(Margins.SIDES.map((side) => [side, get(stores[PlotMargins.#AUTO_STORE[side]])]));
142
+ }
127
143
  const cell = this.#cell;
128
144
  if (!cell)
129
145
  return;
@@ -132,6 +148,12 @@ export class PlotMargins {
132
148
  cell.report(new Margins(Number(marginTop) || 0, Number(marginRight) || 0, Number(marginBottom) || 0, Number(marginLeft) || 0));
133
149
  });
134
150
  }
151
+ static #AUTO_STORE = {
152
+ top: 'autoMarginTop',
153
+ right: 'autoMarginRight',
154
+ bottom: 'autoMarginBottom',
155
+ left: 'autoMarginLeft'
156
+ };
135
157
  /** The room the `reserve` boxes take past the edges their points can sit on. */
136
158
  #reserved = $derived.by(() => this.#inputs.reserve().reduce((room, { box, edges }) => {
137
159
  const reach = box.reach;
@@ -143,7 +165,10 @@ export class PlotMargins {
143
165
  const settled = this.#settled;
144
166
  const { width, height } = this.#inputs.size();
145
167
  const growth = this.#measured.max(this.#reserved).min(Margins.half(width, height));
146
- const estimate = this.#inputs.estimate();
168
+ // A side grows from at least the room svelteplot's axes take on it: their ticks and title.
169
+ const estimate = this.#inputs
170
+ .estimate()
171
+ .map((value, side) => Math.max(value, this.#auto[side] ?? 0));
147
172
  const out = {};
148
173
  for (const side of Margins.SIDES) {
149
174
  const own = author?.[side];
@@ -45,6 +45,6 @@ interface $$IsomorphicComponent {
45
45
  <TRow extends Record<string, unknown>, TScales extends object, TCore extends PlotCoreBase>(internal: unknown, props: ReturnType<__sveltets_Render<TRow, TScales, TCore>['props']> & {}): ReturnType<__sveltets_Render<TRow, TScales, TCore>['exports']>;
46
46
  z_$$bindings?: ReturnType<__sveltets_Render<any, any, any>['bindings']>;
47
47
  }
48
- declare const Plot: $$IsomorphicComponent;
49
- type Plot<TRow extends Record<string, unknown>, TScales extends object, TCore extends PlotCoreBase> = InstanceType<typeof Plot<TRow, TScales, TCore>>;
50
- export default Plot;
48
+ declare const AbstractPlot: $$IsomorphicComponent;
49
+ type AbstractPlot<TRow extends Record<string, unknown>, TScales extends object, TCore extends PlotCoreBase> = InstanceType<typeof AbstractPlot<TRow, TScales, TCore>>;
50
+ export default AbstractPlot;
@@ -2,16 +2,22 @@ import { Box } from '../margins/Box';
2
2
  import { PlotAxes } from '../../phases/data/PlotAxes';
3
3
  import type { PlotRowContext } from '../../types/layout/context';
4
4
  import type { PlotStylesConfig } from '../../types/configuration/styling';
5
- import type { ValuesStyle } from '../../types/layout/styles';
5
+ import type { ValuesFontStyle, ValuesStyle } from '../../types/layout/styles';
6
6
  import type { ValueLabelsOptions } from '../../types/phases/styles/phase';
7
7
  import type { Series } from '../../types/plots/data/common';
8
8
  import type { FontStyle, StrokeStyle } from '../../types/plots/styling';
9
- import type { LabelAnchor, TextAnchor, ValueAnchor } from '../../types/plots/constants';
10
9
  import type { BoxAnchors, EdgeBox } from '../../types/layout/box';
11
10
  import type { MarginSide } from '../../types/plots/props';
12
11
  import type { AxisPlotEnv, AxisPlotState, ProcessedData, ProcessedRow } from '../../types/phases';
13
12
  import type { TextMarkerConfig } from '../../types/markers/common';
14
- import type { TextLabelConfig } from '../../types/markers/basic/text';
13
+ import type { LabelLayout, MarkerLabelConfig } from '../../types/markers/labels/common';
14
+ import type { PointGeometryOf, PointLabelConfig, PointSide } from '../../types/markers/labels/point';
15
+ /** One part of each row a plot labels, with where its label sits and how it writes it. */
16
+ type ValuePart = {
17
+ name: string;
18
+ placement: Record<string, unknown>;
19
+ format: (value: number, row?: object, series?: string, part?: string) => string;
20
+ };
15
21
  /**
16
22
  * A plot's value labels: the author's `styles.values` over the plot kind's
17
23
  * own defaults, how their text measures, where they are expected to sit
@@ -21,15 +27,24 @@ import type { TextLabelConfig } from '../../types/markers/basic/text';
21
27
  export declare class ValueLabels {
22
28
  #private;
23
29
  readonly show: boolean;
24
- readonly anchor: ValueAnchor;
30
+ /** How the labels are laid out on their shapes: the author's `layout`, else the kind's. */
31
+ readonly layout: LabelLayout;
32
+ /** The options that place a label in its {@link layout}: the author's over the kind's own for it. */
33
+ readonly placement: Record<string, unknown>;
34
+ /** The parts of each row labelled - a simple plot's one `value`, a box's `median`... - each with its own options. */
35
+ readonly parts: ValuePart[];
36
+ /** The series whose rows are labelled, when `styles.values.series` names some; every series else. */
37
+ readonly series: string[] | undefined;
38
+ /** Whether the author gave the labels a text of their own, beyond the value. */
39
+ readonly formatted: boolean;
25
40
  /**
26
41
  * A label's text for `value`: the author's `format`, else the kind's, else the value as the display fields ask.
27
42
  * `row` is the row it labels, whose context the format reads; `series` names its series where there is no row
28
- * (a label measured ahead of the rows).
43
+ * (a label measured ahead of the rows); `part`, which of the row's values it writes.
29
44
  */
30
- readonly format: (value: number, row?: object, series?: string) => string;
45
+ readonly format: (value: number, row?: object, series?: string, part?: string) => string;
31
46
  readonly hoverIsolate: boolean;
32
- readonly font: FontStyle | undefined;
47
+ readonly font: ValuesFontStyle | undefined;
33
48
  readonly fontSize: number;
34
49
  /** The connectors of labels that had to move apart. */
35
50
  readonly strokeStyle: StrokeStyle | undefined;
@@ -39,11 +54,45 @@ export declare class ValueLabels {
39
54
  readonly declutter: PlotStylesConfig['text']['declutter'];
40
55
  /** Where the labels are expected to sit: boxes around their points, on the edges those can reach. */
41
56
  readonly reach: EdgeBox[];
42
- constructor(values: ValuesStyle | undefined, cfg: PlotStylesConfig, options?: Omit<ValueLabelsOptions<never>, 'reach'>, reach?: (labels: ValueLabels) => EdgeBox[]);
57
+ constructor(values: ValuesStyle | undefined, cfg: PlotStylesConfig, options?: Omit<ValueLabelsOptions<never>, 'reach' | 'layout' | 'layouts' | 'parts' | 'formatFor'> & {
58
+ layout?: LabelLayout;
59
+ layouts?: Partial<Record<LabelLayout, Record<string, unknown>>>;
60
+ parts?: {
61
+ placements: Record<string, Record<string, unknown>>;
62
+ default: string[];
63
+ };
64
+ }, reach?: (labels: ValueLabels) => EdgeBox[]);
65
+ /** Whether the part named `name` is labelled: a plot writing its `value` on its own shapes asks first. */
66
+ writes(name: string): boolean;
67
+ /** Whether the labels draw: `show` as set, else when any other option of them is set, else the kind's default. */
68
+ static shows(values: ValuesStyle | undefined, fallback?: boolean): boolean;
43
69
  /** The context a label's `format` reads: its row's, with `series` when the row gives none. */
44
70
  static contextOf(row: object | undefined, series?: string): PlotRowContext;
71
+ /**
72
+ * The value a label writes for `row`, read off its context: its `value`, or one `part` of it on a plot whose rows
73
+ * have several. `undefined` when it is not a number.
74
+ */
75
+ static valueIn(row: object | undefined, part?: string): number | undefined;
76
+ /** A label's text for `row`: its context's `part` (its `value` by default), as that part's format writes it. */
77
+ text(row: object, part?: string): string;
45
78
  /** A plot's value labels: `values` over `options`, the kind's own defaults, their reach read off `state`. */
46
79
  static build<TState>(values: ValuesStyle | undefined, cfg: PlotStylesConfig, options: ValueLabelsOptions<TState>, state: TState): ValueLabels;
80
+ /** Whether the labels sit past the tip of their shapes, where the plot's margins keep room for them. */
81
+ get pastTip(): boolean;
82
+ /**
83
+ * The `label` a plot kind's shapes write their value in, in this plot's {@link layout}: `text`, over what every
84
+ * label shares ({@link hooks}). Inside its shape a label takes no `fill` of its own, so it is written in the color
85
+ * that reads best on the shape; past it, the plot's `fill`.
86
+ */
87
+ label<TLabel extends MarkerLabelConfig = MarkerLabelConfig>(env: AxisPlotEnv, text: (row: ProcessedRow) => string, defaults?: {
88
+ fill?: string;
89
+ fontSize?: number;
90
+ fontWeight?: FontStyle['fontWeight'];
91
+ /** The rows `styles.values.select` picks: a `'point'` label writes only theirs. */
92
+ labelled?: readonly ProcessedRow[];
93
+ /** A row's series color, which `color: 'series'` writes a label off its shape in. */
94
+ seriesColor?: (row: ProcessedRow) => string | undefined;
95
+ }): TLabel;
47
96
  /** The configuration the labels were built under, for a plot kind's own label defaults. */
48
97
  get config(): PlotStylesConfig;
49
98
  /**
@@ -56,8 +105,8 @@ export declare class ValueLabels {
56
105
  get lineHeight(): number;
57
106
  /** The box one label's `text` takes. */
58
107
  textBox(text: string): Box;
59
- /** The labels' gap (px) from their mark along `axis`: their own `dx`/`dy`, else `fallback`. */
60
- offset(axis: 'x' | 'y', fallback?: number): number;
108
+ /** The labels' gap (px) from their mark: the layout's `gap`, else `fallback`. */
109
+ offset(fallback?: number): number;
61
110
  /**
62
111
  * Where a label reading `text` sits around its point: anchored `at` that
63
112
  * offset from it (its `dx`/`dy`), on `side` of that anchor per axis - the
@@ -76,48 +125,12 @@ export declare class ValueLabels {
76
125
  series: S;
77
126
  value: number;
78
127
  }[];
79
- /**
80
- * The author's own `textAnchor`, if any. `'outside'` is a hover marker's
81
- * per-point anchor; a label's own `anchor` already places it, so here it
82
- * counts as unset.
83
- */
84
- get textAnchor(): Exclude<TextAnchor, 'outside'> | undefined;
85
128
  /**
86
129
  * What every label marker shares: the plot's margins grow to fit it, it
87
130
  * dims with its row's series, and with `hoverIsolate`, hovering it
88
131
  * isolates that series. A row without a series gets the margins only.
89
132
  */
90
133
  hooks(env: AxisPlotEnv): Pick<TextMarkerConfig, 'fitMargins' | 'onpointerenter' | 'onpointerleave' | 'styles'>;
91
- /**
92
- * Where in a bar a label goes, from the `anchor` of `styles.values`: its `label` anchor, and whether it is beyond
93
- * the bar. `negative` tells a bar that runs to the left (or down) from its base from one that runs right (or up),
94
- * which `'tip'`, `'base'` and `'outside'` follow; the others are the edges of the page. A `stacked` bar's label is
95
- * always in the middle. An anchor the shapes do not read (`'start'`, `'end'`) is taken for `'outside'`, the default.
96
- */
97
- static boxAnchor(anchor: ValueAnchor, shape: {
98
- isHorizontal: boolean;
99
- negative?: boolean;
100
- stacked?: boolean;
101
- }): {
102
- anchor: LabelAnchor;
103
- outside?: boolean;
104
- };
105
- /** Whether a label at `anchor` goes to one side or the other of a bar with the sign of its value. */
106
- static followsSign(anchor: ValueAnchor): boolean;
107
- /**
108
- * The `label` the shapes of a plot kind write their value in: this plot's `styles.values` over the `defaults` the kind
109
- * sets, with what every label shares ({@link hooks}) - its dimming and isolating of a series. A label inside its shape
110
- * never reaches out of the plot, so only one `outside` has the margins grow to hold it; and it takes the default
111
- * `fill` only `outside`, for inside it is written in the color that contrasts with its shape.
112
- */
113
- shapeLabel(env: AxisPlotEnv, text: (row: ProcessedRow) => string, defaults?: {
114
- fill?: string;
115
- fontSize?: number;
116
- fontWeight?: FontStyle['fontWeight'];
117
- anchor?: LabelAnchor;
118
- outside?: boolean;
119
- gap?: number;
120
- }): TextLabelConfig;
121
134
  /**
122
135
  * A processed row's own value, read through its series' own value channel
123
136
  * (`y`, or `x` on a horizontal plot) - what a label shows, even where a
@@ -129,24 +142,61 @@ export declare class ValueLabels {
129
142
  axes?: PlotAxes;
130
143
  }): (d: ProcessedRow) => unknown;
131
144
  /**
132
- * A series-over-`x` plot's (line, area) labels: one `'text'` marker over
133
- * the processed rows, each visible series' last row with a value
134
- * (`filter` + `selectLast`, per series), at its top (a stacked row's
135
- * `y2`), labelled with its own value and placed by `anchor`. Labels that
136
- * would overlap fan out into a shared column (`declutter`), colored by the
137
- * segment the row falls in. A plot that labels each end of its series apart
138
- * passes the `rows` and `anchor` of one end, and `lane` to route its labels
139
- * out to a lane whether or not they overlap (`flip: false` keeps one on the
140
- * left in the margin instead of flipping it over its points).
145
+ * A series-over-`x` plot's (line, area) labels, for its line or area markers: each labelled row (by default its
146
+ * series' last) with its own value, on the `side` of it its placement asks, colored by the segment it falls in.
147
+ * Labels that would overlap fan out into a shared column. A plot that labels each end of its series apart passes
148
+ * the `rows` and `side` of one end, and `lane` to route them out to a lane whether or not they overlap.
141
149
  */
142
- atSeriesEnds<TData extends Record<string, unknown>>(state: AxisPlotState<TData, object>, end?: {
150
+ atSeriesEnds<TData extends Record<string, unknown>>(state: AxisPlotState<TData, object>, env: AxisPlotEnv, end?: {
143
151
  rows?: ProcessedRow[];
144
- anchor?: ValueAnchor;
152
+ side?: PointSide;
145
153
  lane?: boolean | {
146
154
  elbowGap: number;
147
155
  };
148
156
  flip?: boolean;
157
+ }): PointLabelConfig[];
158
+ /**
159
+ * The labels of a plot whose rows have several values (a box's quartiles, a bullet's target): one `text` marker per
160
+ * part labelled, each of `rows` written at that part's value in its context - one label per value, for a part
161
+ * holding several (a box's outliers). `at` places a label at a value: its point, and the shape around it its
162
+ * `anchor` reads.
163
+ */
164
+ partMarkers(env: AxisPlotEnv, rows: readonly ProcessedRow[], at: (value: symbol) => PointGeometryOf, defaults?: {
165
+ seriesColor?: (row: ProcessedRow) => string | undefined;
166
+ /** The parts the kind writes on its own shapes, left out here: a bar's `value`. */
167
+ skip?: string[];
149
168
  }): TextMarkerConfig[];
169
+ /**
170
+ * Where a part label of a row sits on a plot of categories: at the row's `category` (a field or a function of
171
+ * it), level with the part's value, across the category's band - which an `anchor` reads, to write it beside.
172
+ */
173
+ static bandPoint(state: {
174
+ axes?: PlotAxes;
175
+ }, category: unknown,
176
+ /** How wide the shape is across its band, in pixels, when it does not fill it: a violin's inner box. */
177
+ across?: number,
178
+ /** A row's slot of its band, when its series share it side by side. */
179
+ slot?: (row: ProcessedRow) => {
180
+ index: number;
181
+ count: number;
182
+ } | undefined): (value: symbol) => PointGeometryOf;
183
+ /** The placement that writes each of `parts` beside its category's band: to its right, or above sideways bands. */
184
+ static besideBand(state: {
185
+ axes?: PlotAxes;
186
+ }, parts: string[]): Record<string, Record<string, unknown>>;
187
+ /** A series-over-`x` plot's labels in its {@link layout}: at its series' ends, or along its lines. */
188
+ forSeries<TData extends Record<string, unknown>>(state: AxisPlotState<TData, object>, env: AxisPlotEnv): MarkerLabelConfig[];
189
+ /** The side of its point a `'point'` label is written on: its placement's, else after it. */
190
+ get side(): PointSide;
191
+ /**
192
+ * A series-over-`x` plot's `'path'` labels, for its line or area markers: each series' name - or, with a `format`,
193
+ * the text it writes for the series' first row - along its line, colored by the series.
194
+ */
195
+ alongSeries<TData extends Record<string, unknown>>(state: AxisPlotState<TData, object>, env: AxisPlotEnv): MarkerLabelConfig[];
196
+ /** `markers` with `labels` on each one of `type`: the shapes of a series carry its labels. */
197
+ static onMarkers<T extends {
198
+ type: string;
199
+ }>(markers: T[], type: string, labels: MarkerLabelConfig[] | undefined): T[];
150
200
  /** How far (px) a lane takes the label of a series' first point from it - clear of the line that leaves it. */
151
201
  static readonly FIRST_LANE_GAP = 28;
152
202
  /** The rows of a series-over-`x` plot that cannot be labelled: a gap, or a stack with no top. For the "transforms" phase's `labels.skip`. */
@@ -163,5 +213,6 @@ export declare class ValueLabels {
163
213
  * domain when the scales set one, so it holds across a timeline; else off
164
214
  * every value.
165
215
  */
166
- reachAtSeriesEnds<TData extends Record<string, unknown>>(state: AxisPlotState<TData, object>, anchor?: ValueAnchor): EdgeBox[];
216
+ reachAtSeriesEnds<TData extends Record<string, unknown>>(state: AxisPlotState<TData, object>, pointSide?: PointSide): EdgeBox[];
167
217
  }
218
+ export {};