@fundar/data-chart-telling 1.16.0 → 1.17.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 (194) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/configuration/ConfigVars.svelte +14 -0
  3. package/dist/configuration/ConfigVars.svelte.d.ts +3 -0
  4. package/dist/configuration/config.svelte.d.ts +5 -0
  5. package/dist/configuration/config.svelte.js +17 -4
  6. package/dist/configuration/themes/dark.js +1 -1
  7. package/dist/configuration/themes/editorial.js +1 -1
  8. package/dist/index.d.ts +6 -3
  9. package/dist/index.js +2 -1
  10. package/dist/layout/base/BasePlotLayout.svelte +1 -1
  11. package/dist/layout/figure/Figure.svelte +5 -12
  12. package/dist/layout/figure/FigureChrome.svelte +2 -10
  13. package/dist/layout/geometry/GeometryLayout.svelte +42 -1
  14. package/dist/layout/hover/HoverSettings.d.ts +3 -1
  15. package/dist/layout/hover/HoverSettings.js +4 -2
  16. package/dist/layout/legend/DisabledStyle.d.ts +1 -1
  17. package/dist/layout/legend/DisabledStyle.js +1 -1
  18. package/dist/layout/legend/DiscreteBadgeItem.svelte +1 -1
  19. package/dist/layout/legend/DiscreteSection.svelte +1 -1
  20. package/dist/layout/legend/LegendState.svelte.d.ts +12 -2
  21. package/dist/layout/legend/LegendState.svelte.js +18 -4
  22. package/dist/layout/legend/LegendText.d.ts +4 -1
  23. package/dist/layout/legend/LegendText.js +7 -1
  24. package/dist/layout/margins/PlotMargins.svelte.d.ts +2 -2
  25. package/dist/layout/margins/PlotMargins.svelte.js +2 -2
  26. package/dist/layout/plot/Plot.svelte +4 -2
  27. package/dist/layout/plot/Plot.svelte.d.ts +2 -1
  28. package/dist/layout/scales/ScalesLayout.svelte +1 -1
  29. package/dist/layout/styles/ValueLabels.d.ts +33 -2
  30. package/dist/layout/styles/ValueLabels.js +67 -8
  31. package/dist/layout/timeline/Timeline.svelte +5 -1
  32. package/dist/markers/area/AreaMarker.svelte +3 -0
  33. package/dist/markers/arrow/ArrowMarker.svelte +9 -5
  34. package/dist/markers/bar/BarMarker.svelte +45 -48
  35. package/dist/markers/bollinger/BollingerMarker.svelte +3 -0
  36. package/dist/markers/box/BoxMarker.svelte +3 -1
  37. package/dist/markers/cell/CellMarker.svelte +11 -7
  38. package/dist/markers/contour/ContourMarker.svelte +3 -0
  39. package/dist/markers/delaunay/DelaunayLinkMarker.svelte +9 -5
  40. package/dist/markers/delaunay/DelaunayMeshMarker.svelte +3 -0
  41. package/dist/markers/density/DensityMarker.svelte +3 -0
  42. package/dist/markers/difference/DifferenceMarker.svelte +3 -0
  43. package/dist/markers/dot/DotMarker.svelte +11 -6
  44. package/dist/markers/geo/GraticuleMarker.svelte +3 -0
  45. package/dist/markers/geo/SphereMarker.svelte +3 -0
  46. package/dist/markers/hover/HoverMarker.svelte +22 -6
  47. package/dist/markers/hull/HullMarker.svelte +3 -0
  48. package/dist/markers/image/ImageMarker.svelte +3 -0
  49. package/dist/markers/line/LineMarker.svelte +3 -0
  50. package/dist/markers/link/LinkMarker.svelte +3 -0
  51. package/dist/markers/raster/RasterMarker.svelte +3 -0
  52. package/dist/markers/rect/RectMarker.svelte +25 -17
  53. package/dist/markers/regression/RegressionMarker.svelte +3 -0
  54. package/dist/markers/rule/RuleMarker.svelte +15 -5
  55. package/dist/markers/text/TextFit.d.ts +44 -0
  56. package/dist/markers/text/TextFit.js +86 -0
  57. package/dist/markers/text/TextMarker.svelte +186 -90
  58. package/dist/markers/text/TextMarker.svelte.d.ts +2 -0
  59. package/dist/markers/tick/TickMarker.svelte +9 -3
  60. package/dist/markers/trail/TrailMarker.svelte +3 -0
  61. package/dist/markers/utils/BoxLabel.d.ts +58 -0
  62. package/dist/markers/utils/BoxLabel.js +175 -0
  63. package/dist/markers/utils/MarkerEvents.d.ts +23 -0
  64. package/dist/markers/utils/MarkerEvents.js +44 -0
  65. package/dist/markers/utils/MarkerLabel.svelte +33 -0
  66. package/dist/markers/utils/MarkerLabel.svelte.d.ts +10 -0
  67. package/dist/markers/utils/RowStroke.d.ts +9 -21
  68. package/dist/markers/utils/RowStroke.js +10 -21
  69. package/dist/markers/utils/RowStyle.d.ts +26 -0
  70. package/dist/markers/utils/RowStyle.js +55 -0
  71. package/dist/markers/vector/VectorMarker.svelte +3 -0
  72. package/dist/markers/voronoi/VoronoiMarker.svelte +9 -5
  73. package/dist/markers/voronoi/VoronoiMeshMarker.svelte +3 -0
  74. package/dist/markers/waffle/WaffleMarker.svelte +9 -5
  75. package/dist/phases/Pipeline.d.ts +3 -1
  76. package/dist/phases/Pipeline.js +1 -0
  77. package/dist/phases/data/DataPhase.js +51 -10
  78. package/dist/phases/hover/HoverPhase.js +9 -1
  79. package/dist/phases/mapping/MappingPhase.js +2 -1
  80. package/dist/phases/markers/MarkerStyles.d.ts +18 -0
  81. package/dist/phases/markers/MarkerStyles.js +111 -0
  82. package/dist/phases/markers/MarkersPhase.d.ts +1 -1
  83. package/dist/phases/markers/MarkersPhase.js +7 -5
  84. package/dist/phases/statistics/StatisticsPhase.js +1 -1
  85. package/dist/phases/transforms/Transforms.js +3 -1
  86. package/dist/phases/transforms/TransformsPhase.js +58 -4
  87. package/dist/phases/transforms/methods/TreemapTransform.d.ts +28 -0
  88. package/dist/phases/transforms/methods/TreemapTransform.js +119 -0
  89. package/dist/plots/correlation/heatmap/defaults.js +2 -2
  90. package/dist/plots/correlation/heatmap/markers.d.ts +0 -8
  91. package/dist/plots/correlation/heatmap/markers.js +8 -35
  92. package/dist/plots/correlation/scatter/markers.d.ts +1 -1
  93. package/dist/plots/correlation/scatter/markers.js +2 -2
  94. package/dist/plots/distribution/histogram/defaults.js +2 -2
  95. package/dist/plots/distribution/histogram/markers.d.ts +1 -10
  96. package/dist/plots/distribution/histogram/markers.js +17 -59
  97. package/dist/plots/distribution/pyramid/defaults.js +3 -3
  98. package/dist/plots/distribution/pyramid/markers.d.ts +0 -10
  99. package/dist/plots/distribution/pyramid/markers.js +13 -64
  100. package/dist/plots/distribution/violin/markers.js +1 -1
  101. package/dist/plots/map/geo/phases/markers.js +3 -2
  102. package/dist/plots/part-of-a-whole/treemap/Plot.svelte +47 -0
  103. package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +26 -0
  104. package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +10 -0
  105. package/dist/plots/part-of-a-whole/treemap/defaults.js +167 -0
  106. package/dist/plots/ranking/bar/defaults.js +2 -2
  107. package/dist/plots/ranking/bar/markers.d.ts +1 -11
  108. package/dist/plots/ranking/bar/markers.js +38 -80
  109. package/dist/plots/ranking/bullet/markers.js +2 -3
  110. package/dist/plots/ranking/cleveland/ClevelandMarkers.js +17 -13
  111. package/dist/types/configuration/styling.d.ts +27 -2
  112. package/dist/types/layout/legend.d.ts +8 -1
  113. package/dist/types/layout/mapping.d.ts +12 -3
  114. package/dist/types/layout/scales.d.ts +6 -0
  115. package/dist/types/layout/tooltip.d.ts +16 -4
  116. package/dist/types/markers/axis/arrow.d.ts +2 -8
  117. package/dist/types/markers/axis/bar.d.ts +4 -8
  118. package/dist/types/markers/axis/cell.d.ts +5 -5
  119. package/dist/types/markers/axis/delaunayLink.d.ts +2 -2
  120. package/dist/types/markers/axis/rect.d.ts +4 -8
  121. package/dist/types/markers/axis/rule.d.ts +2 -16
  122. package/dist/types/markers/axis/tick.d.ts +2 -2
  123. package/dist/types/markers/axis/voronoi.d.ts +2 -2
  124. package/dist/types/markers/axis/waffle.d.ts +4 -3
  125. package/dist/types/markers/basic/dot.d.ts +2 -2
  126. package/dist/types/markers/basic/text.d.ts +56 -14
  127. package/dist/types/markers/channels/commonMarkerConfig.d.ts +4 -1
  128. package/dist/types/markers/channels/commonMarkerConfigWithArea.d.ts +1 -2
  129. package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +10 -0
  130. package/dist/types/markers/channels/commonMarkerConfigWithLabel.js +1 -0
  131. package/dist/types/markers/channels/commonMarkerEvents.d.ts +17 -0
  132. package/dist/types/markers/channels/commonMarkerEvents.js +13 -0
  133. package/dist/types/markers/geo/contour.d.ts +2 -1
  134. package/dist/types/markers/geo/graticule.d.ts +2 -1
  135. package/dist/types/markers/geo/link.d.ts +2 -1
  136. package/dist/types/markers/geo/sphere.d.ts +2 -1
  137. package/dist/types/markers/geo/vector.d.ts +2 -1
  138. package/dist/types/phases/data/phase.d.ts +5 -0
  139. package/dist/types/phases/hover/phase.d.ts +2 -0
  140. package/dist/types/phases/state.d.ts +7 -0
  141. package/dist/types/phases/statistics/phase.d.ts +2 -1
  142. package/dist/types/phases/transforms/phase.d.ts +11 -0
  143. package/dist/types/plots/constants.d.ts +14 -14
  144. package/dist/types/plots/correlation/heatmap/props.d.ts +3 -3
  145. package/dist/types/plots/correlation/scatter/props.d.ts +3 -3
  146. package/dist/types/plots/data/common.d.ts +19 -6
  147. package/dist/types/plots/data/geo.d.ts +1 -1
  148. package/dist/types/plots/distribution/box/props.d.ts +3 -3
  149. package/dist/types/plots/distribution/density/props.d.ts +3 -11
  150. package/dist/types/plots/distribution/histogram/props.d.ts +3 -11
  151. package/dist/types/plots/distribution/pyramid/props.d.ts +3 -3
  152. package/dist/types/plots/distribution/violin/props.d.ts +3 -3
  153. package/dist/types/plots/evolution/area/props.d.ts +3 -3
  154. package/dist/types/plots/evolution/line/props.d.ts +3 -3
  155. package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +19 -0
  156. package/dist/types/plots/part-of-a-whole/treemap/props.js +1 -0
  157. package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +6 -0
  158. package/dist/types/plots/part-of-a-whole/treemap/state.js +1 -0
  159. package/dist/types/plots/props.d.ts +7 -4
  160. package/dist/types/plots/ranking/bar/props.d.ts +3 -3
  161. package/dist/types/plots/ranking/cleveland/props.d.ts +3 -3
  162. package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -3
  163. package/dist/types/plots/ranking/forest/props.d.ts +3 -3
  164. package/dist/types/plots/ranking/lollipop/props.d.ts +3 -3
  165. package/dist/types/plots/styling.d.ts +24 -0
  166. package/dist/types/transforms/index.d.ts +3 -1
  167. package/dist/types/transforms/treemap.d.ts +32 -0
  168. package/dist/types/transforms/treemap.js +5 -0
  169. package/dist/utils/color/ColorTones.d.ts +19 -0
  170. package/dist/utils/color/ColorTones.js +64 -0
  171. package/dist/utils/color/CssColor.d.ts +14 -5
  172. package/dist/utils/color/CssColor.js +69 -27
  173. package/dist/utils/color/index.d.ts +4 -0
  174. package/dist/utils/color/index.js +4 -0
  175. package/dist/utils/grouping.d.ts +18 -1
  176. package/dist/utils/grouping.js +31 -0
  177. package/docs/concepts.md +3 -2
  178. package/docs/legend.md +8 -5
  179. package/docs/markers.md +76 -26
  180. package/docs/plots/part-of-a-whole/treemap.md +69 -0
  181. package/docs/plots/ranking/bar.md +1 -1
  182. package/docs/plots.md +21 -20
  183. package/docs/scales.md +3 -0
  184. package/docs/state.md +1 -1
  185. package/docs/styles-and-segments.md +19 -18
  186. package/docs/theming.md +4 -11
  187. package/docs/tooltip-and-hover.md +1 -1
  188. package/docs/transforms-and-statistics.md +17 -16
  189. package/examples/Treemap.svelte +13 -0
  190. package/examples/datasets/economic-activity.ts +40 -0
  191. package/examples/datasets/index.ts +1 -0
  192. package/llms-full.txt +5 -5
  193. package/llms.txt +2 -0
  194. package/package.json +3 -1
