@fundar/data-chart-telling 1.17.0 → 1.18.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 (205) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/configuration/config.svelte.js +3 -2
  3. package/dist/index.d.ts +7 -2
  4. package/dist/index.js +2 -0
  5. package/dist/layout/geometry/GeometryLayout.svelte +188 -54
  6. package/dist/layout/hover/HoverDefaults.d.ts +52 -0
  7. package/dist/layout/hover/HoverDefaults.js +96 -0
  8. package/dist/layout/hover/HoverLayers.d.ts +35 -0
  9. package/dist/layout/hover/HoverLayers.js +122 -0
  10. package/dist/layout/hover/HoverSettings.d.ts +1 -3
  11. package/dist/layout/hover/HoverSettings.js +3 -2
  12. package/dist/layout/hover/HoverShapes.d.ts +44 -0
  13. package/dist/layout/hover/HoverShapes.js +103 -0
  14. package/dist/layout/hover/PlotHover.svelte.d.ts +18 -0
  15. package/dist/layout/hover/PlotHover.svelte.js +54 -1
  16. package/dist/layout/legend/LegendState.svelte.d.ts +2 -0
  17. package/dist/layout/legend/LegendState.svelte.js +5 -0
  18. package/dist/layout/plot/Core.svelte +2 -2
  19. package/dist/layout/plot/PublicPlotContext.d.ts +6 -0
  20. package/dist/layout/plot/PublicPlotContext.js +8 -0
  21. package/dist/layout/styles/ValueLabels.d.ts +12 -4
  22. package/dist/layout/styles/ValueLabels.js +22 -13
  23. package/dist/layout/tooltip/TooltipState.svelte.d.ts +8 -9
  24. package/dist/layout/tooltip/TooltipState.svelte.js +28 -29
  25. package/dist/markers/MarkerKinds.d.ts +5 -0
  26. package/dist/markers/MarkerKinds.js +1 -0
  27. package/dist/markers/band/BandMarker.svelte +74 -0
  28. package/dist/markers/band/BandMarker.svelte.d.ts +7 -0
  29. package/dist/markers/band/Bands.d.ts +16 -0
  30. package/dist/markers/band/Bands.js +48 -0
  31. package/dist/markers/bar/BarMarker.svelte +23 -66
  32. package/dist/markers/hover/HoverMarker.svelte +7 -265
  33. package/dist/markers/hover/HoverMarker.svelte.d.ts +4 -27
  34. package/dist/markers/utils/PointPixel.d.ts +37 -0
  35. package/dist/markers/utils/PointPixel.js +44 -0
  36. package/dist/phases/Pipeline.d.ts +7 -7
  37. package/dist/phases/Pipeline.js +12 -9
  38. package/dist/phases/context/ContextPhase.d.ts +22 -0
  39. package/dist/phases/context/ContextPhase.js +23 -0
  40. package/dist/phases/context/RowContext.d.ts +35 -0
  41. package/dist/phases/context/RowContext.js +95 -0
  42. package/dist/phases/data/DataPhase.d.ts +4 -4
  43. package/dist/phases/data/DataPhase.js +7 -7
  44. package/dist/phases/hover/HoverPhase.d.ts +6 -7
  45. package/dist/phases/hover/HoverPhase.js +28 -23
  46. package/dist/phases/mapping/MappingPhase.d.ts +4 -4
  47. package/dist/phases/mapping/MappingPhase.js +2 -2
  48. package/dist/phases/markers/MarkerStyles.d.ts +8 -0
  49. package/dist/phases/markers/MarkerStyles.js +15 -0
  50. package/dist/phases/markers/MarkersPhase.d.ts +4 -4
  51. package/dist/phases/markers/MarkersPhase.js +4 -4
  52. package/dist/phases/scales/ScalesPhase.d.ts +4 -4
  53. package/dist/phases/scales/ScalesPhase.js +4 -4
  54. package/dist/phases/sharedScales/SharedScalesPhase.d.ts +2 -2
  55. package/dist/phases/sharedScales/SharedScalesPhase.js +2 -2
  56. package/dist/phases/statistics/MarkerGrouping.d.ts +0 -9
  57. package/dist/phases/statistics/MarkerGrouping.js +0 -16
  58. package/dist/phases/styles/StylesPhase.d.ts +3 -3
  59. package/dist/phases/styles/StylesPhase.js +2 -2
  60. package/dist/phases/tooltip/TooltipPhase.d.ts +4 -4
  61. package/dist/phases/tooltip/TooltipPhase.js +10 -7
  62. package/dist/phases/transforms/TransformsPhase.d.ts +3 -3
  63. package/dist/phases/transforms/TransformsPhase.js +2 -2
  64. package/dist/plots/correlation/heatmap/defaults.d.ts +2 -2
  65. package/dist/plots/correlation/heatmap/defaults.js +21 -6
  66. package/dist/plots/correlation/heatmap/markers.d.ts +2 -2
  67. package/dist/plots/correlation/heatmap/markers.js +4 -3
  68. package/dist/plots/correlation/scatter/defaults.d.ts +2 -2
  69. package/dist/plots/correlation/scatter/defaults.js +9 -3
  70. package/dist/plots/correlation/scatter/markers.d.ts +2 -2
  71. package/dist/plots/correlation/scatter/markers.js +4 -3
  72. package/dist/plots/distribution/box/BoxSummaries.d.ts +17 -8
  73. package/dist/plots/distribution/box/BoxSummaries.js +90 -40
  74. package/dist/plots/distribution/box/defaults.d.ts +2 -2
  75. package/dist/plots/distribution/box/defaults.js +9 -6
  76. package/dist/plots/distribution/box/markers.d.ts +2 -2
  77. package/dist/plots/distribution/box/markers.js +3 -3
  78. package/dist/plots/distribution/density/defaults.d.ts +2 -2
  79. package/dist/plots/distribution/density/defaults.js +17 -1
  80. package/dist/plots/distribution/density/markers.d.ts +2 -2
  81. package/dist/plots/distribution/density/markers.js +4 -4
  82. package/dist/plots/distribution/histogram/defaults.d.ts +2 -2
  83. package/dist/plots/distribution/histogram/defaults.js +20 -25
  84. package/dist/plots/distribution/histogram/markers.d.ts +2 -2
  85. package/dist/plots/distribution/histogram/markers.js +5 -4
  86. package/dist/plots/distribution/pyramid/defaults.d.ts +2 -2
  87. package/dist/plots/distribution/pyramid/defaults.js +4 -1
  88. package/dist/plots/distribution/pyramid/markers.d.ts +2 -2
  89. package/dist/plots/distribution/pyramid/markers.js +4 -3
  90. package/dist/plots/distribution/violin/defaults.d.ts +2 -2
  91. package/dist/plots/distribution/violin/defaults.js +9 -7
  92. package/dist/plots/distribution/violin/markers.d.ts +2 -2
  93. package/dist/plots/distribution/violin/markers.js +3 -3
  94. package/dist/plots/evolution/area/defaults.d.ts +2 -2
  95. package/dist/plots/evolution/area/defaults.js +1 -1
  96. package/dist/plots/evolution/area/markers.d.ts +2 -2
  97. package/dist/plots/evolution/area/markers.js +4 -4
  98. package/dist/plots/evolution/line/defaults.d.ts +2 -2
  99. package/dist/plots/evolution/line/defaults.js +1 -1
  100. package/dist/plots/evolution/line/markers.d.ts +2 -2
  101. package/dist/plots/evolution/line/markers.js +3 -3
  102. package/dist/plots/evolution/slope/defaults.d.ts +2 -2
  103. package/dist/plots/evolution/slope/defaults.js +8 -4
  104. package/dist/plots/evolution/slope/markers.d.ts +2 -2
  105. package/dist/plots/evolution/slope/markers.js +3 -3
  106. package/dist/plots/evolution/sparkline/defaults.d.ts +2 -2
  107. package/dist/plots/evolution/sparkline/defaults.js +5 -2
  108. package/dist/plots/evolution/sparkline/markers.d.ts +2 -2
  109. package/dist/plots/evolution/sparkline/markers.js +3 -3
  110. package/dist/plots/map/geo/Core.svelte +3 -3
  111. package/dist/plots/map/geo/defaults.d.ts +2 -2
  112. package/dist/plots/map/geo/phases/data.d.ts +1 -1
  113. package/dist/plots/map/geo/phases/data.js +1 -1
  114. package/dist/plots/map/geo/phases/hover.d.ts +2 -2
  115. package/dist/plots/map/geo/phases/hover.js +3 -3
  116. package/dist/plots/map/geo/phases/markers.d.ts +2 -2
  117. package/dist/plots/map/geo/phases/markers.js +3 -3
  118. package/dist/plots/map/geo/phases/scales.d.ts +2 -2
  119. package/dist/plots/map/geo/phases/scales.js +2 -2
  120. package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +2 -2
  121. package/dist/plots/part-of-a-whole/treemap/defaults.js +37 -21
  122. package/dist/plots/ranking/bar/defaults.d.ts +2 -2
  123. package/dist/plots/ranking/bar/defaults.js +13 -7
  124. package/dist/plots/ranking/bar/markers.d.ts +3 -3
  125. package/dist/plots/ranking/bar/markers.js +8 -7
  126. package/dist/plots/ranking/bullet/defaults.d.ts +2 -2
  127. package/dist/plots/ranking/bullet/defaults.js +4 -4
  128. package/dist/plots/ranking/bullet/markers.d.ts +3 -3
  129. package/dist/plots/ranking/bullet/markers.js +9 -9
  130. package/dist/plots/ranking/cleveland/ClevelandMarkers.d.ts +3 -3
  131. package/dist/plots/ranking/cleveland/ClevelandMarkers.js +11 -10
  132. package/dist/plots/ranking/cleveland/defaults.d.ts +2 -2
  133. package/dist/plots/ranking/cleveland/defaults.js +7 -4
  134. package/dist/plots/ranking/dumbbell/defaults.d.ts +2 -2
  135. package/dist/plots/ranking/forest/defaults.d.ts +2 -2
  136. package/dist/plots/ranking/lollipop/defaults.d.ts +2 -2
  137. package/dist/types/configuration/styling.d.ts +4 -0
  138. package/dist/types/layout/context.d.ts +21 -0
  139. package/dist/types/layout/context.js +1 -0
  140. package/dist/types/layout/hover.d.ts +3 -0
  141. package/dist/types/layout/styles/values.d.ts +8 -2
  142. package/dist/types/layout/tooltip.d.ts +5 -10
  143. package/dist/types/markers/axis/axisBasedMarkersConfig.d.ts +2 -1
  144. package/dist/types/markers/axis/band.d.ts +24 -0
  145. package/dist/types/markers/axis/band.js +1 -0
  146. package/dist/types/markers/axis/bar.d.ts +2 -2
  147. package/dist/types/markers/axis/hover.d.ts +60 -15
  148. package/dist/types/markers/channels/commonMarkerConfig.d.ts +6 -2
  149. package/dist/types/markers/common.d.ts +1 -0
  150. package/dist/types/markers/common.js +1 -0
  151. package/dist/types/phases/context/phase.d.ts +7 -0
  152. package/dist/types/phases/context/phase.js +1 -0
  153. package/dist/types/phases/data/phase.d.ts +2 -2
  154. package/dist/types/phases/hover/phase.d.ts +10 -7
  155. package/dist/types/phases/index.d.ts +2 -1
  156. package/dist/types/phases/kind.d.ts +11 -8
  157. package/dist/types/phases/mapping/phase.d.ts +2 -2
  158. package/dist/types/phases/markers/phase.d.ts +3 -3
  159. package/dist/types/phases/scales/phase.d.ts +4 -4
  160. package/dist/types/phases/stage.d.ts +3 -3
  161. package/dist/types/phases/state.d.ts +1 -1
  162. package/dist/types/phases/styles/phase.d.ts +3 -2
  163. package/dist/types/phases/transforms/phase.d.ts +2 -2
  164. package/dist/types/plots/constants.d.ts +3 -5
  165. package/dist/types/plots/correlation/heatmap/state.d.ts +2 -2
  166. package/dist/types/plots/correlation/scatter/state.d.ts +2 -2
  167. package/dist/types/plots/distribution/box/props.d.ts +23 -1
  168. package/dist/types/plots/distribution/box/state.d.ts +6 -17
  169. package/dist/types/plots/distribution/density/state.d.ts +2 -2
  170. package/dist/types/plots/distribution/histogram/props.d.ts +14 -1
  171. package/dist/types/plots/distribution/histogram/state.d.ts +2 -2
  172. package/dist/types/plots/distribution/pyramid/state.d.ts +2 -2
  173. package/dist/types/plots/distribution/violin/props.d.ts +2 -1
  174. package/dist/types/plots/distribution/violin/state.d.ts +2 -2
  175. package/dist/types/plots/evolution/area/state.d.ts +2 -2
  176. package/dist/types/plots/evolution/line/state.d.ts +2 -2
  177. package/dist/types/plots/evolution/slope/state.d.ts +2 -2
  178. package/dist/types/plots/evolution/sparkline/state.d.ts +2 -2
  179. package/dist/types/plots/map/geo/state.d.ts +1 -1
  180. package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +14 -4
  181. package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +2 -2
  182. package/dist/types/plots/props.d.ts +3 -0
  183. package/dist/types/plots/ranking/bar/state.d.ts +2 -2
  184. package/dist/types/plots/ranking/bullet/state.d.ts +2 -2
  185. package/dist/types/plots/ranking/cleveland/state.d.ts +2 -2
  186. package/dist/types/plots/ranking/dumbbell/state.d.ts +2 -2
  187. package/dist/types/plots/ranking/forest/state.d.ts +2 -2
  188. package/dist/types/plots/ranking/lollipop/state.d.ts +2 -2
  189. package/docs/markers.md +34 -2
  190. package/docs/plots/evolution/line.md +1 -1
  191. package/docs/plots/evolution/slope.md +1 -1
  192. package/docs/plots/part-of-a-whole/treemap.md +36 -2
  193. package/docs/plots/ranking/bar.md +1 -0
  194. package/docs/plots/ranking/cleveland.md +1 -1
  195. package/docs/state.md +33 -0
  196. package/docs/styles-and-segments.md +2 -2
  197. package/docs/theming.md +2 -1
  198. package/docs/tooltip-and-hover.md +81 -3
  199. package/examples/LineDates.svelte +1 -1
  200. package/examples/ResponsiveLine.svelte +3 -1
  201. package/examples/TreemapHover.svelte +25 -0
  202. package/examples/ValueLabels.svelte +1 -1
  203. package/llms-full.txt +8 -6
  204. package/llms.txt +2 -1
  205. package/package.json +1 -1
