@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,111 @@
1
+ import { MarkerChannels } from '../../markers/utils/MarkerChannels';
2
+ import { RowStyle } from '../../markers/utils/RowStyle';
3
+ import { CssColor } from '../../utils/color/CssColor';
4
+ /**
5
+ * A plot's markers with their styles as svelteplot reads them, before any is drawn: a named line style
6
+ * (`strokeDasharray: 'dashed'`) is the SVG dash pattern it stands for, wherever in a marker's config it is
7
+ * set - its `styles`, a label's, a part's (a box's whiskers) - and a label inside its shape with no `fill` of its
8
+ * own is written in the color that reads best on the shape's. A marker component draws what it is given.
9
+ */
10
+ export class MarkerStyles {
11
+ /** The SVG dash pattern each named line style stands for; a solid line has none. */
12
+ static #DASHES = {
13
+ solid: undefined,
14
+ dashed: '6 4',
15
+ dotted: '2 3',
16
+ dashdot: '6 3 2 3'
17
+ };
18
+ /** The style fields that take a dash pattern. */
19
+ static #DASH_KEYS = new Set(['strokeDasharray', 'dotStrokeDasharray']);
20
+ /** Fields that hold rows rather than config, which are never walked. */
21
+ static #ROW_KEYS = new Set(['data', 'features']);
22
+ /**
23
+ * Every marker of `markers`, resolved. A marker with nothing to resolve is kept as it is. `rows` are what a
24
+ * marker with no `data` of its own draws.
25
+ */
26
+ static resolveAll(markers, plot = {}) {
27
+ return markers.map((marker) => MarkerStyles.#contrasted(MarkerStyles.#walk(marker), plot));
28
+ }
29
+ /** The marker with its label inside its shape written in the candidate that reads best on each row's color. */
30
+ static #contrasted(marker, plot) {
31
+ const { cfg } = plot;
32
+ if (!cfg || !MarkerStyles.#isPlain(marker))
33
+ return marker;
34
+ const label = marker.label;
35
+ if (!MarkerStyles.#isPlain(label) || label.outside || label.styles?.fill !== undefined) {
36
+ return marker;
37
+ }
38
+ const colorOf = MarkerStyles.#shapeColor(marker, marker.data ?? plot.rows ?? [], cfg);
39
+ if (!colorOf)
40
+ return marker;
41
+ const candidates = cfg.text.label.contrast;
42
+ const opacityOf = MarkerStyles.#opacity(marker.styles?.fillOpacity);
43
+ // What the label is read on: the shape's color, blended with the plot's background as far as it is see-through.
44
+ const fill = (row) => {
45
+ const color = colorOf(row);
46
+ if (!color)
47
+ return undefined;
48
+ const seen = CssColor.composite(color, opacityOf(row), cfg.background);
49
+ return CssColor.contrastText(seen, candidates);
50
+ };
51
+ return { ...marker, label: { ...label, styles: { ...label.styles, fill } } };
52
+ }
53
+ /** The opacity each row's shape is drawn with: a number, the field of the rows a string names, a function of the row. */
54
+ static #opacity(style) {
55
+ const read = typeof style === 'function'
56
+ ? (row) => style(row)
57
+ : typeof style === 'string'
58
+ ? (row) => row?.[style]
59
+ : () => style;
60
+ return (row) => {
61
+ const value = Number(read(row) ?? 1);
62
+ return Number.isFinite(value) ? value : 1;
63
+ };
64
+ }
65
+ /** The color each row's shape is drawn with: the marker's `color` channel, else its `styles.fill`. */
66
+ static #shapeColor(marker, rows, cfg) {
67
+ const fill = marker.styles?.fill;
68
+ return (MarkerChannels.buildFill(rows, marker.color, marker.scales, cfg) ??
69
+ RowStyle.colorOf(fill, rows) ??
70
+ (marker.type === 'waffle' ? () => cfg.palette[0] : undefined));
71
+ }
72
+ /** A dash pattern for `style`: a named line style's, any other as it is; a function of the row gives each row's. */
73
+ static #dash(style) {
74
+ if (typeof style === 'function')
75
+ return (...args) => MarkerStyles.#pattern(style(...args)) ?? 'none';
76
+ return MarkerStyles.#pattern(style);
77
+ }
78
+ static #pattern(style) {
79
+ return typeof style === 'string' && style in MarkerStyles.#DASHES
80
+ ? MarkerStyles.#DASHES[style]
81
+ : style;
82
+ }
83
+ /** `value` with its dash styles resolved: plain objects and arrays walked, copied only where something changes. */
84
+ static #walk(value) {
85
+ if (Array.isArray(value)) {
86
+ const next = value.map((item) => MarkerStyles.#walk(item));
87
+ return next.some((item, i) => item !== value[i]) ? next : value;
88
+ }
89
+ if (!MarkerStyles.#isPlain(value))
90
+ return value;
91
+ let copy;
92
+ for (const [key, own] of Object.entries(value)) {
93
+ if (MarkerStyles.#ROW_KEYS.has(key))
94
+ continue;
95
+ const next = MarkerStyles.#DASH_KEYS.has(key)
96
+ ? MarkerStyles.#dash(own)
97
+ : MarkerStyles.#walk(own);
98
+ if (next === own)
99
+ continue;
100
+ copy ??= { ...value };
101
+ copy[key] = next;
102
+ }
103
+ return copy ?? value;
104
+ }
105
+ static #isPlain(value) {
106
+ if (typeof value !== 'object' || value === null)
107
+ return false;
108
+ const proto = Object.getPrototypeOf(value);
109
+ return proto === Object.prototype || proto === null;
110
+ }
111
+ }
@@ -4,7 +4,7 @@ import type { AxisPlotCtx, AxisPlotState, MarkersStageOptions, PhaseStage } from
4
4
  * plot kind's own marks, then what the "statistics" phase left in