@@ -0,0 +1,23 @@
1
+ import type { CommonMarkerEvents, MarkEventHandler, MarkEventName } from '../../types/markers/channels/commonMarkerEvents';
2
+ /** What svelteplot's marks are given for a marker's events. */
3
+ export type MarkerEventProps = Partial<Record<MarkEventName, MarkEventHandler>>;
4
+ /**
5
+ * The events of a marker, as the props of the svelteplot mark that draws it: the handlers it is given
6
+ * (`onclick`, `onpointerenter`...). Every marker takes them from `CommonMarkerConfig`, so each one only has to
7
+ * spread {@link MarkerEvents.props} on its mark.
8
+ */
9
+ export declare class MarkerEvents {
10
+ #private;
11
+ private constructor();
12
+ /** The events of the marker `marker` reads. */
13
+ static use(marker: () => CommonMarkerEvents): MarkerEvents;
14
+ /** Whether the marker has a handler for any event. */
15
+ get any(): boolean;
16
+ /**
17
+ * The handlers as DOM listeners of the shape drawn for `row` (the `index`-th), for a marker that draws
18
+ * its shapes itself rather than through a svelteplot mark that wires them up.
19
+ */
20
+ forRow(row: unknown, index: number): Partial<Record<MarkEventName, (event: Event) => void>>;
21
+ /** The props to spread on the marker's svelteplot mark. Read in the template, so it follows the marker. */
22
+ get props(): MarkerEventProps;
23
+ }
@@ -0,0 +1,44 @@
1
+ import { MARK_EVENTS } from '../../types/markers/channels/commonMarkerEvents';
2
+ /**
3
+ * The events of a marker, as the props of the svelteplot mark that draws it: the handlers it is given
4
+ * (`onclick`, `onpointerenter`...). Every marker takes them from `CommonMarkerConfig`, so each one only has to
5
+ * spread {@link MarkerEvents.props} on its mark.
6
+ */
7
+ export class MarkerEvents {
8
+ #marker;
9
+ constructor(marker) {
10
+ this.#marker = marker;
11
+ }
12
+ /** The events of the marker `marker` reads. */
13
+ static use(marker) {
14
+ return new MarkerEvents(marker);
15
+ }
16
+ /** Whether the marker has a handler for any event. */
17
+ get any() {
18
+ const marker = this.#marker();
19
+ return MARK_EVENTS.some((name) => !!marker[name]);
20
+ }
21
+ /**
22
+ * The handlers as DOM listeners of the shape drawn for `row` (the `index`-th), for a marker that draws
23
+ * its shapes itself rather than through a svelteplot mark that wires them up.
24
+ */
25
+ forRow(row, index) {
26
+ const marker = this.#marker();
27
+ const own = {};
28
+ for (const name of MARK_EVENTS) {
29
+ const handler = marker[name];
30
+ if (handler)
31
+ own[name] = (event) => handler(event, row, index);
32
+ }
33
+ return own;
34
+ }
35
+ /** The props to spread on the marker's svelteplot mark. Read in the template, so it follows the marker. */
36
+ get props() {
37
+ const marker = this.#marker();
38
+ const own = {};
39
+ for (const name of MARK_EVENTS)
40
+ if (marker[name])
41
+ own[name] = marker[name];
42
+ return own;
43
+ }
44
+ }
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import TextMarker from '../text/TextMarker.svelte';
3
+ import { getConfiguration } from '../../configuration/config.svelte';
4
+ import { usePlot } from '../../layout/plot/usePlot';
5
+ import { BoxLabel, type Labelled } from './BoxLabel';
6
+ import { MARK_EVENTS } from '../../types/markers/channels/commonMarkerEvents';
7
+ import type { TextLabelConfig } from '../../types/markers/basic/text';
8
+
9
+ /**
10
+ * The `label` of a marker that draws a box per row: a `text` marker anchored in each box and fitted to it. A marker
11
+ * with a label only has to render `<MarkerLabel {marker} />` after its own shapes. Nothing is drawn without a
12
+ * `label`, or when the marker does not say where its boxes are.
13
+ */
14
+ const { marker }: { marker: Labelled & { label?: TextLabelConfig } } = $props();
15
+ const plot = usePlot();
16
+ const cfg = $derived(getConfiguration());
17
+
18
+ const labelled = $derived(
19
+ BoxLabel.of(marker, {
20
+ isHorizontal: plot.axes?.isHorizontal,
21
+ gap: cfg.text.label.gap,
22
+ scales: () => plot.svelteplot.scales as never
23
+ })
24
+ );
25
+ /** A label with a handler of its own listens for the pointer; one without leaves it to the shape under it. */
26
+ const listens = $derived(MARK_EVENTS.some((name) => !!marker.label?.[name]));
27
+ </script>
28
+
29
+ {#if labelled}
30
+ <g pointer-events={listens ? undefined : 'none'}>
31
+ <TextMarker marker={labelled.marker} box={labelled.box} />
32
+ </g>
33
+ {/if}
@@ -0,0 +1,10 @@
1
+ import { type Labelled } from './BoxLabel';
2
+ import type { TextLabelConfig } from '../../types/markers/basic/text';
3
+ type $$ComponentProps = {
4
+ marker: Labelled & {
5
+ label?: TextLabelConfig;
6
+ };
7
+ };
8
+ declare const MarkerLabel: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type MarkerLabel = ReturnType<typeof MarkerLabel>;
10
+ export default MarkerLabel;
@@ -1,31 +1,19 @@
1
- import type { StrokeStyle } from '../../types/plots/styling';
2
- /** A stroke channel per row: a field key of the row, or a function of it. */
3
- type RowChannel<T> = string | ((d: never) => T) | undefined;
1
+ import type { PerRow, StrokeStyle } from '../../types/plots/styling';
4
2
  /**
5
- * The stroke of a line-like marker that takes it row by row (a `rule`, an
6
- * `arrow`): each of `stroke`, `strokeWidth` and `strokeOpacity` is a field of
7
- * the row or a function of it, else the marker's own `styles`, else a default.
8
- * A color and an opacity per row are drawn as they are - svelteplot would map
9
- * them through its own color and opacity scales.
3
+ * The stroke of a line-like marker that takes it row by row (a `rule`, an `arrow`): each of
4
+ * `stroke`, `strokeWidth` and `strokeOpacity` in the marker's `styles` is a value or a function of
5
+ * the row, else a default. A color and an opacity per row are drawn as they are - svelteplot would
6
+ * map them through its own color and opacity scales.
10
7
  */
11
8
  export declare class RowStroke {
12
- #private;
13
- /** The props a mark takes for `channels`, over `styles` and the given `fallback`s. */
14
- static of(channels: {
15
- stroke?: RowChannel<string | undefined>;
16
- strokeWidth?: RowChannel<number>;
17
- strokeOpacity?: RowChannel<number>;
18
- }, styles: StrokeStyle | undefined, fallback: {
9
+ /** The props a mark takes for `styles`, over the given `fallback`s; `rows` are what a string in `styles` can name a field of. */
10
+ static of(styles: PerRow<StrokeStyle> | undefined, rows: readonly unknown[], fallback: {
19
11
  stroke: string;
20
12
  strokeWidth?: number;
21
13
  strokeOpacity?: number;
22
14
  }): {
23
- stroke: string | {
24
- value: (d: Record<string, unknown>) => string | undefined;
25
- scale: null;
26
- };
27
- strokeWidth: number | ((d: Record<string, unknown>) => number) | undefined;
15
+ stroke: string;
16
+ strokeWidth: number | undefined;
28
17
  strokeOpacity: never;
29
18
  };
30
19
  }
31
- export {};
@@ -1,28 +1,17 @@
1
+ import { RowStyle } from './RowStyle';
1
2
  /**
2
- * The stroke of a line-like marker that takes it row by row (a `rule`, an
3
- * `arrow`): each of `stroke`, `strokeWidth` and `strokeOpacity` is a field of
4
- * the row or a function of it, else the marker's own `styles`, else a default.
5
- * A color and an opacity per row are drawn as they are - svelteplot would map
6
- * them through its own color and opacity scales.
3
+ * The stroke of a line-like marker that takes it row by row (a `rule`, an `arrow`): each of
4
+ * `stroke`, `strokeWidth` and `strokeOpacity` in the marker's `styles` is a value or a function of
5
+ * the row, else a default. A color and an opacity per row are drawn as they are - svelteplot would
6
+ * map them through its own color and opacity scales.
7
7
  */
8
8
  export class RowStroke {
9
- /** A per-row channel as the function svelteplot reads. */
10
- static #accessor(channel) {
11
- return typeof channel === 'string'
12
- ? (d) => d[channel]
13
- : channel;
14
- }
15
- /** The props a mark takes for `channels`, over `styles` and the given `fallback`s. */
16
- static of(channels, styles, fallback) {
17
- const color = RowStroke.#accessor(channels.stroke);
18
- const width = RowStroke.#accessor(channels.strokeWidth);
19
- const opacity = RowStroke.#accessor(channels.strokeOpacity);
9
+ /** The props a mark takes for `styles`, over the given `fallback`s; `rows` are what a string in `styles` can name a field of. */
10
+ static of(styles, rows, fallback) {
20
11
  return {
21
- stroke: color ? { value: color, scale: null } : (styles?.stroke ?? fallback.stroke),
22
- strokeWidth: width ?? styles?.strokeWidth ?? fallback.strokeWidth,
23
- strokeOpacity: (opacity
24
- ? { value: opacity, scale: null }
25
- : (styles?.strokeOpacity ?? fallback.strokeOpacity))
12
+ stroke: RowStyle.of(styles?.stroke, rows) ?? fallback.stroke,
13
+ strokeWidth: RowStyle.field(styles?.strokeWidth) ?? fallback.strokeWidth,
14
+ strokeOpacity: (RowStyle.opacity(styles?.strokeOpacity) ?? fallback.strokeOpacity)
26
15
  };
27
16
  }
28
17
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A marker's style as svelteplot reads it. A style that depends on the row is handed over as the
3
+ * value of a channel with no scale: svelteplot would otherwise map what it gives through its own
4
+ * color or opacity scale, and a row's opacity of 0.55 would no longer be 0.55.
5
+ */
6
+ export declare class RowStyle {
7
+ #private;
8
+ /**
9
+ * A color, as a prop of a mark: a function of the row is a channel with no scale; a string is the
10
+ * field of the rows it names (when they have one of that name), else the color itself.
11
+ */
12
+ static of<T>(style: T | ((row: never) => T) | undefined, rows?: readonly unknown[]): T | undefined;
13
+ /**
14
+ * The color each row is drawn with, as {@link of} reads `style`: a function of the row, the field of the rows a
15
+ * string names, else the color itself. `undefined` when there is no color to read.
16
+ */
17
+ static colorOf(style: unknown, rows?: readonly unknown[]): ((row: unknown) => string | undefined) | undefined;
18
+ /** An opacity: a number as it is, a string the field of the rows it names, a function of the row; the last two with no scale. */
19
+ static opacity(style: number | string | ((row: never) => number) | undefined): number | undefined;
20
+ /** Any other number (a width, a radius, an inset): a string is the field of the rows it names, a function is called with the row. */
21
+ static field<T>(style: T | string | ((row: never) => T) | undefined): T | undefined;
22
+ /** \`styles\` without its functions - for a shape that stands for no row (a hover band), where there is nothing to call them with. */
23
+ static flat<TStyle extends object>(styles: TStyle | undefined): {
24
+ [K in keyof TStyle]?: Exclude<TStyle[K], (...args: never[]) => unknown>;
25
+ };
26
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A marker's style as svelteplot reads it. A style that depends on the row is handed over as the
3
+ * value of a channel with no scale: svelteplot would otherwise map what it gives through its own
4
+ * color or opacity scale, and a row's opacity of 0.55 would no longer be 0.55.
5
+ */
6
+ export class RowStyle {
7
+ /** The accessor that reads `key` off a row. */
8
+ static #read = (key) => (row) => row?.[key];
9
+ /**
10
+ * A color, as a prop of a mark: a function of the row is a channel with no scale; a string is the
11
+ * field of the rows it names (when they have one of that name), else the color itself.
12
+ */
13
+ static of(style, rows) {
14
+ if (typeof style === 'function')
15
+ return { value: style, scale: null };
16
+ if (typeof style === 'string' && RowStyle.#isField(style, rows))
17
+ return { value: RowStyle.#read(style), scale: null };
18
+ return style;
19
+ }
20
+ /**
21
+ * The color each row is drawn with, as {@link of} reads `style`: a function of the row, the field of the rows a
22
+ * string names, else the color itself. `undefined` when there is no color to read.
23
+ */
24
+ static colorOf(style, rows) {
25
+ if (typeof style === 'function')
26
+ return (row) => style(row);
27
+ if (typeof style !== 'string')
28
+ return undefined;
29
+ if (RowStyle.#isField(style, rows)) {
30
+ const read = RowStyle.#read(style);
31
+ return (row) => read(row);
32
+ }
33
+ return () => style;
34
+ }
35
+ /** An opacity: a number as it is, a string the field of the rows it names, a function of the row; the last two with no scale. */
36
+ static opacity(style) {
37
+ if (typeof style === 'function')
38
+ return { value: style, scale: null };
39
+ if (typeof style === 'string')
40
+ return { value: RowStyle.#read(style), scale: null };
41
+ return style;
42
+ }
43
+ /** Any other number (a width, a radius, an inset): a string is the field of the rows it names, a function is called with the row. */
44
+ static field(style) {
45
+ return (typeof style === 'string' ? RowStyle.#read(style) : style);
46
+ }
47
+ /** Whether a string names a field of the rows - at least one has it - rather than being a color. */
48
+ static #isField(name, rows) {
49
+ return !!rows?.some((row) => typeof row === 'object' && row !== null && name in row);
50
+ }
51
+ /** \`styles\` without its functions - for a shape that stands for no row (a hover band), where there is nothing to call them with. */
52
+ static flat(styles) {
53
+ return Object.fromEntries(Object.entries(styles ?? {}).filter(([, value]) => typeof value !== 'function'));
54
+ }
55
+ }
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Vector } from 'svelteplot';
3
4
  import type { GeoVectorMarkerConfig } from '../../types/markers/geo';
4
5
 
@@ -10,12 +11,14 @@
10
11
  * shape actually draws with, matching svelteplot's own convention.
11
12
  */
12
13
  let { marker }: { marker: GeoVectorMarkerConfig } = $props();
14
+ const events = MarkerEvents.use(() => marker);
13
15
 
14
16
  const shape = $derived(marker.shape ?? 'arrow-filled');
15
17
  const isFilledShape = $derived(shape === 'arrow-filled');
16
18
  </script>
17
19
 
18
20
  <Vector
21
+ {...events.props}
19
22
  data={marker.data}
20
23
  x={marker.x}
21
24
  y={marker.y}
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { RowStyle } from '../utils/RowStyle';
3
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
4
  import { Voronoi } from 'svelteplot';
3
5
  import { MarkerChannels } from '../utils/MarkerChannels';
4
6
  import { getConfiguration } from '../../configuration/config.svelte';
@@ -6,6 +8,7 @@
6
8
 
7
9
  /** Renders one `'voronoi'` marker as `<Voronoi>` - one Voronoi cell per point. */
8
10
  let { marker }: { marker: VoronoiMarkerConfig } = $props();
11
+ const events = MarkerEvents.use(() => marker);
9
12
 
10
13
  const cfg = $derived(getConfiguration());
11
14
  const data = $derived(marker.data ?? []);
@@ -13,14 +16,15 @@
13
16
  </script>
14
17
 
15
18
  <Voronoi
19
+ {...events.props}
16
20
  {data}
17
21
  x={marker.x}
18
22
  y={marker.y}
19
23
  z={marker.grouping}
20
- fill={marker.styles?.fill}
21
- fillOpacity={marker.styles?.fillOpacity}
22
- stroke={marker.styles?.stroke ?? channelColor}
23
- strokeWidth={marker.styles?.strokeWidth}
24
- strokeOpacity={marker.styles?.strokeOpacity}
24
+ fill={RowStyle.of(marker.styles?.fill, data)}
25
+ fillOpacity={RowStyle.opacity(marker.styles?.fillOpacity)}
26
+ stroke={RowStyle.of(marker.styles?.stroke, data) ?? channelColor}
27
+ strokeWidth={RowStyle.field(marker.styles?.strokeWidth)}
28
+ strokeOpacity={RowStyle.opacity(marker.styles?.strokeOpacity)}
25
29
  strokeDasharray={marker.styles?.strokeDasharray}
26
30
  />
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { VoronoiMesh } from 'svelteplot';
3
4
  import { MarkerChannels } from '../utils/MarkerChannels';
4
5
  import { getConfiguration } from '../../configuration/config.svelte';
@@ -6,6 +7,7 @@
6
7
 
7
8
  /** Renders one `'voronoiMesh'` marker as `<VoronoiMesh>` - the Voronoi diagram as a single mesh. */
8
9
  let { marker }: { marker: VoronoiMeshMarkerConfig } = $props();
10
+ const events = MarkerEvents.use(() => marker);
9
11
 
10
12
  const cfg = $derived(getConfiguration());
11
13
  const data = $derived(marker.data ?? []);
@@ -13,6 +15,7 @@
13
15
  </script>
14
16
 
15
17
  <VoronoiMesh
18
+ {...events.props}
16
19
  {data}
17
20
  x={marker.x}
18
21
  y={marker.y}
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { RowStyle } from '../utils/RowStyle';
3
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
4
  import { WaffleX, WaffleY } from 'svelteplot';
3
5
  import { usePlot } from '../../layout/plot/usePlot';
4
6
  import { MarkerChannels } from '../utils/MarkerChannels';
@@ -7,6 +9,7 @@
7
9
 
8
10
  /** Renders one `'waffle'` marker as `<WaffleX>` or `<WaffleY>`, picked from the way the plot runs (`usePlot().axes`). */
9
11
  let { marker }: { marker: WaffleMarkerConfig } = $props();
12
+ const events = MarkerEvents.use(() => marker);
10
13
 
11
14
  const plot = usePlot();
12
15
  const isHorizontal = $derived(plot.axes?.isHorizontal ?? false);
@@ -19,6 +22,7 @@
19
22
  </script>
20
23
 
21
24
  <WaffleMark
25
+ {...events.props}
22
26
  {data}
23
27
  x={marker.x}
24
28
  y={marker.y}
@@ -27,10 +31,10 @@
27
31
  multiple={marker.multiple}
28
32
  gap={marker.gap}
29
33
  round={marker.round}
30
- borderRadius={marker.styles?.borderRadius}
34
+ borderRadius={RowStyle.field(marker.styles?.borderRadius)}
31
35
  {fill}
32
- fillOpacity={marker.styles?.fillOpacity}
33
- stroke={marker.styles?.stroke}
34
- strokeWidth={marker.styles?.strokeWidth}
35
- strokeOpacity={marker.styles?.strokeOpacity}
36
+ fillOpacity={RowStyle.opacity(marker.styles?.fillOpacity)}
37
+ stroke={RowStyle.of(marker.styles?.stroke, data)}
38
+ strokeWidth={RowStyle.field(marker.styles?.strokeWidth)}
39
+ strokeOpacity={RowStyle.opacity(marker.styles?.strokeOpacity)}
36
40
  />
@@ -4,13 +4,14 @@ import type { FacetConfig } from '../types/layout/facet';
4
4
  import type { LegendState } from '../layout/legend/LegendState.svelte';
5
5
  import type { HoverStrategy } from '../types/layout/hover';
6
6
  import type { Accessor } from '../types/plots/data/common';
7
+ import type { GroupingAccessor } from '../types/layout/mapping';
7
8
  /** The core props an axis plot kind seeds its pipeline from. */
8
9
  export type AxisPipelineProps<TData extends Record<string, unknown>> = {
9
10
  series?: unknown[];
10
11
  data?: TData[];
11
12
  x?: Accessor<TData, unknown>;
12
13
  y?: Accessor<TData, unknown>;
13
- grouping?: Accessor<TData, unknown>;
14
+ grouping?: GroupingAccessor<TData>;
14
15
  color?: Accessor<TData, unknown>;
15
16
  opacity?: unknown;
16
17
  r?: unknown;
@@ -20,6 +21,7 @@ export type AxisPipelineProps<TData extends Record<string, unknown>> = {
20
21
  error?: Accessor<TData, unknown>;
21
22
  target?: unknown;
22
23
  ranges?: unknown[];
24
+ value?: Accessor<TData, unknown>;
23
25
  segments?: unknown;
24
26
  styles?: {
25
27
  gaps?: unknown;
@@ -215,6 +215,7 @@ export class Pipeline {
215
215
  error: props.error,
216
216
  target: props.target,
217
217
  ranges: props.ranges,
218
+ value: props.value,
218
219
  segments: props.segments ?? {},
219
220
  styles: props.styles,
220
221
  gaps: props.styles?.gaps,
@@ -1,5 +1,5 @@
1
- import { buildSeries } from '../../utils/grouping';
2
- import { ColorRamp } from '../../utils/color';
1
+ import { buildSeries, Grouping } from '../../utils/grouping';
2
+ import { ColorRamp, ColorTones } from '../../utils/color';
3
3
  import { resolveAccessor } from '../../utils/accessors';
4
4
  import { buildGroupedSeries, buildScopedMarkerGroups } from '../../plots/utils/segments';
5
5
  import { buildGapFillMaps, isGapValue } from '../../plots/utils/gaps';
@@ -26,7 +26,7 @@ export class DataPhase {
26
26
  const options = this.#options;
27
27
  const { series, data, segments, scales } = state;
28
28
  const { connect = true, color: segmentColor } = options.segments ?? {};
29
- const seriesGrouping = DataPhase.#buildGrouping(state);
29
+ const seriesGrouping = DataPhase.#buildGrouping(state, options.nested);
30
30
  const built = series ?? buildSeries(data ?? [], state.x, state.y, seriesGrouping);
31
31
  const fullSeries = options.orderSeries ? options.orderSeries(built, state) : built;
32
32
  const axes = PlotAxes.build({
@@ -35,11 +35,18 @@ export class DataPhase {
35
35
  given: { x: state.x !== undefined, y: state.y !== undefined },
36
36
  discrete: options.discreteAxes
37
37
  });
38
- const colorForSeries = ColorRamp.resolve(scales.color, (options.colorOrder ? options.colorOrder(fullSeries) : fullSeries).map((s) => s.name), ctx.cfg.palette);
38
+ const seriesColors = ColorRamp.resolve(scales.color, (options.colorOrder ? options.colorOrder(fullSeries) : fullSeries).map((s) => s.name), ctx.cfg.palette);
39
+ const colorForPath = series ? undefined : DataPhase.#buildPathColors(state, ctx);
40
+ // A plot that splits its series by every level colors each one by its path; a nested one by its outermost.
41
+ const colorForSeries = colorForPath && !options.nested
42
+ ? DataPhase.#byPath(state, colorForPath, seriesColors)
43
+ : seriesColors;
39
44
  const panelSeries = state.panels && state.panelOf && !series
40
45
  ? FacetPanels.splitSeries(fullSeries, state.panels, state.panelOf)
41
46
  : fullSeries;
42
- const resolvedSeries = ctx.interaction?.legend?.withoutOmitted(panelSeries) ?? panelSeries;
47
+ const legend = ctx.interaction?.legend;
48
+ const resolvedSeries = legend?.withoutOmitted(panelSeries) ?? panelSeries;
49
+ const removedSeries = legend?.removed(panelSeries);
43
50
  const baseFor = BasicStyles.of(state.styles, options.segments?.base);
44
51
  const groupedSeries = buildGroupedSeries(resolvedSeries, segments, connect, baseFor);
45
52
  const scopedGroups = buildScopedMarkerGroups({
@@ -57,6 +64,7 @@ export class DataPhase {
57
64
  ...state,
58
65
  axes,
59
66
  resolvedSeries,
67
+ ...(removedSeries ? { removedSeries } : {}),
60
68
  seriesGrouping,
61
69
  allSeriesNames: fullSeries.map((s) => s.name),
62
70
  groupedSeries,
@@ -64,20 +72,53 @@ export class DataPhase {
64
72
  flat: resolvedSeries.flatMap((s) => s.data),
65
73
  scopedGroups,
66
74
  colorForSeries,
75
+ ...(colorForPath ? { colorForPath } : {}),
67
76
  ...(gapFillMaps ? { gapFillMaps } : {})
68
77
  };
69
78
  return options.extend ? options.extend(next, ctx) : next;
70
79
  };
71
80
  /**
72
- * The key a plot's rows split into series by: its `grouping`, or its `color`
73
- * when that names categories. A numeric `color` is a continuous value (a
74
- * heatmap's cells), so it colors rows without grouping them.
81
+ * The color of a path of the plot's `grouping` levels, when `scales.color.depth` reaches past the outermost
82
+ * one: each outermost value its categorical color, and every path down to `depth` a tone of it (see
83
+ * {@link ColorTones.hierarchy}). `undefined` for a grouping of one level, or a depth of 1.
75
84
  */
76
- static #buildGrouping(state
85
+ static #buildPathColors(state, ctx) {
86
+ const depth = state.scales.color?.depth ?? 1;
87
+ const rows = state.data ?? [];
88
+ const levels = Grouping.levels(state.grouping, rows).map((level) => resolveAccessor(level));
89
+ if (depth <= 1 || levels.length <= 1)
90
+ return undefined;
91
+ const paths = rows.map((row) => levels.map((read) => String(read(row))));
92
+ const outermost = [...new Set(paths.map((path) => path[0]))];
93
+ const colorOf = ColorRamp.resolve(state.scales.color, outermost, ctx.cfg.palette);
94
+ return ColorTones.hierarchy(paths, depth, (value) => colorOf(value));
95
+ }
96
+ /** A series' color as the color of its path - its name is the key its levels make together. */
97
+ static #byPath(state, colorForPath, fallback) {
98
+ const rows = state.data ?? [];
99
+ const key = resolveAccessor(Grouping.key(state.grouping, rows));
100
+ const levels = Grouping.levels(state.grouping, rows).map((level) => resolveAccessor(level));
101
+ const pathOf = new Map();
102
+ for (const row of rows) {
103
+ const name = String(key(row));
104
+ if (!pathOf.has(name))
105
+ pathOf.set(name, levels.map((read) => String(read(row))));
106
+ }
107
+ return (name) => {
108
+ const path = pathOf.get(name);
109
+ return path ? colorForPath(path) : fallback(name);
110
+ };
111
+ }
112
+ /**
113
+ * The key a plot's rows split into series by: its `grouping` (its levels together, or the outermost
114
+ * one alone for a `nested` kind), or its `color` when that names categories. A numeric `color` is a
115
+ * continuous value (a heatmap's cells), so it colors rows without grouping them.
116
+ */
117
+ static #buildGrouping(state, nested = false
77
118
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
119
  ) {
79
120
  if (state.grouping !== undefined)
80
- return state.grouping;
121
+ return Grouping.key(state.grouping, state.data, nested);
81
122
  const color = state.color;
82
123
  if (color === undefined || !state.data)
83
124
  return color;
@@ -52,7 +52,8 @@ export class HoverPhase {
52
52
  label: label,
53
53
  defaultStrategy: options.strategy ?? HoverSettings.strategyOf(state.axes),
54
54
  pointMatching: options.pointMatching,
55
- number: NumberFormat.of(NumberFormat.displayOf(state.tooltip), ctx.cfg)
55
+ number: NumberFormat.of(NumberFormat.displayOf(state.tooltip), ctx.cfg),
56
+ context: HoverPhase.#contextOf(options.context?.(state))
56
57
  });
57
58
  const hitAreas = options.hitAreas?.(state, hover, ctx) ?? [];
58
59
  return {
@@ -94,6 +95,13 @@ export class HoverPhase {
94
95
  };
95
96
  });
96
97
  }
98
+ /** What the plot knows of a hovered row: its series, and whatever its kind adds. */
99
+ static #contextOf(kind) {
100
+ return (row) => {
101
+ const series = row[SERIES_KEY];
102
+ return { ...(typeof series === 'string' ? { series } : {}), ...kind?.(row) };
103
+ };
104
+ }
97
105
  /**
98
106
  * Which processed rows can be hovered: not a gap (no value where it sits, or
99
107
  * filled in by `styles.gaps`), and, while the legend narrows a continuous
@@ -25,7 +25,8 @@ export class MappingPhase {
25
25
  opacity: MappingPhase.#channel(state.opacity),
26
26
  symbol: MappingPhase.#channel(state.symbol),
27
27
  r: size(state.r),
28
- length: size(state.length)
28
+ length: size(state.length),
29
+ value: MappingPhase.#channel(state.value)
29
30
  };
30
31
  const next = { ...state, channels };
31
32
  return this.#options.extend ? this.#options.extend(next, ctx) : next;
@@ -0,0 +1,18 @@
1
+ import type { PlotStylesConfig } from '../../types/configuration/styling';
2
+ /**
3
+ * A plot's markers with their styles as svelteplot reads them, before any is drawn: a named line style
4
+ * (`strokeDasharray: 'dashed'`) is the SVG dash pattern it stands for, wherever in a marker's config it is
5
+ * set - its `styles`, a label's, a part's (a box's whiskers) - and a label inside its shape with no `fill` of its
6
+ * own is written in the color that reads best on the shape's. A marker component draws what it is given.
7
+ */
8
+ export declare class MarkerStyles {
9
+ #private;
10
+ /**
11
+ * Every marker of `markers`, resolved. A marker with nothing to resolve is kept as it is. `rows` are what a
12
+ * marker with no `data` of its own draws.
13
+ */
14
+ static resolveAll<T>(markers: T[], plot?: {
15
+ cfg?: PlotStylesConfig;
16
+ rows?: unknown[];
17
+ }): T[];
18
+ }