@@ -0,0 +1,122 @@
1
+ import { NumberFormat } from '../../utils/NumberFormat';
2
+ /**
3
+ * The markers a `hover` marker draws in its `back` and `front` layers, over the hovered points: each with the
4
+ * points as its rows, unless it has `data` of its own, placed at them, and split by the slice of the band a point
5
+ * sits in (a grouped bar's series), to be moved into it - all but a `band`, which takes the slice itself.
6
+ */
7
+ export class HoverLayers {
8
+ /** The kinds placed by `x` and `y`, which default to the point's. */
9
+ static #PLACED = new Set(['dot', 'text', 'tick', 'image', 'line', 'trail']);
10
+ /** `points` with their label written as `marker`'s number fields, or its `format`, ask. */
11
+ static labelled(points, marker, cfg) {
12
+ const numberOf = NumberFormat.maybe(NumberFormat.displayOf(marker), cfg);
13
+ const number = numberOf ?? NumberFormat.of(undefined, cfg);
14
+ const { format } = marker;
15
+ if (!format && !numberOf)
16
+ return [...points];
17
+ return points.map((point) => {
18
+ if (format)
19
+ return { ...point, label: format(point, number) };
20
+ const value = Number(point.label);
21
+ const written = point.label !== undefined && point.label !== '' && Number.isFinite(value)
22
+ ? numberOf(value)
23
+ : point.label;
24
+ return { ...point, label: written ?? '' };
25
+ });
26
+ }
27
+ /**
28
+ * The markers of `marker`'s `layer` over a group of points. A marker over points that sit in a slice of their
29
+ * band is one part per slice, which `slice` names, to be moved into it as it is drawn - the move is in pixels,
30
+ * so it is left to the drawing, which knows the band's width.
31
+ */
32
+ static parts(marker, layer, group, cfg) {
33
+ if (group.points.length === 0)
34
+ return [];
35
+ return (marker[layer] ?? []).flatMap((nested, i) => {
36
+ const key = `${layer}-${group.key}-${i}`;
37
+ const { points: which, ...own } = nested;
38
+ if (own.data)
39
+ return [{ key, marker: own }];
40
+ const rows = HoverLayers.#pointsOf(which, group);
41
+ if (rows.length === 0)
42
+ return [];
43
+ const placed = HoverLayers.#placed(HoverLayers.#inHover(own, cfg));
44
+ if (own.type === 'band')
45
+ return [{ key, marker: { ...placed, data: rows } }];
46
+ return HoverLayers.#bySlice(rows).map(({ slice, points }) => ({
47
+ key: `${key}-${slice ? `${slice.start},${slice.end}` : ''}`,
48
+ marker: { ...placed, data: points },
49
+ slice
50
+ }));
51
+ });
52
+ }
53
+ /** The points a nested marker draws: all of them, the one under the pointer, or those a function keeps. */
54
+ static #pointsOf(which, group) {
55
+ if (which === 'active')
56
+ return group.active ? [group.active] : [];
57
+ if (typeof which === 'function')
58
+ return group.points.filter(which);
59
+ return group.points;
60
+ }
61
+ /**
62
+ * `marker` with what it leaves unset taken from the hover: a `rule` at each point along its axis and a `band`
63
+ * around it, in its slice; a `text` writes the point's label; each in the configuration's `hover` look (a `rect`
64
+ * too, in its `area` one), and a `dot` and a `text` in the point's series color.
65
+ */
66
+ static #inHover(marker, cfg) {
67
+ const own = marker;
68
+ const color = (point) => point.color ?? 'currentColor';
69
+ switch (marker.type) {
70
+ case 'rule':
71
+ return {
72
+ ...marker,
73
+ value: own.value ?? ((point) => point[marker.axis]),
74
+ styles: { ...cfg.hover.rule, ...own.styles }
75
+ };
76
+ case 'band':
77
+ return {
78
+ ...marker,
79
+ value: own.value ?? ((point) => point[marker.axis]),
80
+ slice: own.slice ?? ((point) => point.areaInset),
81
+ styles: { ...cfg.hover.area, ...own.styles }
82
+ };
83
+ case 'rect':
84
+ return { ...marker, styles: { ...cfg.hover.area, ...own.styles } };
85
+ case 'dot':
86
+ return {
87
+ ...marker,
88
+ styles: { ...cfg.hover.dot, dotFill: color, dotStroke: color, ...own.styles }
89
+ };
90
+ case 'text':
91
+ return {
92
+ ...marker,
93
+ text: own.text ?? ((point) => point.label),
94
+ styles: { ...cfg.hover.font, fill: color, ...own.styles }
95
+ };
96
+ default:
97
+ return marker;
98
+ }
99
+ }
100
+ /** `marker` placed at the points: its own `x` and `y`, else the point's. */
101
+ static #placed(marker) {
102
+ if (!HoverLayers.#PLACED.has(marker.type))
103
+ return marker;
104
+ const own = marker;
105
+ return {
106
+ ...marker,
107
+ x: own.x ?? ((point) => point.x),
108
+ y: own.y ?? ((point) => point.y)
109
+ };
110
+ }
111
+ /** `points` split by the slice of the band each one sits in, in the order they come. */
112
+ static #bySlice(points) {
113
+ const groups = new Map();
114
+ for (const point of points) {
115
+ const slice = point.areaInset;
116
+ const id = slice ? `${slice.start},${slice.end}` : '';
117
+ const group = groups.get(id) ?? groups.set(id, { slice, points: [] }).get(id);
118
+ group.points.push(point);
119
+ }
120
+ return [...groups.values()];
121
+ }
122
+ }
@@ -1,4 +1,4 @@
1
- import type { TooltipContext, TooltipProp } from '../../types/layout/tooltip';
1
+ import type { TooltipProp } from '../../types/layout/tooltip';
2
2
  import type { HoverPoint, HoverStrategy } from '../../types/layout/hover';
