@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
@@ -6,6 +6,7 @@ import { seriesIdOf } from '../../utils/grouping';
6
6
  import { isGapValue } from '../../plots/utils/gaps';
7
7
  import { PlotAxes } from '../../phases/data/PlotAxes';
8
8
  import { NumberFormat } from '../../utils/NumberFormat';
9
+ import { RowContext } from '../../phases/context/RowContext';
9
10
  /**
10
11
  * A plot's value labels: the author's `styles.values` over the plot kind's
11
12
  * own defaults, how their text measures, where they are expected to sit
@@ -15,6 +16,11 @@ import { NumberFormat } from '../../utils/NumberFormat';
15
16
  export class ValueLabels {
16
17
  show;
17
18
  anchor;
19
+ /**
20
+ * A label's text for `value`: the author's `format`, else the kind's, else the value as the display fields ask.
21
+ * `row` is the row it labels, whose context the format reads; `series` names its series where there is no row
22
+ * (a label measured ahead of the rows).
23
+ */
18
24
  format;
19
25
  hoverIsolate;
20
26
  font;
@@ -33,12 +39,10 @@ export class ValueLabels {
33
39
  this.anchor = values?.anchor ?? options.anchor ?? 'outside';
34
40
  // The value as the display fields ask (else as it is), which a plot kind's own text builds on.
35
41
  const number = NumberFormat.maybe(NumberFormat.displayOf(values), cfg) ?? ((v) => `${v}`);
36
- const kindFormat = options.format;
37
- this.format = values?.format
38
- ? (v, name, row) => values.format(v, name, row, number)
39
- : kindFormat
40
- ? (v, name) => kindFormat(v, name, number)
41
- : (v) => number(v);
42
+ const own = values?.format ?? options.format;
43
+ this.format = own
44
+ ? (v, row, series) => own(v, number, ValueLabels.contextOf(row, series))
45
+ : (v) => number(v);
42
46
  this.hoverIsolate = values?.hoverIsolate ?? false;
43
47
  this.font = values?.fontStyle;
44
48
  this.fontSize = typeof this.font?.fontSize === 'number' ? this.font.fontSize : 12;
@@ -48,6 +52,11 @@ export class ValueLabels {
48
52
  this.#cfg = cfg;
49
53
  this.reach = (this.show && reach?.(this)) || [];
50
54
  }
55
+ /** The context a label's `format` reads: its row's, with `series` when the row gives none. */
56
+ static contextOf(row, series) {
57
+ const context = RowContext.of(row);
58
+ return context.series === undefined && series !== undefined ? { ...context, series } : context;
59
+ }
51
60
  /** A plot's value labels: `values` over `options`, the kind's own defaults, their reach read off `state`. */
52
61
  static build(values, cfg, options, state) {
53
62
  const { reach } = options;
@@ -134,8 +143,8 @@ export class ValueLabels {
134
143
  * dims with its row's series, and with `hoverIsolate`, hovering it
135
144
  * isolates that series. A row without a series gets the margins only.
136
145
  */
137
- hooks(ctx) {
138
- const interaction = ctx.interaction;
146
+ hooks(env) {
147
+ const interaction = env.interaction;
139
148
  const seriesOf = (d) => d?.[SERIES_KEY] === undefined ? undefined : String(d[SERIES_KEY]);
140
149
  const isolate = this.hoverIsolate && interaction;
141
150
  return {
@@ -190,8 +199,8 @@ export class ValueLabels {
190
199
  * never reaches out of the plot, so only one `outside` has the margins grow to hold it; and it takes the default
191
200
  * `fill` only `outside`, for inside it is written in the color that contrasts with its shape.
192
201
  */
193
- shapeLabel(ctx, text, defaults = {}) {
194
- const { fitMargins, styles: shared, ...events } = this.hooks(ctx);
202
+ shapeLabel(env, text, defaults = {}) {
203
+ const { fitMargins, styles: shared, ...events } = this.hooks(env);
195
204
  const { font } = this;
196
205
  return {
197
206
  ...events,
@@ -289,7 +298,7 @@ export class ValueLabels {
289
298
  x: (processed.channels.x ?? position.x),
290
299
  y: (processed.channels.y2 ?? processed.channels.y ?? topOf),
291
300
  grouping: SERIES_ID_KEY,
292
- text: (d) => format(Number(valueOf(d)), nameOf(d), d),
301
+ text: (d) => format(Number(valueOf(d)), d, nameOf(d)),
293
302
  styles: {
294
303
  ...font,
295
304
  fill: this.fill ?? ((d) => colorOf(d) ?? 'currentColor'),
@@ -356,7 +365,7 @@ export class ValueLabels {
356
365
  const lo = Number(domain[0]);
357
366
  const hi = Number(domain[domain.length - 1]);
358
367
  for (const s of series)
359
- texts.push(format(lo, s.name), format(hi, s.name));
368
+ texts.push(format(lo, undefined, s.name), format(hi, undefined, s.name));
360
369
  }
361
370
  else {
362
371
  for (const s of series) {
@@ -366,7 +375,7 @@ export class ValueLabels {
366
375
  for (const d of s.data) {
367
376
  const v = Number(y(d));
368
377
  if (!Number.isNaN(v))
369
- texts.push(format(v, s.name));
378
+ texts.push(format(v, undefined, s.name));
370
379
  }
371
380
  }
372
381
  }
@@ -1,8 +1,8 @@
1
1
  import { Box } from '../margins/Box';
2
- import type { PlotAxes } from '../../phases/data/PlotAxes';
2
+ import { PlotAxes } from '../../phases/data/PlotAxes';
3
3
  import type { ProcessedData, ProcessedRow } from '../../types/phases';
4
- import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
5
4
  import type { Series } from '../../types/plots/data/common';
5
+ import type { TooltipAnchorX, TooltipAnchorY } from '../../types/layout/tooltip';
6
6
  /** What a tooltip is shown from, each read reactively. */
7
7
  export type TooltipInputs = {
8
8
  /** The point it sits beside. */
@@ -46,16 +46,15 @@ export declare class TooltipState {
46
46
  /** A value as plain text: a date as a locale date, a number as `number` writes it (when given), anything else as itself. */
47
47
  static value(value: unknown, number?: (value: number) => string): string;
48
48
  /**
49
- * How a hovered row reads by default, on every plot: its series, when the
50
- * plot groups its rows; then where it sits - `category: value` when one
51
- * axis is a band of categories (the row's own value, as its series reads
52
- * it), else `x: y` as the transforms left them; then the value its color
53
- * shows, when that is a number (a heatmap's cells). With `number`, the values - the one on the value axis
54
- * (the plot's `y` when neither axis holds categories) and the color's - read as it writes them.
49
+ * How a hovered row reads by default, on every plot, from its context (see {@link RowContext}): its series,
50
+ * when the plot groups its rows; then where it sits - `category: value` when one axis is a band of categories,
51
+ * else `x: y` (an axis the transforms spread into a span, a bin's, as `from-to`); then the value its color
52
+ * shows, when that is a number (a heatmap's cells). With `number`, the values - the one on the value axis and
53
+ * the color's - read as it writes them.
55
54
  */
56
55
  static default(state: {
57
56
  processedData?: ProcessedData;
58
- resolvedSeries?: Series<Record<string, unknown>>[];
57
+ resolvedSeries?: Series<any>[];
59
58
  channels?: {
60
59
  grouping?: unknown;
61
60
  color?: {
@@ -2,7 +2,8 @@ import { Box } from '../margins/Box';
2
2
  import { PlotMargins } from '../margins/PlotMargins.svelte';
3
3
  import { resolveAccessor } from '../../utils/accessors';
4
4
  import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
5
- import { ValueLabels } from '../styles/ValueLabels';
5
+ import { RowContext } from '../../phases/context/RowContext';
6
+ import { PlotAxes } from '../../phases/data/PlotAxes';
6
7
  /** A tooltip anchor as the side of the point the tooltip sits on. */
7
8
  const SIDES = {
8
9
  left: 'before',
@@ -69,29 +70,33 @@ export class TooltipState {
69
70
  return number && typeof value === 'number' ? number(value) : String(value);
70
71
  }
71
72
  /**
72
- * How a hovered row reads by default, on every plot: its series, when the
73
- * plot groups its rows; then where it sits - `category: value` when one
74
- * axis is a band of categories (the row's own value, as its series reads
75
- * it), else `x: y` as the transforms left them; then the value its color
76
- * shows, when that is a number (a heatmap's cells). With `number`, the values - the one on the value axis
77
- * (the plot's `y` when neither axis holds categories) and the color's - read as it writes them.
73
+ * How a hovered row reads by default, on every plot, from its context (see {@link RowContext}): its series,
74
+ * when the plot groups its rows; then where it sits - `category: value` when one axis is a band of categories,
75
+ * else `x: y` (an axis the transforms spread into a span, a bin's, as `from-to`); then the value its color
76
+ * shows, when that is a number (a heatmap's cells). With `number`, the values - the one on the value axis and
77
+ * the color's - read as it writes them.
78
78
  */
79
79
  static default(state, number) {
80
80
  const processed = state.processedData;
81
81
  if (!processed)
82
82
  return () => '';
83
- const x = TooltipState.#axisText(processed, 'x');
84
- const y = TooltipState.#axisText(processed, 'y');
85
- // With no band axis, the value is the y.
86
- const valueY = TooltipState.#axisText(processed, 'y', number);
83
+ const valueAxis = PlotAxes.valueOf(state);
84
+ const contextOf = RowContext.reader(state);
87
85
  const bandX = state.axes?.bands.includes('x') ?? false;
88
86
  const bandY = state.axes?.bands.includes('y') ?? false;
89
- const valueOf = ValueLabels.valueOf(state);
87
+ const along = (axis) => {
88
+ const span = TooltipState.#spanText(processed, axis, number);
89
+ const measured = axis === valueAxis && !state.axes?.bands.includes(axis);
90
+ return (row) => span ? span(row) : TooltipState.value(contextOf(row)[axis], measured ? number : undefined);
91
+ };
92
+ const x = along('x');
93
+ const y = along('y');
94
+ const valueOf = (row) => TooltipState.value(contextOf(row).value, number);
90
95
  const position = bandX && !bandY
91
- ? (row) => `${x(row)}: ${TooltipState.value(valueOf(row), number)}`
96
+ ? (row) => `${x(row)}: ${valueOf(row)}`
92
97
  : bandY && !bandX
93
- ? (row) => `${y(row)}: ${TooltipState.value(valueOf(row), number)}`
94
- : (row) => `${x(row)}: ${valueY(row)}`;
98
+ ? (row) => `${y(row)}: ${valueOf(row)}`
99
+ : (row) => `${x(row)}: ${y(row)}`;
95
100
  const grouped = state.channels?.grouping !== undefined;
96
101
  const color = state.channels?.color;
97
102
  const colorName = typeof color?.source === 'string' ? color.source : 'color';
@@ -106,26 +111,20 @@ export class TooltipState {
106
111
  };
107
112
  }
108
113
  /**
109
- * How one axis reads on a processed row: its own channel, else the range
110
- * the transforms gave it - a stack's own share (`2 - 1`), any other span as `1-2`.
114
+ * How an axis the transforms spread into a span reads on a processed row - a bin's edges as `from-to` - or
115
+ * `undefined` when the axis has a value of its own or is a stack's (whose own value the context gives).
111
116
  */
112
- static #axisText(processed, axis, number) {
117
+ static #spanText(processed, axis, number) {
113
118
  const { channels, transforms } = processed;
114
- const read = (channel) => resolveAccessor(channel);
115
- const point = channels[axis];
116
- if (point !== undefined) {
117
- const value = read(point);
118
- return (row) => TooltipState.value(value(row), number);
119
- }
120
119
  const low = channels[`${axis}1`];
121
120
  const high = channels[`${axis}2`];
122
- if (low === undefined || high === undefined)
123
- return () => '';
121
+ if (channels[axis] !== undefined || low === undefined || high === undefined)
122
+ return undefined;
123
+ if (transforms.some((t) => t.type === STACKS[axis]))
124
+ return undefined;
125
+ const read = (channel) => resolveAccessor(channel);
124
126
  const from = read(low);
125
127
  const to = read(high);
126
- if (transforms.some((t) => t.type === STACKS[axis])) {
127
- return (row) => TooltipState.value(Number(to(row)) - Number(from(row)), number);
128
- }
129
128
  return (row) => `${TooltipState.value(from(row), number)}-${TooltipState.value(to(row), number)}`;
130
129
  }
131
130
  }
@@ -14,6 +14,11 @@ export declare class MarkerKinds {
14
14
  clip: false;
15
15
  drawsRows: true;
16
16
  };
17
+ band: {
18
+ clip: true;
19
+ drawsRows: false;
20
+ legendSymbol: "rect";
21
+ };
17
22
  bar: {
18
23
  clip: true;
19
24
  drawsRows: true;
@@ -6,6 +6,7 @@ export class MarkerKinds {
6
6
  static traits = {
7
7
  area: { clip: true, drawsRows: true, legendSymbol: 'area' },
8
8
  arrow: { clip: false, drawsRows: true },
9
+ band: { clip: true, drawsRows: false, legendSymbol: 'rect' },
9
10
  bar: { clip: true, drawsRows: true, legendSymbol: 'bar' },
10
11
  bollinger: { clip: true, drawsRows: true, legendSymbol: 'line' },
11
12
  brush: { clip: false, drawsRows: false },
@@ -0,0 +1,74 @@
1
+ <script lang="ts">
2
+ import { usePlot } from '../../layout/plot/usePlot';
3
+ import { getConfiguration } from '../../configuration/config.svelte';
4
+ import { MarkerEvents } from '../utils/MarkerEvents';
5
+ import { RowStyle } from '../utils/RowStyle';
6
+ import { Bands, type Band } from './Bands';
7
+ import type { BandMarkerConfig } from '../../types/markers/common';
8
+
9
+ /**
10
+ * Renders one `'band'` marker: a rectangle over the band of each category it names, across the whole of the
11
+ * other axis. Nothing on an axis that is not a band scale. It takes the pointer only with a handler of its own,
12
+ * so a band behind the plot's shapes does not take their hover.
13
+ */
14
+ const { marker }: { marker: BandMarkerConfig } = $props();
15
+ const cfg = $derived(getConfiguration());
16
+ const events = MarkerEvents.use(() => marker);
17
+ const plot = usePlot().svelteplot;
18
+
19
+ const bands = $derived(Bands.of(marker));
20
+ const rows = $derived(marker.data ?? []);
21
+ const styles = $derived({ ...cfg.band, ...marker.styles });
22
+ const scale = $derived(plot.scales[marker.axis]);
23
+ const across = $derived(plot.scales[marker.axis === 'x' ? 'y' : 'x']);
24
+
25
+ /** The pixels the other axis spans, lowest first. */
26
+ const extent = $derived.by((): [number, number] => {
27
+ const range = across?.fn?.range?.() as number[] | undefined;
28
+ if (!range || range.length < 2) return [0, 0];
29
+ const [a, b] = [Number(range[0]), Number(range[range.length - 1])];
30
+ return [Math.min(a, b), Math.max(a, b)];
31
+ });
32
+
33
+ /** Where a band is drawn: its slice of the band along the axis, the other axis' whole span across it. */
34
+ function boxOf(band: Band) {
35
+ const bandwidth = scale.fn.bandwidth?.() ?? 0;
36
+ const start = Number(scale.fn(band.category as never));
37
+ const from = start + band.slice.start * bandwidth;
38
+ const size = Math.max(0, bandwidth * (1 - band.slice.start - band.slice.end));
39
+ const [low, high] = extent;
40
+ return marker.axis === 'x'
41
+ ? { x: from, y: low, width: size, height: high - low }
42
+ : { x: low, y: from, width: high - low, height: size };
43
+ }
44
+
45
+ /** A color of `styles` for the row: a value, the field of the rows it names, or a function of the row. */
46
+ const colorOf = (style: unknown, row: unknown) => RowStyle.colorOf(style, rows)?.(row);
47
+ /** A number of `styles` for the row: a value, the field of the rows it names, or a function of the row. */
48
+ const numberOf = (style: unknown, row: unknown): number | undefined => {
49
+ const read = RowStyle.field(style as never) as unknown;
50
+ const value = typeof read === 'function' ? (read as (row: unknown) => unknown)(row) : read;
51
+ return value == null ? undefined : Number(value);
52
+ };
53
+ </script>
54
+
55
+ {#if scale?.type === 'band'}
56
+ <g class="band" pointer-events={events.any ? undefined : 'none'}>
57
+ {#each bands as band (band.category instanceof Date ? +band.category : band.category)}
58
+ {@const box = boxOf(band)}
59
+ {@const radius = numberOf(styles.borderRadius, band.row)}
60
+ <rect
61
+ {...box}
62
+ rx={radius}
63
+ ry={radius}
64
+ fill={colorOf(styles.fill, band.row)}
65
+ fill-opacity={numberOf(styles.fillOpacity, band.row)}
66
+ stroke={colorOf(styles.stroke, band.row)}
67
+ stroke-width={numberOf(styles.strokeWidth, band.row)}
68
+ stroke-opacity={numberOf(styles.strokeOpacity, band.row)}
69
+ stroke-dasharray={colorOf(styles.strokeDasharray, band.row)}
70
+ {...events.forRow(band.row, band.index)}
71
+ />
72
+ {/each}
73
+ </g>
74
+ {/if}
@@ -0,0 +1,7 @@
1
+ import type { BandMarkerConfig } from '../../types/markers/common';
2
+ type $$ComponentProps = {
3
+ marker: BandMarkerConfig;
4
+ };
5
+ declare const BandMarker: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type BandMarker = ReturnType<typeof BandMarker>;
7
+ export default BandMarker;
@@ -0,0 +1,16 @@
1
+ import type { AxisValue } from '../../types/layout/mapping';
2
+ import type { BandSlice } from '../../types/layout/hover';
3
+ import type { BandMarkerConfig } from '../../types/markers/common';
4
+ /** One band to draw: its category, the part of it filled, and the first row that names it. */
5
+ export type Band = {
6
+ category: AxisValue;
7
+ slice: BandSlice;
8
+ row: unknown;
9
+ index: number;
10
+ };
11
+ /** The bands a `band` marker draws, one per category its rows (or its `value`) name, in the order they first come. */
12
+ export declare class Bands {
13
+ #private;
14
+ /** The marker's bands. Rows that share a category share one band, which fills what all of their slices do. */
15
+ static of(marker: Pick<BandMarkerConfig, 'value' | 'slice' | 'data'>): Band[];
16
+ }
@@ -0,0 +1,48 @@
1
+ import { resolveAccessor } from '../../utils/accessors';
2
+ /** The bands a `band` marker draws, one per category its rows (or its `value`) name, in the order they first come. */
3
+ export class Bands {
4
+ static #WHOLE = { start: 0, end: 0 };
5
+ /** The marker's bands. Rows that share a category share one band, which fills what all of their slices do. */
6
+ static of(marker) {
7
+ const { rows, categoryOf } = Bands.#rowsOf(marker);
8
+ const sliceOf = Bands.#sliceOf(marker.slice);
9
+ const bands = new Map();
10
+ rows.forEach((row, index) => {
11
+ const category = categoryOf(row);
12
+ if (category == null)
13
+ return;
14
+ const key = category instanceof Date ? +category : category;
15
+ const slice = sliceOf(row);
16
+ const band = bands.get(key);
17
+ if (!band) {
18
+ bands.set(key, { category, slice, row, index });
19
+ return;
20
+ }
21
+ // What either one fills: the least left out at each end.
22
+ band.slice = {
23
+ start: Math.min(band.slice.start, slice.start),
24
+ end: Math.min(band.slice.end, slice.end)
25
+ };
26
+ });
27
+ return [...bands.values()];
28
+ }
29
+ /** The rows a marker draws from, and the category of each: its `data` and `value`, or its `value`'s own. */
30
+ static #rowsOf(marker) {
31
+ const { value, data } = marker;
32
+ if (data) {
33
+ const read = resolveAccessor(value);
34
+ return { rows: data, categoryOf: read };
35
+ }
36
+ const values = (Array.isArray(value) ? value : [value]);
37
+ return { rows: values, categoryOf: (row) => row };
38
+ }
39
+ /** Each row's slice: the marker's own, or a function of the row's; the whole band when it gives none. */
40
+ static #sliceOf(slice) {
41
+ const whole = Bands.#WHOLE;
42
+ const read = typeof slice === 'function' ? slice : () => slice;
43
+ return (row) => {
44
+ const own = read(row);
45
+ return own ? { start: own.start ?? 0, end: own.end ?? 0 } : whole;
46
+ };
47
+ }
48
+ }
@@ -9,13 +9,11 @@
9
9
 
10
10
  /**
11
11
  * Renders one `'bar'` marker: `role: 'segment'` (visible bar via
12
- * `<BarX>`/`<BarY>`), `role: 'hitArea'` (invisible hover hit region for
13
- * one segment), or `role: 'highlight'` (visible area highlight).
12
+ * `<BarX>`/`<BarY>`) or `role: 'hitArea'` (invisible hover hit region for
13
+ * one segment).
14
14
  */
15
15
  let { marker }: { marker: BarMarkerConfig } = $props();
16
16
  const events = MarkerEvents.use(() => marker);
17
- /** The hover band stands for no row, so a style that is a function of one does not apply to it. */
18
- const flatStyles = $derived(RowStyle.flat(marker.styles));
19
17
 
20
18
  const cfg = $derived(getConfiguration());
21
19
  const data = $derived(marker.data ?? []);
@@ -90,69 +88,28 @@
90
88
  {@const insetRight = (marker.styles?.insetRight ?? 0) * bandwidth}
91
89
  {@const insetTop = (marker.styles?.insetTop ?? 0) * bandwidth}
92
90
  {@const insetBottom = (marker.styles?.insetBottom ?? 0) * bandwidth}
93
- {#if marker.role === 'hitArea'}
94
- <!-- The band runs the plot's whole value axis, so the pointer needn't be over the bar. -->
95
- {@const [rangeMin, rangeMax] = valueExtentFor(marker.isHorizontal)}
96
- {#if marker.isHorizontal}
97
- <rect
98
- class="dct-hit-area"
99
- x={rangeMin + insetLeft}
100
- y={bandStart + insetBottom}
101
- width={Math.max(0, rangeMax - rangeMin - insetLeft - insetRight)}
102
- height={Math.max(0, bandwidth - insetTop - insetBottom)}
103
- fill="transparent"
104
- {...events.forRow(data[0], 0)}
105
- />
106
- {:else}
107
- <rect
108
- class="dct-hit-area"
109
- x={bandStart + insetLeft}
110
- y={rangeMin + insetBottom}
111
- width={Math.max(0, bandwidth - insetLeft - insetRight)}
112
- height={Math.max(0, rangeMax - rangeMin - insetTop - insetBottom)}
113
- fill="transparent"
114
- {...events.forRow(data[0], 0)}
115
- />
116
- {/if}
91
+ <!-- The band runs the plot's whole value axis, so the pointer needn't be over the bar. -->
92
+ {@const [rangeMin, rangeMax] = valueExtentFor(marker.isHorizontal)}
93
+ {#if marker.isHorizontal}
94
+ <rect
95
+ class="dct-hit-area"
96
+ x={rangeMin + insetLeft}
97
+ y={bandStart + insetBottom}
98
+ width={Math.max(0, rangeMax - rangeMin - insetLeft - insetRight)}
99
+ height={Math.max(0, bandwidth - insetTop - insetBottom)}
100
+ fill="transparent"
101
+ {...events.forRow(data[0], 0)}
102
+ />
117
103
  {:else}
118
- {@const [valueMin, valueMax] = valueExtentFor(marker.isHorizontal)}
119
- {#if marker.isHorizontal}
120
- {@const thickness = Math.max(0, bandwidth - insetTop - insetBottom) + 2}
121
- {@const center = bandStart + insetBottom + (bandwidth - insetTop - insetBottom) / 2}
122
- <rect
123
- class="hover-area"
124
- x={valueMin}
125
- y={center - thickness / 2}
126
- width={Math.max(0, valueMax - valueMin)}
127
- height={thickness}
128
- fill={flatStyles.fill}
129
- fill-opacity={flatStyles.fillOpacity}
130
- stroke={flatStyles.stroke}
131
- stroke-width={flatStyles.strokeWidth}
132
- stroke-opacity={flatStyles.strokeOpacity}
133
- stroke-dasharray={flatStyles.strokeDasharray}
134
- rx={flatStyles.borderRadius}
135
- pointer-events="none"
136
- />
137
- {:else}
138
- {@const thickness = Math.max(0, bandwidth - insetLeft - insetRight) + 2}
139
- {@const center = bandStart + insetLeft + (bandwidth - insetLeft - insetRight) / 2}
140
- <rect
141
- class="hover-area"
142
- x={center - thickness / 2}
143
- y={valueMin}
144
- width={thickness}
145
- height={Math.max(0, valueMax - valueMin)}
146
- fill={flatStyles.fill}
147
- fill-opacity={flatStyles.fillOpacity}
148
- stroke={flatStyles.stroke}
149
- stroke-width={flatStyles.strokeWidth}
150
- stroke-opacity={flatStyles.strokeOpacity}
151
- stroke-dasharray={flatStyles.strokeDasharray}
152
- ry={flatStyles.borderRadius}
153
- pointer-events="none"
154
- />
155
- {/if}
104
+ <rect
105
+ class="dct-hit-area"
106
+ x={bandStart + insetLeft}
107
+ y={rangeMin + insetBottom}
108
+ width={Math.max(0, bandwidth - insetLeft - insetRight)}
109
+ height={Math.max(0, rangeMax - rangeMin - insetTop - insetBottom)}
110
+ fill="transparent"
111
+ {...events.forRow(data[0], 0)}
112
+ />
156
113
  {/if}
157
114
  {/if}
158
115
  {/if}