5
5
  * `state.markers` (the author's markers and the plot's statistics), then its
6
6
  * value labels when `styles.values.show` turns them on, each with what every
7
- * label shares ({@link ValueLabels.hooks}).
7
+ * label shares ({@link ValueLabels.hooks}), and every style as svelteplot reads it ({@link MarkerStyles}).
8
8
  */
9
9
  export declare class MarkersPhase<TState extends AxisPlotState<any, any, any, any>, TCtx extends AxisPlotCtx> implements PhaseStage<TState, TCtx> {
10
10
  #private;
@@ -1,10 +1,11 @@
1
1
  import { MarkerChannels } from '../../markers/utils/MarkerChannels';
2
+ import { MarkerStyles } from './MarkerStyles';
2
3
  /**
3
4
  * The shared "markers" phase: everything the plot draws, bottom to top - the
4
5
  * plot kind's own marks, then what the "statistics" phase left in
5
6
  * `state.markers` (the author's markers and the plot's statistics), then its
6
7
  * value labels when `styles.values.show` turns them on, each with what every
7
- * label shares ({@link ValueLabels.hooks}).
8
+ * label shares ({@link ValueLabels.hooks}), and every style as svelteplot reads it ({@link MarkerStyles}).
8
9
  */
9
10
  export class MarkersPhase {
10
11
  phase = 'markers';
@@ -16,13 +17,14 @@ export class MarkersPhase {
16
17
  const options = this.#options;
17
18
  const labels = state.valueLabels;
18
19
  const valueLabels = labels?.show && options.valueLabels
19
- ? options
20
- .valueLabels(labels, state, ctx)
21
- .map((marker) => ({ ...labels.hooks(ctx), ...marker }))
20
+ ? options.valueLabels(labels, state, ctx).map((marker) => {
21
+ const { styles, ...hooks } = labels.hooks(ctx);
22
+ return { ...hooks, ...marker, styles: { ...styles, ...marker.styles } };
23
+ })
22
24
  : [];
23
25
  return {
24
26
  ...state,
25
- markers: MarkerChannels.inheritPosition(MarkerChannels.inheritAll([...options.marks(state, ctx), ...(state.markers ?? []), ...valueLabels], state.scales), state.processedData)
27
+ markers: MarkerStyles.resolveAll(MarkerChannels.inheritPosition(MarkerChannels.inheritAll([...options.marks(state, ctx), ...(state.markers ?? []), ...valueLabels], state.scales), state.processedData), { cfg: ctx.cfg, rows: state.processedData?.data })
26
28
  };
27
29
  };
28
30
  }
@@ -68,7 +68,7 @@ export class StatisticsPhase {
68
68
  colorFor: colorForSeries
69
69
  }),