3
3
  import { PlotAxes } from '../../phases/data/PlotAxes';
4
4
  import type { HoverConfig, PointMatchingStrategy, TooltipConfig } from '../../types/plots/props';
@@ -31,7 +31,5 @@ export declare class HoverSettings<TRow extends Record<string, unknown>> {
31
31
  defaultStrategy: HoverStrategy;
32
32
  pointMatching?: PointMatchingStrategy;
33
33
  number?: (value: number) => string;
34
- /** What the plot knows of a row, the third argument of the author's own `format` - see {@link TooltipContext}. */
35
- context?: (row: TRow) => TooltipContext;
36
34
  }): HoverConfig<TRow>;
37
35
  }
@@ -1,3 +1,4 @@
1
+ import { RowContext } from '../../phases/context/RowContext';
1
2
  import { PlotAxes } from '../../phases/data/PlotAxes';
2
3
  /**
3
4
  * A plot's hover as its `tooltip` and hover marker set it up: whether it is on,
@@ -33,7 +34,7 @@ export class HoverSettings {
33
34
  }
34
35
  /** A plot's `hover` config; its tooltip reads as `format` unless it sets its own. */
35
36
  static buildConfig(args) {
36
- const { tooltip, format, sort, markers, points, label, defaultStrategy, pointMatching, context } = args;
37
+ const { tooltip, format, sort, markers, points, label, defaultStrategy, pointMatching } = args;
37
38
  const number = args.number ?? String;
38
39
  const settings = new HoverSettings(tooltip, markers, defaultStrategy);
39
40
  return {
@@ -45,7 +46,7 @@ export class HoverSettings {
45
46
  tooltip: tooltip && {
46
47
  ...tooltip,
47
48
  format: tooltip.format
48
- ? (row) => tooltip.format(row, number, context?.(row) ?? {})
49
+ ? (row) => tooltip.format(row, number, RowContext.of(row))
49
50
  : format,
50
51
  ...(sort ? { sort } : {})
51
52
  },
@@ -0,0 +1,44 @@
1
+ import type { HoverShapeStyle } from '../../types/markers/common';
2
+ import type { PlotStylesConfig } from '../../types/configuration/styling';
3
+ type Row = Record<string, unknown>;
4
+ type Role = 'active' | 'other';
5
+ /** What a hover marker sets for the shapes linked to the hover. */
6
+ type ShapeRule = {
7
+ active?: HoverShapeStyle;
8
+ others?: HoverShapeStyle;
9
+ series?: string;
10
+ };
11
+ /** What the plot tells about each row the shapes draw. */
12
+ export type HoverShapesReach = {
13
+ /** How the hover reaches a row - see `PlotHover.roleOf`. */
14
+ roleOf: (row: Row, series?: string) => Role | null;
15
+ /** Whether the legend dims the row's series, which then keeps its own fill and opacities. */
16
+ dimmed: (row: Row) => boolean;
17
+ cfg: PlotStylesConfig;
18
+ };
19
+ /** One part of a marker: its rows that the hover styles alike, keyed by how. */
20
+ export type HoverShapesPart<T> = {
21
+ key: string;
22
+ marker: T;
23
+ };
24
+ /**
25
+ * The look the plot's hover markers give the shapes of every marker linked to the hover (`hover: true` or
26
+ * `'style'`): the hovered ones take `active`, the others `others`, each over the shape's own style. A shape
27
+ * the legend dims keeps its own fill and opacities; one the hover cannot reach (hidden, omitted) is untouched.
28
+ */
29
+ export declare class HoverShapes {
30
+ #private;
31
+ constructor(markers: readonly ({
32
+ type: string;
33
+ } & ShapeRule)[]);
34
+ /** Whether any hover marker restyles shapes. */
35
+ get any(): boolean;
36
+ /** Whether `marker`'s shapes take the hover's styles. */
37
+ static links(marker: object): boolean;
38
+ /**
39
+ * `marker` in parts, each drawing the rows the hover styles alike - the shapes it does not style first, the
40
+ * hovered ones last, so their outline is on top. A marker the hover does not style is one part, as it is.
41
+ */
42
+ split<T extends object>(marker: T, rows: readonly unknown[], reach: HoverShapesReach): HoverShapesPart<T>[];
43
+ }
44
+ export {};
@@ -0,0 +1,103 @@
1
+ import { MarkerStyles } from '../../phases/markers/MarkerStyles';
2
+ import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
3
+ /**
4
+ * The look the plot's hover markers give the shapes of every marker linked to the hover (`hover: true` or
5
+ * `'style'`): the hovered ones take `active`, the others `others`, each over the shape's own style. A shape
6
+ * the legend dims keeps its own fill and opacities; one the hover cannot reach (hidden, omitted) is untouched.
7
+ */
8
+ export class HoverShapes {
9
+ /** What the legend owns on a dimmed shape. */
10
+ static #LEGEND_KEYS = ['fill', 'fillOpacity', 'strokeOpacity'];
11
+ /** A shape style's field as a `dot` marker names it. */
12
+ static #DOT_KEYS = {
13
+ fill: 'dotFill',
14
+ fillOpacity: 'dotFillOpacity',
15
+ stroke: 'dotStroke',
16
+ strokeWidth: 'dotStrokeWidth',
17
+ strokeOpacity: 'dotStrokeOpacity',
18
+ strokeDasharray: 'dotStrokeDasharray'
19
+ };
20
+ #rules;
21
+ constructor(markers) {
22
+ this.#rules = markers.filter((m) => m.type === 'hover' && !!(m.active || m.others));
23
+ }
24
+ /** Whether any hover marker restyles shapes. */
25
+ get any() {
26
+ return this.#rules.length > 0;
27
+ }
28
+ /** Whether `marker`'s shapes take the hover's styles. */
29
+ static links(marker) {
30
+ const hover = marker.hover;
31
+ return hover === true || hover === 'style';
32
+ }
33
+ /**
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.
36
+ */
37
+ split(marker, rows, reach) {
38
+ const whole = [{ key: '', marker }];
39
+ if (!this.any || !HoverShapes.links(marker))
40
+ return whole;
41
+ 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);
47
+ }
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
+ }));
57
+ }
58
+ /**
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.
61
+ */
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) ? '~' : '');
77
+ }
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 });
90
+ }
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]));
97
+ }
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))));
102
+ }
103
+ }
@@ -1,6 +1,7 @@
1
1
  import { HoverState } from './HoverState.svelte';