70
70
  ...Statistics.expandMarkers(AuthorMarkers.prepare(positioned, {
71
- grouping: inheritGrouping ? state.grouping : undefined,
71
+ grouping: inheritGrouping ? state.seriesGrouping : undefined,
72
72
  color: inheritGrouping ? state.color : undefined,
73
73
  defaultColor,
74
74
  flat,
@@ -6,6 +6,7 @@ import { NormalizeTransform } from './methods/NormalizeTransform';
6
6
  import { PositionTransform } from './methods/PositionTransform';
7
7
  import { OrderTransform } from './methods/OrderTransform';
8
8
  import { GroupTransform } from './methods/GroupTransform';
9
+ import { TreemapTransform } from './methods/TreemapTransform';
9
10
  /** Each transform type's adapter: its channels, how it runs, and its own fixes. */
10
11
  const ADAPTERS = {
11
12
  stackX: StackTransform.x,
@@ -47,7 +48,8 @@ const ADAPTERS = {
47
48
  groupX: GroupTransform.x,
48
49
  groupY: GroupTransform.y,
49
50
  groupZ: GroupTransform.z,
50
- group: GroupTransform.grid
51
+ group: GroupTransform.grid,
52
+ treemap: TreemapTransform.adapter
51
53
  };
52
54
  /** Runs a marker's or a plot's transforms through svelteplot, one type's adapter at a time. */
53
55
  export class Transforms {
@@ -39,7 +39,12 @@ export class TransformsPhase {
39
39
  }
40
40
  run = (state, ctx) => {
41
41
  const options = this.#options;
42
- const series = (state.resolvedSeries ?? []);
42
+ const visible = (state.resolvedSeries ?? []);
43
+ // An omitted part of a whole is laid out with the rest, so they keep their places; it is dropped after.
44
+ const removed = state.removedSeries;
45
+ const kept = options.keepsOmitted && removed?.mode === 'omit' ? removed.series : [];
46
+ const series = [...visible, ...kept];
47
+ const omittedIds = new Set(kept.map(seriesIdOf));
43
48
  // A gap `styles.gaps` fills carries its filled value and kind, and reads at that value.
44
49
  const fillsOf = (s) => state.gapFillMaps?.get(seriesIdOf(s));
45
50
  const data = series.flatMap((s) => s.data.map((row) => ({
@@ -60,13 +65,21 @@ export class TransformsPhase {
60
65
  const read = resolveAccessor(own);
61
66
  shared[valueAxis] = (row) => row[GAP_VALUE] ?? read(row);
62
67
  }
63
- const { x, y } = shared;
64
68
  const transforms = options.transforms
65
69
  ? options.transforms(state, ctx)
66
70
  : (state.transforms ?? []);
71
+ const rescaled = TransformsPhase.#rescaled(state, transforms, shared, valueAxis);
72
+ if (rescaled)
73
+ shared[valueAxis] = rescaled;
74
+ const { x, y } = shared;
67
75
  if (!transforms.length) {
68
76
  const channels = Object.fromEntries(Object.entries({ x, y }).filter(([, channel]) => channel !== undefined));
69
- const processedData = { data, channels, options: {}, transforms };
77
+ const processedData = {
78
+ data: data.filter((row) => !omittedIds.has(row[SERIES_ID_KEY])),
79
+ channels,
80
+ options: {},
81
+ transforms
82
+ };
70
83
  return this.#withGroups({ ...state, processedData });
71
84
  }
72
85
  const out = TransformsPhase.#runByPanel(data, (rows) => Transforms.resolve({ data: rows, x, y, grouping: SERIES_ID_KEY, transforms }, rows));
@@ -82,7 +95,7 @@ export class TransformsPhase {
82
95
  }
83
96
  // An aggregating transform (bin, group) keeps only its `z`, the series id: put the name back.
84
97
  const nameById = new Map(series.map((s) => [seriesIdOf(s), s.name]));
85
- const rows = out.data;
98
+ const rows = out.data.filter((row) => !omittedIds.has(row[SERIES_ID_KEY]));
86
99
  for (const row of rows) {
87
100
  if (row[SERIES_KEY] === undefined && row[SERIES_ID_KEY] !== undefined) {
88
101
  row[SERIES_KEY] = nameById.get(row[SERIES_ID_KEY]);
@@ -147,6 +160,47 @@ export class TransformsPhase {
147
160
  labelSelects: ValueSelection.selectors(select)
148
161
  };
149
162
  }
163
+ /**
164
+ * The value channel of a stack whose legend rescales some of its series: each visible row's value scaled by
165
+ * how much of its category's total (in its facet panel) those series took, so the visible parts make the
166
+ * whole the category had. `undefined` for a plot that stacks nothing on its value axis, or rescales nothing.
167
+ */
168
+ static #rescaled(state, transforms, shared, valueAxis) {
169
+ const removed = state.removedSeries;
170
+ const rescaled = removed?.mode === 'rescale' ? removed.series : [];
171
+ const stack = valueAxis === 'y' ? 'stackY' : 'stackX';
172
+ const own = shared[valueAxis];
173
+ const category = shared[valueAxis === 'y' ? 'x' : 'y'];
174
+ if (!rescaled.length || own === undefined || category === undefined)
175
+ return undefined;
176
+ if (!transforms.some((t) => t.type === stack))
177
+ return undefined;
178
+ const value = resolveAccessor(own);
179
+ const categoryOf = resolveAccessor(category);
180
+ const keyOf = (row, panel) => JSON.stringify([panel, categoryOf(row)]);
181
+ const totals = (series) => {
182
+ const sums = new Map();
183
+ for (const s of series) {
184
+ for (const row of s.data) {
185
+ const amount = Number(value(row));
186
+ if (!Number.isFinite(amount))
187
+ continue;
188
+ const key = keyOf(row, s.panel);
189
+ sums.set(key, (sums.get(key) ?? 0) + amount);
190
+ }
191
+ }
192
+ return sums;
193
+ };
194
+ const visible = (state.resolvedSeries ?? []);
195
+ const shown = totals(visible);
196
+ const whole = totals([...visible, ...rescaled]);
197
+ return (row) => {
198
+ const amount = Number(value(row));
199
+ const key = keyOf(row, row[PANEL_KEY]);
200
+ const part = shown.get(key) ?? 0;
201
+ return part === 0 ? amount : (amount * (whole.get(key) ?? part)) / part;
202
+ };
203
+ }
150
204
  /** What a gap-filled row carries: its interpolated value and the kind of zone that filled it. */
151
205
  static #fillTags(fill) {
152
206
  return fill ? { [GAP_VALUE]: fill.value, [GAP_KIND]: fill.kind } : {};
@@ -0,0 +1,28 @@
1
+ import type { Row, TransformAdapter } from '../../../types/phases/transforms/adapter';
2
+ /** What a treemap leaves on each row it produces, and the channels it reads them back by. */
3
+ export declare const TREEMAP_ROW: {
4
+ /** The tile's name: the value at its last level. */
5
+ readonly name: symbol;
6
+ /** The value at every level of the tile, outermost first. */
7
+ readonly path: symbol;
8
+ /** The tile's summed `value`. */
9
+ readonly value: symbol;
10
+ /** The tile's part of the whole, from `0` to `1`. */
11
+ readonly share: symbol;
12
+ readonly x1: symbol;
13
+ readonly x2: symbol;
14
+ readonly y1: symbol;
15
+ readonly y2: symbol;
16
+ readonly x: symbol;
17
+ readonly y: symbol;
18
+ };
19
+ /** A treemap as a transform: the layout of d3-hierarchy, in the shape every other transform returns. */
20
+ export declare class TreemapTransform {
21
+ static readonly adapter: TransformAdapter;
22
+ /**
23
+ * A row per tile of `channels.data`, biggest first within its group, placed in the unit square. A row whose
24
+ * value is not a positive number has no area, so it makes no tile. The layout is `aspect` wide while it is
25
+ * computed and scaled back after, so its tiles are shaped after the box they fill.
26
+ */
27
+ static layout(channels: Row, options: Row): Row;
28
+ }
@@ -0,0 +1,119 @@
1
+ import { hierarchy, treemap, treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify } from 'd3-hierarchy';
2
+ import { resolveAccessor } from '../../../utils/accessors';
3
+ import { TREEMAP_CHANNEL_KEYS } from '../../../types/transforms/treemap';
4
+ const TILES = {
5
+ squarify: treemapSquarify,
6
+ binary: treemapBinary,
7
+ slice: treemapSlice,
8
+ dice: treemapDice,
9
+ sliceDice: treemapSliceDice
10
+ };
11
+ /** What a treemap leaves on each row it produces, and the channels it reads them back by. */
12
+ export const TREEMAP_ROW = {
13
+ /** The tile's name: the value at its last level. */
14
+ name: Symbol('treemap.name'),
15
+ /** The value at every level of the tile, outermost first. */
16
+ path: Symbol('treemap.path'),
17
+ /** The tile's summed `value`. */
18
+ value: Symbol('treemap.value'),
19
+ /** The tile's part of the whole, from `0` to `1`. */
20
+ share: Symbol('treemap.share'),
21
+ x1: Symbol('treemap.x1'),
22
+ x2: Symbol('treemap.x2'),
23
+ y1: Symbol('treemap.y1'),
24
+ y2: Symbol('treemap.y2'),
25
+ x: Symbol('treemap.x'),
26
+ y: Symbol('treemap.y')
27
+ };
28
+ const read = (channel) => typeof channel === 'function'
29
+ ? channel
30
+ : (row) => row[channel];
31
+ /** A treemap as a transform: the layout of d3-hierarchy, in the shape every other transform returns. */
32
+ export class TreemapTransform {
33
+ static adapter = {
34
+ channelKeys: TREEMAP_CHANNEL_KEYS,
35
+ run: (channels, options) => TreemapTransform.layout(channels, options)
36
+ };
37
+ /**
38
+ * A row per tile of `channels.data`, biggest first within its group, placed in the unit square. A row whose
39
+ * value is not a positive number has no area, so it makes no tile. The layout is `aspect` wide while it is
40
+ * computed and scaled back after, so its tiles are shaped after the box they fill.
41
+ */
42
+ static layout(channels, options) {
43
+ const { data, value, ...rest } = channels;
44
+ if (!data)
45
+ throw new Error('treemap: missing data');
46
+ if (value === undefined)
47
+ throw new Error('treemap: missing value channel');
48
+ const size = read(value);
49
+ const levels = (options.levels ?? []).map((level) => resolveAccessor(level));
50
+ const tile = options.tile ?? 'squarify';
51
+ const aspect = Number(options.aspect);
52
+ const width = aspect > 0 && Number.isFinite(aspect) ? aspect : 1;
53
+ const root = { name: '', path: [], rows: [], children: [] };
54
+ const nodes = new Map();
55
+ const nodeOf = (path) => {
56
+ let parent = root;
57
+ for (let depth = 1; depth <= path.length; depth++) {
58
+ const key = JSON.stringify(path.slice(0, depth));
59
+ let node = nodes.get(key);
60
+ if (!node) {
61
+ node = { name: path[depth - 1], path: path.slice(0, depth), rows: [], children: [] };
62
+ nodes.set(key, node);
63
+ parent.children.push(node);
64
+ }
65
+ parent = node;
66
+ }
67
+ return parent;
68
+ };
69
+ for (const row of data) {
70
+ const area = Number(size(row));
71
+ if (!(Number.isFinite(area) && area > 0))
72
+ continue;
73
+ const leaf = nodeOf(levels.map((level) => String(level(row))));
74
+ leaf.rows.push(row);
75
+ leaf.value = (leaf.value ?? 0) + area;
76
+ }
77
+ const tiles = [];
78
+ if (root.children.length > 0 || root.value !== undefined) {
79
+ const tree = hierarchy(root, (node) => (node.children?.length ? node.children : null))
80
+ .sum((node) => node.value ?? 0)
81
+ .sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
82
+ const total = tree.value ?? 0;
83
+ treemap().size([width, 1]).tile(TILES[tile]).round(false)(tree);
84
+ for (const leaf of tree.leaves()) {
85
+ const at = leaf;
86
+ const { rows, path, name } = leaf.data;
87
+ const sum = leaf.value ?? 0;
88
+ const first = rows[0];
89
+ const x1 = at.x0 / width;
90
+ const x2 = at.x1 / width;
91
+ tiles.push({
92
+ ...first,
93
+ // A string `value` names the field the tile's sum reads back from.
94
+ ...(typeof value === 'string' ? { [value]: sum } : {}),
95
+ [TREEMAP_ROW.name]: name,
96
+ [TREEMAP_ROW.path]: path,
97
+ [TREEMAP_ROW.value]: sum,
98
+ [TREEMAP_ROW.share]: total > 0 ? sum / total : 0,
99
+ [TREEMAP_ROW.x1]: x1,
100
+ [TREEMAP_ROW.x2]: x2,
101
+ [TREEMAP_ROW.y1]: at.y0,
102
+ [TREEMAP_ROW.y2]: at.y1,
103
+ [TREEMAP_ROW.x]: (x1 + x2) / 2,
104
+ [TREEMAP_ROW.y]: (at.y0 + at.y1) / 2
105
+ });
106
+ }
107
+ }
108
+ return {
109
+ ...rest,
110
+ data: tiles,
111
+ x: TREEMAP_ROW.x,
112
+ x1: TREEMAP_ROW.x1,
113
+ x2: TREEMAP_ROW.x2,
114
+ y: TREEMAP_ROW.y,
115
+ y1: TREEMAP_ROW.y1,
116
+ y2: TREEMAP_ROW.y2
117
+ };
118
+ }
119
+ }
@@ -1,7 +1,7 @@
1
1
  import { ColorRamp } from '../../../utils/color';
2
2
  import { OpacityScale } from '../../../utils/OpacityScale';
3
3
  import { ContinuousDomain } from '../../../utils/ContinuousDomain';
4
- import { buildMarks, buildValueLabels } from './markers';
4
+ import { buildMarks } from './markers';
5
5
  /**
6
6
  * HeatmapPlot's own "scales" output, over the band axes the shared phase laid
7
7
  * out: the color domain over every cell's value (when `color` is set), and each
@@ -76,7 +76,7 @@ export function buildHeatmapDefaults() {
76
76
  extend: extendWithColorScales
77
77
  },
78
78
  styles: { values: { show: true } },
79
- markers: { marks: buildMarks, valueLabels: buildValueLabels },
79
+ markers: { marks: buildMarks },
80
80
  hover: {
81
81
  // A hovered cell reads as its formatted value.
82
82
  label: (state) => {
@@ -1,13 +1,5 @@
1
- import type { TextMarkerConfig } from '../../../types/markers/common';
2
1
  import type { HeatmapPlotState, HeatmapPlotCtx } from '../../../types/plots/correlation/heatmap/state';
3
- import type { ValueLabels } from '../../../layout/styles/ValueLabels';
4
2
  import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
5
- /**
6
- * HeatmapPlot's `styles.values` as one `'text'` marker over the same cells
7
- * the plot draws, each labelled with its own value and centred on it. Shown
8
- * by default.
9
- */
10
- export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: HeatmapPlotState<TData>, ctx: HeatmapPlotCtx): TextMarkerConfig[];
11
3
  /**
12
4
  * HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
13
5
  * bucket - svelteplot's opacity/numeric scale machinery requires
@@ -8,38 +8,6 @@ function readChannel(channel) {
8
8
  return resolveAccessor(channel);
9
9
  }
10
10
  const OPACITY_STEPS = 32;
11
- /**
12
- * HeatmapPlot's `styles.values` as one `'text'` marker over the same cells
13
- * the plot draws, each labelled with its own value and centred on it. Shown
14
- * by default.
15
- */
16
- export function buildValueLabels(labels, state, ctx) {
17
- const { processedData } = state;
18
- if (!processedData?.data.length)
19
- return [];
20
- const { font, format } = labels;
21
- const getColor = state.channels.color.accessor;
22
- return [
23
- {
24
- type: 'text',
25
- data: processedData.data,
26
- // Read like the cells read them, so the labels add no axis title the cells don't.
27
- x: readChannel(processedData.channels.x),
28
- y: readChannel(processedData.channels.y),
29
- text: (d) => format(Number(getColor(d)), ''),
30
- styles: {
31
- ...font,
32
- fill: font?.fill ?? ctx.cfg.continuous.labelColor,
33
- fontSize: font?.fontSize ?? 12,
34
- fontWeight: font?.fontWeight ?? 600,
35
- dx: font?.dx ?? 0,
36
- dy: font?.dy ?? 0,
37
- textAnchor: labels.textAnchor ?? 'middle',
38
- lineAnchor: font?.lineAnchor ?? 'middle'
39
- }
40
- }
41
- ];
42
- }
43
11
  /**
44
12
  * HeatmapPlot's "markers" phase: one `'cell'` marker per resolved-style
45
13
  * bucket - svelteplot's opacity/numeric scale machinery requires
@@ -49,6 +17,7 @@ export function buildValueLabels(labels, state, ctx) {
49
17
  export function buildMarks(state, ctx) {
50
18
  const { segments, channels, resolveColor, resolveOpacity } = state;
51
19
  const { range } = ctx;
20
+ const labels = state.valueLabels?.show ? state.valueLabels : undefined;
52
21
  const getColor = channels.color.accessor;
53
22
  // The cells are the rows the "transforms" phase left, placed by its channels.
54
23
  const processed = state.processedData;
@@ -131,16 +100,20 @@ export function buildMarks(state, ctx) {
131
100
  data: group.data,
132
101
  x: getX,
133
102
  y: getY,
134
- fill: (d) => group.styles.fill ?? resolveColor(d),
135
103
  styles: {
104
+ fill: (d) => group.styles.fill ?? resolveColor(d),
136
105
  fillOpacity: group.styles.fillOpacity ?? group.opacity ?? 1,
137
106
  stroke: group.styles.stroke,
138
107
  strokeWidth: group.styles.strokeWidth ?? 0,
139
108
  strokeOpacity: group.styles.strokeOpacity,
140
109
  strokeDasharray: group.styles.strokeDasharray,
141
- borderRadius: group.styles.borderRadius
110
+ borderRadius: group.styles.borderRadius,
111
+ inset: 0.5
142
112
  },
143
- inset: 0.5
113
+ // Each cell writes its own value, in the middle of it.
114
+ label: labels?.shapeLabel(ctx, (d) => labels.format(Number(getColor(d)), ''), {
115
+ fontWeight: 600
116
+ })
144
117
  }));
145
118
  return markers;
146
119
  }
@@ -8,7 +8,7 @@ import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
8
8
  * sits beside its point and flips to the other side where its text would
9
9
  * cross the plot's edge (`edgeAware`).
10
10
  */
11
- export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData>, ctx: ScatterPlotCtx): TextMarkerConfig[];
11
+ export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: ScatterPlotState<TData>): TextMarkerConfig[];
12
12
  /**
13
13
  * ScatterPlot's "markers" phase: one `'dot'` marker per visual segment of the
14
14
  * rows the "transforms" phase left, in their own order, so a reordering
@@ -7,7 +7,7 @@ import { ValueLabels } from '../../../layout/styles/ValueLabels';
7
7
  * sits beside its point and flips to the other side where its text would
8
8
  * cross the plot's edge (`edgeAware`).
9
9
  */
10
- export function buildValueLabels(labels, state, ctx) {
10
+ export function buildValueLabels(labels, state) {
11
11
  const { processedData } = state;
12
12
  if (!processedData?.data.length)
13
13
  return [];
@@ -24,7 +24,7 @@ export function buildValueLabels(labels, state, ctx) {
24
24
  text: (d) => format(Number(valueOf(d)), nameOf(d), d),
25
25
  styles: {
26
26
  ...font,
27
- fill: font?.fill ?? ctx.cfg.axis.color,
27
+ fill: font?.fill ?? 'currentColor',
28
28
  fontSize: font?.fontSize ?? 11,
29
29
  fontWeight: font?.fontWeight ?? 500,
30
30
  dx: font?.dx ?? 8,
@@ -1,4 +1,4 @@
1
- import { buildMarks, buildValueLabels } from './markers';
1
+ import { buildMarks } from './markers';
2
2
  import { SERIES_KEY } from '../../../phases/transforms/TransformsPhase';
3
3
  import { resolveAccessor } from '../../../utils/accessors';
4
4
  import { PlotAxes } from '../../../phases/data/PlotAxes';
@@ -63,7 +63,7 @@ export function buildHistogramDefaults() {
63
63
  }
64
64
  }
65
65
  },
66
- markers: { marks: buildMarks, valueLabels: buildValueLabels },
66
+ markers: { marks: buildMarks },
67
67
  hover: {
68
68
  // A hovered bin is labelled with its count, and reads as its row plus its count,
69
69
  // the edges it spans and its series.
@@ -1,14 +1,5 @@
1
1
  import type { HistogramPlotState, HistogramPlotCtx } from '../../../types/plots/distribution/histogram/state';
2
- import type { AxisBasedMarkersConfig, TextMarkerConfig } from '../../../types/markers/common';
3
- import type { ValueLabels } from '../../../layout/styles/ValueLabels';
4
- /**
5
- * HistogramPlot's `styles.values` as one `'text'` marker over its non-empty
6
- * bins (a `filter` on the count), each labelled with its count by `format`.
7
- * `anchor` places each label along its bar: past its top (`'outside'`), just
8
- * inside it (`'start'`), at its base (`'end'`), or centred (`'middle'`) -
9
- * upward, or rightward on a horizontal histogram.
10
- */
11
- export declare function buildValueLabels<TData extends Record<string, unknown>>(labels: ValueLabels, state: HistogramPlotState<TData>, ctx: HistogramPlotCtx): TextMarkerConfig[];
2
+ import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
12
3
  /**
13
4
  * HistogramPlot's "markers" phase: one `rect` per visual segment of each
14
5
  * series, drawing that segment's bins, dimmed with its series. The markers