2
2
  import type { AxisValue } from '../../types/layout/mapping';
3
3
  import type { HoverPoint } from '../../types/layout/hover';
4
+ import type { HoverLook } from '../../types/markers/common';
4
5
  import type { HoverConfig } from '../../types/plots/props';
5
6
  /** What a plot's hover is built from, each read reactively. */
6
7
  export type PlotHoverInputs<T extends Record<string, unknown>> = {
@@ -23,6 +24,12 @@ export declare class PlotHover<T extends Record<string, unknown>> {
23
24
  #private;
24
25
  readonly state: HoverState;
25
26
  constructor(inputs: PlotHoverInputs<T>);
27
+ /**
28
+ * How the hover reaches `row`: `'active'` while it is matched, `'other'` while it can be hovered but is not,
29
+ * `null` while nothing is hovered here or it is no row the hover can reach (one the legend hides). A row is
30
+ * found by identity, else by its x, y and `series`. `series` narrows the active rows to that series.
31
+ */
32
+ roleOf(row: T, series?: string): 'active' | 'other' | null;
26
33
  /** The matched points the tooltip reads: only the tooltip's `series`, when it names some. */
27
34
  readonly matchedRows: HoverPoint<T>[];
28
35
  /** The points the hover marker highlights. */
@@ -33,6 +40,7 @@ export declare class PlotHover<T extends Record<string, unknown>> {
33
40
  series: string | undefined;
34
41
  color: string | undefined;
35
42
  areaInset: import("../../types/layout/hover").BandSlice | undefined;
43
+ context: import("../..").PlotRowContext | undefined;
36
44
  row: T;
37
45
  }[];
38
46
  /**
@@ -40,10 +48,20 @@ export declare class PlotHover<T extends Record<string, unknown>> {
40
48
  * among ties, the one at the hovered x and y. Crosshair rules sit on it.
41
49
  */
42
50
  readonly activePoint: HoverPoint<T> | null;
51
+ /**
52
+ * The plot kind's own hover under the strategy in force - the figure's, which a composer or a scrolly step can
53
+ * change while the plot is shown - for an author's `hover` marker to be laid over.
54
+ */
55
+ get look(): HoverLook;
43
56
  /** Whether a crosshair rule runs across x - the strategy's, unless the config turns them off. */
44
57
  get impliesCrosshairX(): boolean;
45
58
  /** Whether a crosshair rule runs across y. */
46
59
  get impliesCrosshairY(): boolean;
60
+ /** The pointer's position in the plot's box, while it is over this plot. */
61
+ get cursor(): {
62
+ x: number;
63
+ y: number;
64
+ } | null;
47
65
  /** Whether the tooltip shows: under the cursor in the owning plot, in every synced one. */
48
66
  get showTooltip(): boolean;
49
67
  /** Where the tooltip sits: the cursor, else the hovered point, else the owning plot's cursor carried over. */
@@ -1,3 +1,4 @@
1
+ import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
1
2
  import { HoverState } from './HoverState.svelte';
2
3
  /**
3
4
  * One plot's hover: the figure's {@link HoverState} (or its own, outside a
@@ -46,6 +47,46 @@ export class PlotHover {
46
47
  return [];
47
48
  return this.state.match(this.#inputs.hover().points);
48
49
  });
50
+ /** A row's place, as a key: its x, y and series. */
51
+ static #keyOf(x, y, series) {
52
+ const plain = (value) => (value instanceof Date ? +value : value);
53
+ return JSON.stringify([plain(x), plain(y), series ?? null]);
54
+ }
55
+ // The rows the hover can reach, and the matched ones: by identity, and by their place. Built whole
56
+ // inside this derivation and never changed after it, so they have nothing to react to.
57
+ #reach = $derived.by(() => {
58
+ const points = this.#inputs.hover().points;
59
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
60
+ const matched = new Set(this.#matched);
61
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
62
+ const byRow = new Map(points.map((p) => [p.row, p]));
63
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
64
+ const byPlace = new Map(points.map((p) => [PlotHover.#keyOf(p.x, p.y, p.series), p]));
65
+ return { matched, byRow, byPlace };
66
+ });
67
+ /**
68
+ * How the hover reaches `row`: `'active'` while it is matched, `'other'` while it can be hovered but is not,
69
+ * `null` while nothing is hovered here or it is no row the hover can reach (one the legend hides). A row is
70
+ * found by identity, else by its x, y and `series`. `series` narrows the active rows to that series.
71
+ */
72
+ roleOf(row, series) {
73
+ const { matched, byRow, byPlace } = this.#reach;
74
+ if (matched.size === 0)
75
+ return null;
76
+ const point = byRow.get(row) ?? this.#placed(byPlace, row);
77
+ if (!point)
78
+ return null;
79
+ return matched.has(point) && (series === undefined || point.series === series)
80
+ ? 'active'
81
+ : 'other';
82
+ }
83
+ /** The point at `row`'s place: of its own series, else of none (a heatmap's cell). */
84
+ #placed(byPlace, row) {
85
+ const x = this.#inputs.getX()(row);
86
+ const y = this.#inputs.getY()(row);
87
+ const series = row[SERIES_KEY];
88
+ return (byPlace.get(PlotHover.#keyOf(x, y, series)) ?? byPlace.get(PlotHover.#keyOf(x, y, undefined)));
89
+ }
49
90
  /** The matched points the tooltip reads: only the tooltip's `series`, when it names some. */
50
91
  matchedRows = $derived.by(() => {
51
92
  const series = this.#inputs.hover().tooltip?.series;
@@ -54,13 +95,14 @@ export class PlotHover {
54
95
  return this.#matched.filter((p) => p.series != null && series.includes(p.series));
55
96
  });
56
97
  /** The points the hover marker highlights. */
57
- points = $derived(this.#matched.map(({ x, y, label, series, color, areaInset, row }) => ({
98
+ points = $derived(this.#matched.map(({ x, y, label, series, color, areaInset, context, row }) => ({
58
99
  x,
59
100
  y,
60
101
  label,
61
102
  series,
62
103
  color,
63
104
  areaInset,
105
+ context,
64
106
  row
65
107
  })));
66
108
  /**
@@ -79,6 +121,13 @@ export class PlotHover {
79
121
  return (candidates.find((p) => HoverState.same(p.x, activeX) && HoverState.same(p.y, activeY)) ??
80
122
  candidates[0]);
81
123
  });
124
+ /**
125
+ * The plot kind's own hover under the strategy in force - the figure's, which a composer or a scrolly step can
126
+ * change while the plot is shown - for an author's `hover` marker to be laid over.
127
+ */
128
+ get look() {
129
+ return this.#inputs.hover().look?.(this.state.strategy) ?? {};
130
+ }
82
131
  /** Whether a crosshair rule runs across x - the strategy's, unless the config turns them off. */
83
132
  get impliesCrosshairX() {
84
133
  return this.#inputs.hover().crosshair !== false && this.state.crosshairX;
@@ -87,6 +136,10 @@ export class PlotHover {
87
136
  get impliesCrosshairY() {
88
137
  return this.#inputs.hover().crosshair !== false && this.state.crosshairY;
89
138
  }
139
+ /** The pointer's position in the plot's box, while it is over this plot. */
140
+ get cursor() {
141
+ return this.#cursor;
142
+ }
90
143
  /** Whether the tooltip shows: under the cursor in the owning plot, in every synced one. */
91
144
  get showTooltip() {
92
145
  if (this.matchedRows.length === 0)
@@ -31,6 +31,8 @@ export declare class LegendState {
31
31
  }): boolean;
32
32
  /** Whether `series` is turned off. */
33
33
  isOff(series: string): boolean;
34
+ /** Whether `series` is out of the hover: turned off any way but `'dim'`, which leaves it drawn and hoverable. */
35
+ isUnhoverable(series: string): boolean;
34
36
  /** What turning `series` off does, while it is off. */
35
37
  actionOf(series: string): LegendDisabledAction | undefined;
36
38
  turnOff(series: string, action: LegendDisabledAction): void;
@@ -52,6 +52,11 @@ export class LegendState {
52
52
  isOff(series) {
53
53
  return this.disabled.has(series);
54
54
  }
55
+ /** Whether `series` is out of the hover: turned off any way but `'dim'`, which leaves it drawn and hoverable. */
56
+ isUnhoverable(series) {
57
+ const action = this.disabled.get(series);
58
+ return action !== undefined && action.mode !== 'dim';
59
+ }
55
60
  /** What turning `series` off does, while it is off. */
56
61
  actionOf(series) {
57
62
  return this.disabled.get(series);
@@ -47,12 +47,12 @@
47
47
 
48
48
  const cfg = $derived(getConfiguration());
49
49
  const interaction = PlotInteraction.current(() => cfg);
50
- const ctx = $derived({ cfg, interaction, range: interaction.legend?.range });
50
+ const env = $derived({ cfg, interaction, range: interaction.legend?.range });
51
51
 
52
52
  const plotState = $derived(
53
53
  pipeline!.run(
54
54
  { ...rest, width, height, styles, segments, markers, scales, margins },
55
- ctx,
55
+ env,
56
56
  panelState
57
57
  )
58
58
  );
@@ -7,6 +7,7 @@ import type { PlotHover } from '../hover/PlotHover.svelte';
7
7
  import type { Margins } from '../margins/Margins';
8
8
  import type { PlotAxes } from '../../phases/data/PlotAxes';
9
9
  import type { AxisValue } from '../../types/layout/mapping';
10
+ import type { PlotRowContext } from '../../types/layout/context';
10
11
  import type { HoverPoint, HoverStrategy } from '../../types/layout/hover';
11
12
  import type { PlayerControl, TimeInput, TimelineStep } from '../../types/layout/timeline';
12
13
  import type { LegendDisabledAction, LegendEntry, LegendRange } from '../../types/layout/legend';
@@ -126,6 +127,11 @@ export declare class PublicPlotContext<T extends Record<string, unknown> = Recor
126
127
  get axes(): PlotAxes | undefined;
127
128
  /** The rows the plot draws and hovers over. */
128
129
  get data(): T[];
130
+ /**
131
+ * What the plot knows of one of the rows it draws (or a copy of it, as a mark's accessor gets it): its series,
132
+ * where it sits, the value it shows and whatever the plot kind works out for it - see {@link PlotRowContext}.
133
+ */
134
+ contextOf<TContext extends PlotRowContext<T> = PlotRowContext<T>>(row: unknown): TContext;
129
135
  /** The color a series is drawn in, by its name - `undefined` for a plot with no series (a map). */
130
136
  get colorForSeries(): ((name: string) => string) | undefined;
131
137
  get width(): number;
@@ -2,6 +2,7 @@ import { usePlot as usePlotFromSvelteplot } from 'svelteplot';
2
2
  import { HoverState } from '../hover/HoverState.svelte';
3
3
  import { LegendState } from '../legend/LegendState.svelte';
4
4
  import { PlotContext } from './PlotContext';
5
+ import { RowContext } from '../../phases/context/RowContext';
5
6
  import { TimelineState } from '../timeline/TimelineState.svelte';
6
7
  /** What the pointer is over, shared by every plot in a figure. Read-only. */
7
8
  export class PublicHover {
@@ -231,6 +232,13 @@ export class PublicPlotContext {
231
232
  get data() {
232
233
  return this.#context.data;
233
234
  }
235
+ /**
236
+ * What the plot knows of one of the rows it draws (or a copy of it, as a mark's accessor gets it): its series,
237
+ * where it sits, the value it shows and whatever the plot kind works out for it - see {@link PlotRowContext}.
238
+ */
239
+ contextOf(row) {
240
+ return RowContext.of(row);
241
+ }
234
242
  /** The color a series is drawn in, by its name - `undefined` for a plot with no series (a map). */
235
243
  get colorForSeries() {
236
244
  return this.#context.colorForSeries;
@@ -1,5 +1,6 @@
1
1
  import { Box } from '../margins/Box';
2
2
  import { PlotAxes } from '../../phases/data/PlotAxes';
3
+ import type { PlotRowContext } from '../../types/layout/context';
3
4
  import type { PlotStylesConfig } from '../../types/configuration/styling';
4
5
  import type { ValuesStyle } from '../../types/layout/styles';
5
6
  import type { ValueLabelsOptions } from '../../types/phases/styles/phase';
@@ -8,7 +9,7 @@ import type { FontStyle, StrokeStyle } from '../../types/plots/styling';
8
9
  import type { LabelAnchor, TextAnchor, ValueAnchor } from '../../types/plots/constants';
9
10
  import type { BoxAnchors, EdgeBox } from '../../types/layout/box';
10
11
  import type { MarginSide } from '../../types/plots/props';
11
- import type { AxisPlotCtx, AxisPlotState, ProcessedData, ProcessedRow } from '../../types/phases';
12
+ import type { AxisPlotEnv, AxisPlotState, ProcessedData, ProcessedRow } from '../../types/phases';
12
13
  import type { TextMarkerConfig } from '../../types/markers/common';
13
14
  import type { TextLabelConfig } from '../../types/markers/basic/text';
14
15
  /**
@@ -21,7 +22,12 @@ export declare class ValueLabels {
21
22
  #private;
22
23
  readonly show: boolean;
23
24
  readonly anchor: ValueAnchor;
24
- readonly format: (value: number, seriesName: string, row?: Record<string, unknown>) => string;
25
+ /**
26
+ * A label's text for `value`: the author's `format`, else the kind's, else the value as the display fields ask.
27
+ * `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).
29
+ */
30
+ readonly format: (value: number, row?: object, series?: string) => string;
25
31
  readonly hoverIsolate: boolean;
26
32
  readonly font: FontStyle | undefined;
27
33
  readonly fontSize: number;
@@ -34,6 +40,8 @@ export declare class ValueLabels {
34
40
  /** Where the labels are expected to sit: boxes around their points, on the edges those can reach. */
35
41
  readonly reach: EdgeBox[];
36
42
  constructor(values: ValuesStyle | undefined, cfg: PlotStylesConfig, options?: Omit<ValueLabelsOptions<never>, 'reach'>, reach?: (labels: ValueLabels) => EdgeBox[]);
43
+ /** The context a label's `format` reads: its row's, with `series` when the row gives none. */
44
+ static contextOf(row: object | undefined, series?: string): PlotRowContext;
37
45
  /** A plot's value labels: `values` over `options`, the kind's own defaults, their reach read off `state`. */
38
46
  static build<TState>(values: ValuesStyle | undefined, cfg: PlotStylesConfig, options: ValueLabelsOptions<TState>, state: TState): ValueLabels;
39
47
  /** The configuration the labels were built under, for a plot kind's own label defaults. */
@@ -79,7 +87,7 @@ export declare class ValueLabels {
79
87
  * dims with its row's series, and with `hoverIsolate`, hovering it
80
88
  * isolates that series. A row without a series gets the margins only.
81
89
  */
82
- hooks(ctx: AxisPlotCtx): Pick<TextMarkerConfig, 'fitMargins' | 'onpointerenter' | 'onpointerleave' | 'styles'>;
90
+ hooks(env: AxisPlotEnv): Pick<TextMarkerConfig, 'fitMargins' | 'onpointerenter' | 'onpointerleave' | 'styles'>;
83
91
  /**
84
92
  * Where in a bar a label goes, from the `anchor` of `styles.values`: its `label` anchor, and whether it is beyond
85
93
  * the bar. `negative` tells a bar that runs to the left (or down) from its base from one that runs right (or up),
@@ -102,7 +110,7 @@ export declare class ValueLabels {
102
110
  * never reaches out of the plot, so only one `outside` has the margins grow to hold it; and it takes the default
103
111
  * `fill` only `outside`, for inside it is written in the color that contrasts with its shape.
104
112
  */
105
- shapeLabel(ctx: AxisPlotCtx, text: (row: ProcessedRow) => string, defaults?: {
113
+ shapeLabel(env: AxisPlotEnv, text: (row: ProcessedRow) => string, defaults?: {
106
114
  fill?: string;
107
115
  fontSize?: number;
108
116
  fontWeight?: FontStyle['fontWeight'];