@fundar/data-chart-telling 0.0.13 → 0.0.15

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 (141) hide show
  1. package/README.md +121 -17
  2. package/dist/charts/{Chart.svelte.d.ts → BaseChart.svelte.d.ts} +3 -3
  3. package/dist/charts/bar/Chart.svelte +5 -5
  4. package/dist/charts/bar/Chart.svelte.d.ts +1 -2
  5. package/dist/charts/heatmap/Chart.svelte +8 -7
  6. package/dist/charts/heatmap/Chart.svelte.d.ts +2 -3
  7. package/dist/charts/line/Chart.svelte +5 -5
  8. package/dist/charts/line/Chart.svelte.d.ts +1 -2
  9. package/dist/charts/pyramid/Chart.svelte +7 -6
  10. package/dist/charts/pyramid/Chart.svelte.d.ts +2 -3
  11. package/dist/configuration/config.svelte.js +1 -1
  12. package/dist/configuration/themes/index.d.ts +56 -40
  13. package/dist/index.d.ts +26 -12
  14. package/dist/index.js +12 -7
  15. package/dist/layout/facet/FacetLayout.svelte +1 -1
  16. package/dist/layout/facet/FacetLayout.svelte.d.ts +1 -1
  17. package/dist/layout/geo/GeoLayout.svelte +47 -0
  18. package/dist/layout/geo/GeoLayout.svelte.d.ts +22 -0
  19. package/dist/layout/geo/resolveProjection.d.ts +10 -0
  20. package/dist/layout/geo/resolveProjection.js +48 -0
  21. package/dist/layout/legend/DiscreteSection.svelte +1 -1
  22. package/dist/layout/plot/AxisLayout.svelte +95 -0
  23. package/dist/layout/plot/AxisLayout.svelte.d.ts +39 -0
  24. package/dist/layout/plot/BasePlotLayout.svelte +302 -0
  25. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +95 -0
  26. package/dist/layout/plot/GridLayout.svelte +30 -0
  27. package/dist/layout/plot/GridLayout.svelte.d.ts +27 -0
  28. package/dist/layout/plot/RuleLayout.svelte +111 -0
  29. package/dist/layout/plot/RuleLayout.svelte.d.ts +40 -0
  30. package/dist/layout/plot/margins.d.ts +34 -0
  31. package/dist/layout/plot/margins.js +26 -0
  32. package/dist/layout/timeline/TimelineLayout.svelte +1 -1
  33. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
  34. package/dist/layout/tooltip/Tooltip.svelte +15 -0
  35. package/dist/layout/tooltip/Tooltip.svelte.d.ts +7 -0
  36. package/dist/layout/tooltip/TooltipLayout.svelte +45 -0
  37. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +41 -0
  38. package/dist/layout/tooltip/controller.svelte.d.ts +4 -3
  39. package/dist/layout/tooltip/controller.svelte.js +5 -6
  40. package/dist/layout/tooltip/hover.svelte.d.ts +1 -1
  41. package/dist/layout/tooltip/hover.svelte.js +1 -1
  42. package/dist/layout/tooltip/utils.d.ts +2 -2
  43. package/dist/markers/ContourClipMarker.svelte +77 -0
  44. package/dist/markers/ContourClipMarker.svelte.d.ts +10 -0
  45. package/dist/{plots/markers → markers}/DeltaMarker.svelte +4 -4
  46. package/dist/{plots/markers → markers}/DeltaMarker.svelte.d.ts +3 -3
  47. package/dist/{plots/markers → markers}/DotMarker.svelte +3 -3
  48. package/dist/{plots/markers → markers}/DotMarker.svelte.d.ts +3 -3
  49. package/dist/markers/HoverMarker.svelte +136 -0
  50. package/dist/{plots/markers → markers}/HoverMarker.svelte.d.ts +4 -4
  51. package/dist/{plots/markers → markers}/PeakMarker.svelte +2 -2
  52. package/dist/{plots/markers → markers}/PeakMarker.svelte.d.ts +2 -2
  53. package/dist/{plots/markers → markers}/TextMarker.svelte +11 -4
  54. package/dist/{plots/markers → markers}/TextMarker.svelte.d.ts +3 -3
  55. package/dist/markers/inset/InsetMarker.svelte +170 -0
  56. package/dist/markers/inset/InsetMarker.svelte.d.ts +51 -0
  57. package/dist/markers/inset/insetLayout.d.ts +25 -0
  58. package/dist/markers/inset/insetLayout.js +39 -0
  59. package/dist/markers/inset/insetProjection.d.ts +8 -0
  60. package/dist/markers/inset/insetProjection.js +39 -0
  61. package/dist/plots/bar/BarSegments.svelte +66 -0
  62. package/dist/plots/bar/BarSegments.svelte.d.ts +36 -0
  63. package/dist/plots/bar/Plot.svelte +132 -76
  64. package/dist/plots/bar/Plot.svelte.d.ts +7 -3
  65. package/dist/plots/bar/ValueLabels.svelte +89 -0
  66. package/dist/plots/bar/ValueLabels.svelte.d.ts +39 -0
  67. package/dist/plots/bar/hoverPoints.d.ts +15 -0
  68. package/dist/plots/bar/hoverPoints.js +35 -0
  69. package/dist/plots/bar/layout.svelte.d.ts +37 -0
  70. package/dist/plots/bar/layout.svelte.js +132 -0
  71. package/dist/plots/geo/Plot.svelte +548 -0
  72. package/dist/plots/geo/Plot.svelte.d.ts +29 -0
  73. package/dist/plots/geo/TileLayer.svelte +77 -0
  74. package/dist/plots/geo/TileLayer.svelte.d.ts +11 -0
  75. package/dist/plots/geo/projections/argentina.d.ts +8 -0
  76. package/dist/plots/geo/projections/argentina.js +35 -0
  77. package/dist/plots/geo/projections/fit.d.ts +8 -0
  78. package/dist/plots/geo/projections/fit.js +14 -0
  79. package/dist/plots/geo/rotate.svelte.d.ts +26 -0
  80. package/dist/plots/geo/rotate.svelte.js +79 -0
  81. package/dist/plots/geo/zoom.svelte.d.ts +32 -0
  82. package/dist/plots/geo/zoom.svelte.js +50 -0
  83. package/dist/plots/heatmap/Plot.svelte +129 -47
  84. package/dist/plots/heatmap/Plot.svelte.d.ts +7 -3
  85. package/dist/plots/line/Plot.svelte +44 -16
  86. package/dist/plots/line/Plot.svelte.d.ts +7 -3
  87. package/dist/plots/pyramid/Plot.svelte +70 -25
  88. package/dist/plots/pyramid/Plot.svelte.d.ts +7 -3
  89. package/dist/plots/utils/delta.d.ts +1 -1
  90. package/dist/plots/utils/geoAccessors.d.ts +64 -0
  91. package/dist/plots/utils/geoAccessors.js +188 -0
  92. package/dist/plots/utils/geoSegments.d.ts +14 -0
  93. package/dist/plots/utils/geoSegments.js +17 -0
  94. package/dist/plots/utils/segments.d.ts +14 -5
  95. package/dist/plots/utils/segments.js +25 -13
  96. package/dist/plots/utils/tiles.d.ts +89 -0
  97. package/dist/plots/utils/tiles.js +159 -0
  98. package/dist/plots/utils/topojson.d.ts +9 -0
  99. package/dist/plots/utils/topojson.js +30 -0
  100. package/dist/types/charts/common.d.ts +2 -1
  101. package/dist/types/charts/legend.d.ts +1 -1
  102. package/dist/types/charts/props.d.ts +9 -6
  103. package/dist/types/configuration/styling.d.ts +11 -5
  104. package/dist/types/layout/tooltip.d.ts +14 -21
  105. package/dist/types/{plots/markers.d.ts → markers/common.d.ts} +2 -18
  106. package/dist/types/markers/geo.d.ts +237 -0
  107. package/dist/types/markers/props.d.ts +11 -0
  108. package/dist/types/plots/axis.d.ts +55 -0
  109. package/dist/types/plots/axis.js +1 -0
  110. package/dist/types/plots/constants.d.ts +30 -0
  111. package/dist/types/plots/constants.js +1 -0
  112. package/dist/types/plots/data/common.d.ts +40 -0
  113. package/dist/types/plots/data/common.js +1 -0
  114. package/dist/types/plots/data/geo.d.ts +74 -0
  115. package/dist/types/plots/data/geo.js +1 -0
  116. package/dist/types/plots/delta.d.ts +2 -2
  117. package/dist/types/plots/props.d.ts +105 -16
  118. package/dist/types/plots/scales/geo.d.ts +44 -0
  119. package/dist/types/plots/scales/geo.js +1 -0
  120. package/dist/types/plots/segments/common.d.ts +25 -0
  121. package/dist/types/plots/segments/common.js +1 -0
  122. package/dist/types/plots/segments/config.d.ts +51 -0
  123. package/dist/types/plots/segments/config.js +1 -0
  124. package/dist/types/plots/styles/common.d.ts +37 -0
  125. package/dist/types/plots/styles/common.js +1 -0
  126. package/dist/types/plots/styles/geo.d.ts +42 -0
  127. package/dist/types/plots/styles/geo.js +1 -0
  128. package/dist/types/plots/styling.d.ts +45 -0
  129. package/dist/types/plots/styling.js +1 -0
  130. package/dist/utils/grouping.d.ts +2 -1
  131. package/dist/utils/interpolate.d.ts +2 -1
  132. package/package.json +27 -13
  133. package/dist/layout/plot/PlotLayout.svelte +0 -237
  134. package/dist/layout/plot/PlotLayout.svelte.d.ts +0 -70
  135. package/dist/plots/markers/HoverMarker.svelte +0 -98
  136. package/dist/types/plots/common.d.ts +0 -100
  137. package/dist/types/plots/styles.d.ts +0 -187
  138. /package/dist/charts/{Chart.svelte → BaseChart.svelte} +0 -0
  139. /package/dist/types/{plots → markers}/common.js +0 -0
  140. /package/dist/types/{plots/markers.js → markers/geo.js} +0 -0
  141. /package/dist/types/{plots/styles.js → markers/props.js} +0 -0
@@ -5,22 +5,43 @@
5
5
  import { SvelteMap } from 'svelte/reactivity';
6
6
  import { paletteColor } from '../../utils/color';
7
7
  import { buildSeries } from '../../utils/grouping';
8
- import { BarY, Text } from 'svelteplot';
8
+ import { BarX, BarY } from 'svelteplot';
9
9
  import { resolveAccessor } from '../utils/accessors';
10
10
  import { getConfiguration } from '../../configuration/config.svelte';
11
11
  import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX } from '../utils/segments';
12
- import PlotLayout from '../../layout/plot/PlotLayout.svelte';
13
- import HoverMarker from '../markers/HoverMarker.svelte';
14
- import type { Marker } from '../../types/plots/markers';
15
- import type { PlotProps } from '../../types/plots/props';
16
- import type { Series, AxisValue } from '../../types/plots/common';
17
- import type { BarSegmentStyle, ValueAnchor } from '../../types/plots/styles';
12
+ import { createBarLayout } from './layout.svelte';
13
+ import { buildHoverPoints } from './hoverPoints';
14
+ import BarSegments from './BarSegments.svelte';
15
+ import ValueLabels from './ValueLabels.svelte';
16
+ import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
17
+ import AxisLayout from '../../layout/plot/AxisLayout.svelte';
18
+ import GridLayout from '../../layout/plot/GridLayout.svelte';
19
+ import RuleLayout from '../../layout/plot/RuleLayout.svelte';
20
+ import HoverMarker from '../../markers/HoverMarker.svelte';
21
+ import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
22
+ import type { Marker } from '../../types/markers/common';
23
+ import type { BasePlotProps } from '../../types/plots/props';
24
+ import type { AxisScale } from '../../types/plots/axis';
25
+ import type { Series, SeriesProps, DataProps } from '../../types/plots/data/common';
26
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
27
+ import type { BasePlotStyles } from '../../types/plots/styles/common';
28
+ import type { ValueAnchor } from '../../types/plots/constants';
29
+ import type { BarSegmentStyle } from '../../types/plots/segments/common';
18
30
 
19
31
  /**
20
- * A vertical bar chart. Accepts either a pre-built `series` array or flat
21
- * `data/x/y/z` props — mirrors the line/heatmap plot contract so the
22
- * chart-level container/timeline/facet/legend wrap it unchanged.
32
+ * A bar chart, vertical by default. Accepts either a pre-built `series`
33
+ * array or flat `data/x/y/z` props — mirrors the line/heatmap plot contract
34
+ * so the chart-level container/timeline/facet/legend wrap it unchanged.
23
35
  */
36
+ type Props = BasePlotProps<
37
+ SeriesProps<TData> | DataProps<TData>,
38
+ TData,
39
+ BasePlotStyles,
40
+ BarSegmentStyle,
41
+ Marker,
42
+ AxisBasedScalesConfig<TData>
43
+ >;
44
+
24
45
  let {
25
46
  width,
26
47
  height,
@@ -35,14 +56,16 @@
35
56
  scales = {},
36
57
  margins,
37
58
  tooltip = undefined,
38
- }: PlotProps<TData, BarSegmentStyle> = $props();
59
+ }: Props = $props();
39
60
 
40
61
  $effect(() => { validateSegments(segments); });
41
62
 
42
63
  const cfg = $derived(getConfiguration());
43
64
  const resolvedSeries = $derived(series ?? buildSeries(data!, x!, y!, z));
44
65
  const groupedSeries = $derived(
45
- buildGroupedSeries<Series<TData>, BarSegmentStyle>(resolvedSeries, segments),
66
+ // connect: false — bars are discrete; there's no visual gap between
67
+ // adjacent categories to bridge (see buildGroupedSeries's own doc).
68
+ buildGroupedSeries<Series<TData>, BarSegmentStyle>(resolvedSeries, segments, false),
46
69
  );
47
70
  const showVals = $derived(styles.values?.show ?? false);
48
71
  const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
@@ -59,85 +82,117 @@
59
82
  return map;
60
83
  });
61
84
 
85
+ // `xAcc`/`yAcc` are always data-semantic (category/value) — independent of
86
+ // which *visual* axis ends up drawing them, decided by `categoricalAxis`.
62
87
  const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
63
88
  const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
64
89
  const flat = $derived(resolvedSeries.flatMap((s) => s.data));
65
- const hoverPoints = $derived(
66
- resolvedSeries.flatMap((s, idx) => {
67
- const sx = resolveAccessor(s.x);
68
- const sy = resolveAccessor(s.y);
69
- return s.data.map((d) => ({
70
- x: sx(d) as AxisValue,
71
- y: Number(sy(d)),
72
- row: d,
73
- label: String(sy(d)),
74
- series: s.name,
75
- color: paletteColor(idx, cfg.palette),
76
- }));
77
- }),
78
- );
90
+
91
+ /**
92
+ * Which visual axis draws the category, and therefore whether this renders
93
+ * as vertical bars (`BarY`, category on X) or horizontal bars (`BarX`,
94
+ * category on Y). Mirrors how pyramid/heatmap default an `AxisScale.type`
95
+ * themselves: `scales.x`/`scales.y` always describe *that visual axis*
96
+ * regardless of which data field lands there, so marking `scales.y.type:
97
+ * 'categorical'` simply says "the category is drawn on Y this time" — x
98
+ * defaults to categorical when neither is marked.
99
+ */
100
+ const categoricalAxis = $derived.by((): 'x' | 'y' => {
101
+ const xCategorical = scales.x?.type === 'categorical';
102
+ const yCategorical = scales.y?.type === 'categorical';
103
+ if (xCategorical && yCategorical) {
104
+ console.warn(
105
+ "BarPlot: scales.x.type and scales.y.type can't both be 'categorical' — " +
106
+ 'mark only one axis as the category axis. Defaulting to x.',
107
+ );
108
+ return 'x';
109
+ }
110
+ return yCategorical ? 'y' : 'x';
111
+ });
112
+ const isHorizontal = $derived(categoricalAxis === 'y');
113
+ const BarMark = $derived(isHorizontal ? BarX : BarY);
114
+
115
+ /**
116
+ * `'categorical'` is our own sentinel (BarPlot-only), not a real svelteplot
117
+ * scale type — svelteplot's x/y scales don't recognise it (it's valid only
118
+ * for `color`/`symbol`). Strip it before the scale reaches svelteplot so it
119
+ * auto-infers the real type (typically `point`/`band`) the same way it
120
+ * already does when `type` is left unset entirely.
121
+ */
122
+ function stripCategoricalType(scale?: AxisScale<TData>): AxisScale<TData> | undefined {
123
+ if (scale?.type !== 'categorical') return scale;
124
+ const rest = { ...scale };
125
+ delete rest.type;
126
+ return rest;
127
+ }
128
+ // The single, fully-resolved scales object BasePlotLayout reads for
129
+ // everything (ticks, grid, sort, and the real svelteplot scale) — avoids
130
+ // keeping a second, separately-patched copy of `scales.x`/`scales.y` in
131
+ // sync with the one actually forwarded.
132
+ const resolvedScales = $derived({
133
+ ...scales,
134
+ x: stripCategoricalType(scales.x),
135
+ y: stripCategoricalType(scales.y),
136
+ });
137
+
138
+ // The accessor feeding each *visual* channel — swapped from the
139
+ // data-semantic xAcc/yAcc when the category is drawn on Y.
140
+ const plotGetX = $derived(isHorizontal ? (d: TData) => Number(yAcc(d)) : xAcc);
141
+ const plotGetY = $derived(isHorizontal ? xAcc : (d: TData) => Number(yAcc(d)));
142
+
143
+ /**
144
+ * Series-layout geometry (grouped/stacked offsets, band-width estimate,
145
+ * value baseline) — the single source of truth shared by bar-segment
146
+ * rendering, value labels, and hover markers below. See `layout.svelte.ts`.
147
+ */
148
+ const barLayout = createBarLayout<TData>({
149
+ scales: () => scales,
150
+ isHorizontal: () => isHorizontal,
151
+ width: () => width,
152
+ height: () => height,
153
+ margins: () => margins,
154
+ flat: () => flat,
155
+ xAcc: () => xAcc,
156
+ groupedSeries: () => groupedSeries,
157
+ });
158
+
159
+ // `x`/`y` here are visual (matched against BasePlotLayout's own getX/getY,
160
+ // which drive tick/domain computation and the Pointer's hit-testing) —
161
+ // `row` (see BasePlotLayout/HoverPoint) is what lets the template recover the
162
+ // semantic category regardless of which axis it landed on.
163
+ const hoverPoints = $derived(buildHoverPoints(resolvedSeries, isHorizontal, barLayout, cfg.palette));
164
+
165
+ const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
166
+ const hoverStrategy = $derived(resolveHoverStrategy(tooltip?.strategy, markers, categoricalAxis));
167
+ const hoverSync = $derived(resolveHoverSync(tooltip?.sync, markers));
79
168
  </script>
80
169
 
81
- <PlotLayout
170
+ <BasePlotLayout
82
171
  {width}
83
172
  {height}
84
- x={scales.x}
85
- y={scales.y}
86
173
  data={flat}
87
- getX={xAcc}
88
- getY={(d) => Number(yAcc(d))}
174
+ getX={plotGetX}
175
+ getY={plotGetY}
89
176
  {hoverPoints}
90
177
  label={(r) => String(yAcc(r))}
91
178
  {tooltip}
92
- {markers}
93
- seriesData={resolvedSeries}
94
- {scales}
179
+ {hoverActive}
180
+ {hoverStrategy}
181
+ {hoverSync}
182
+ scales={resolvedScales}
95
183
  {margins}
96
- defaultStrategy="x"
184
+ xTickRotate={resolvedScales?.x?.tickRotate}
97
185
  >
98
- {#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY })}
186
+ {#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY, numericMargins })}
187
+ <AxisLayout data={flat} getX={plotGetX} getY={plotGetY} {width} {height} {numericMargins} scales={resolvedScales} />
188
+ <GridLayout scales={resolvedScales} />
189
+ <RuleLayout {markers} seriesData={resolvedSeries} {matchedPoints} {ruleX} {ruleY} {impliesRuleX} {impliesRuleY}>
99
190
  {#each groupedSeries as group, seriesIndex (group.series.name)}
100
191
  {@const defaultColor = paletteColor(seriesIndex, cfg.palette)}
101
- {@const xFn = resolveAccessor(group.series.x)}
102
- {@const yFn = resolveAccessor(group.series.y)}
103
- {#each group.visualSegments as seg, i (`${group.series.name}-${i}`)}
104
- <BarY
105
- data={seg.data}
106
- x={xFn}
107
- y={yFn}
108
- fill={seg.style.fill ?? defaultColor}
109
- fillOpacity={seg.style.fillOpacity ?? 1}
110
- />
111
- {/each}
192
+ <BarSegments {group} {seriesIndex} {barLayout} {isHorizontal} {BarMark} {defaultColor} />
112
193
 
113
194
  {#if showVals}
114
- {@const yTextFn = valAnchor === 'middle'
115
- ? (d: TData) => Number(yFn(d)) / 2
116
- : valAnchor === 'end'
117
- ? () => 0
118
- : (d: TData) => Number(yFn(d))}
119
- {@const defaultDy = valAnchor === 'outside' ? -6 : valAnchor === 'start' ? 4 : valAnchor === 'end' ? -4 : 0}
120
- {@const defaultLA = valAnchor === 'outside' || valAnchor === 'end'
121
- ? ('bottom' as const)
122
- : valAnchor === 'start'
123
- ? ('top' as const)
124
- : ('middle' as const)}
125
- <Text
126
- data={group.series.data}
127
- x={xFn}
128
- y={yTextFn}
129
- text={(d: TData) => fmtVal(Number(yFn(d)), group.series.name)}
130
- dx={styles.values?.dx ?? 0}
131
- dy={styles.values?.dy ?? defaultDy}
132
- textAnchor={styles.values?.textAnchor ?? 'middle'}
133
- lineAnchor={styles.values?.lineAnchor ?? defaultLA}
134
- lineHeight={styles.values?.lineHeight}
135
- rotate={styles.values?.rotate}
136
- class={styles.values?.class}
137
- textClass={styles.values?.textClass}
138
- fill={cfg.axis.color}
139
- fontSize={12}
140
- />
195
+ <ValueLabels {group} {seriesIndex} {barLayout} {isHorizontal} {valAnchor} {fmtVal} values={styles.values} axisColor={cfg.axis.color} />
141
196
  {/if}
142
197
 
143
198
  {#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-hover-${i}`)}
@@ -146,7 +201,7 @@
146
201
  {#if marker.scope === 'segment'}
147
202
  {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
148
203
  {#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
149
- {@const segPoints = seriesPoints.filter((p) => matchesSegmentX(p.x, seg))}
204
+ {@const segPoints = seriesPoints.filter((p) => matchesSegmentX(xAcc(p.row), seg))}
150
205
  {#if segPoints.length > 0}
151
206
  <HoverMarker
152
207
  data={segPoints}
@@ -176,5 +231,6 @@
176
231
  {/if}
177
232
  {/each}
178
233
  {/each}
234
+ </RuleLayout>
179
235
  {/snippet}
180
- </PlotLayout>
236
+ </BasePlotLayout>
@@ -1,7 +1,11 @@
1
- import type { PlotProps } from '../../types/plots/props';
2
- import type { BarSegmentStyle } from '../../types/plots/styles';
1
+ import type { Marker } from '../../types/markers/common';
2
+ import type { BasePlotProps } from '../../types/plots/props';
3
+ import type { SeriesProps, DataProps } from '../../types/plots/data/common';
4
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
5
+ import type { BasePlotStyles } from '../../types/plots/styles/common';
6
+ import type { BarSegmentStyle } from '../../types/plots/segments/common';
3
7
  declare function $$render<TData extends Record<string, unknown>>(): {
4
- props: PlotProps<TData, BarSegmentStyle>;
8
+ props: BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, BasePlotStyles, BarSegmentStyle, Marker, AxisBasedScalesConfig<TData>>;
5
9
  exports: {};
6
10
  bindings: "";
7
11
  slots: {};
@@ -0,0 +1,89 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ import { Text } from 'svelteplot';
3
+ import { resolveAccessor } from '../utils/accessors';
4
+ import type { Series } from '../../types/plots/data/common';
5
+ import type { ValuesStyle } from '../../types/plots/styles/common';
6
+ import type { ValueAnchor } from '../../types/plots/constants';
7
+ import type { BarSegmentStyle } from '../../types/plots/segments/common';
8
+ import type { VisualGroup } from '../../types/plots/segments/config';
9
+ import type { BarLayout } from './layout.svelte';
10
+
11
+ /**
12
+ * Renders one series-group's value label — anchor-driven default
13
+ * position/offset, overridable per-field via `styles.values`. Positions
14
+ * account for the same grouped/stacked geometry as the bars themselves
15
+ * (via {@link BarLayout}), so a label lands on its own bar/segment instead
16
+ * of a shared default position.
17
+ */
18
+ let {
19
+ group,
20
+ seriesIndex,
21
+ barLayout,
22
+ isHorizontal,
23
+ valAnchor,
24
+ fmtVal,
25
+ values,
26
+ axisColor,
27
+ }: {
28
+ group: VisualGroup<Series<TData>, BarSegmentStyle>;
29
+ seriesIndex: number;
30
+ barLayout: BarLayout<TData>;
31
+ isHorizontal: boolean;
32
+ valAnchor: ValueAnchor;
33
+ fmtVal: (value: number, seriesName: string) => string;
34
+ values: ValuesStyle | undefined;
35
+ axisColor: string;
36
+ } = $props();
37
+
38
+ const xFn = $derived(resolveAccessor(group.series.x));
39
+ const yFn = $derived(resolveAccessor(group.series.y));
40
+
41
+ const valueTextFn = $derived(
42
+ barLayout.layout === 'stacked'
43
+ ? (d: TData) => barLayout.stackedBaseline(xFn(d), seriesIndex) + Number(yFn(d)) / 2
44
+ : valAnchor === 'middle' && !isHorizontal
45
+ ? (d: TData) => Number(yFn(d)) / 2
46
+ : valAnchor === 'end'
47
+ ? () => 0
48
+ : (d: TData) => Number(yFn(d)),
49
+ );
50
+ const defaultOffset = $derived(
51
+ valAnchor === 'outside' ? (isHorizontal ? 6 : -6)
52
+ : valAnchor === 'start' ? (isHorizontal ? -4 : 4)
53
+ : valAnchor === 'end' ? (isHorizontal ? 4 : -4)
54
+ : 0,
55
+ );
56
+ const defaultLA = $derived(
57
+ valAnchor === 'outside' || valAnchor === 'end' ? ('bottom' as const)
58
+ : valAnchor === 'start' ? ('top' as const)
59
+ : ('middle' as const),
60
+ );
61
+ const groupOffset = $derived(barLayout.groupOffset(seriesIndex));
62
+
63
+ // `textAnchor: 'outside'` is a per-point escape hatch resolved by
64
+ // HoverMarker (which knows each point's signed x) — value labels already
65
+ // have their own `anchor: 'outside'` concept (computed above), so if it
66
+ // leaks through here just fall back to the anchor-derived default.
67
+ const resolvedTextAnchor = $derived(values?.textAnchor === 'outside' ? undefined : values?.textAnchor);
68
+ </script>
69
+
70
+ <Text
71
+ data={group.series.data}
72
+ {...isHorizontal ? { y: xFn, x: valueTextFn } : { x: xFn, y: valueTextFn }}
73
+ text={(d: TData) => fmtVal(Number(yFn(d)), group.series.name)}
74
+ dx={values?.dx ?? (isHorizontal ? defaultOffset : groupOffset.dx)}
75
+ dy={values?.dy ?? (isHorizontal ? groupOffset.dy : defaultOffset)}
76
+ textAnchor={resolvedTextAnchor ?? (isHorizontal ? (valAnchor === 'outside' || valAnchor === 'end' ? 'start' : valAnchor === 'start' ? 'end' : 'middle') : 'middle')}
77
+ lineAnchor={values?.lineAnchor ?? (isHorizontal ? 'middle' : defaultLA)}
78
+ lineHeight={values?.lineHeight}
79
+ rotate={values?.rotate}
80
+ class={values?.class}
81
+ textClass={values?.textClass}
82
+ fill={values?.fill ?? axisColor}
83
+ fontSize={values?.fontSize ?? 12}
84
+ fontWeight={values?.fontWeight}
85
+ fontStyle={values?.fontStyle}
86
+ stroke={values?.stroke}
87
+ strokeWidth={values?.strokeWidth}
88
+ paintOrder={values?.paintOrder}
89
+ />
@@ -0,0 +1,39 @@
1
+ import type { Series } from '../../types/plots/data/common';
2
+ import type { ValuesStyle } from '../../types/plots/styles/common';
3
+ import type { ValueAnchor } from '../../types/plots/constants';
4
+ import type { BarSegmentStyle } from '../../types/plots/segments/common';
5
+ import type { VisualGroup } from '../../types/plots/segments/config';
6
+ import type { BarLayout } from './layout.svelte';
7
+ declare function $$render<TData extends Record<string, unknown>>(): {
8
+ props: {
9
+ group: VisualGroup<Series<TData>, BarSegmentStyle>;
10
+ seriesIndex: number;
11
+ barLayout: BarLayout<TData>;
12
+ isHorizontal: boolean;
13
+ valAnchor: ValueAnchor;
14
+ fmtVal: (value: number, seriesName: string) => string;
15
+ values: ValuesStyle | undefined;
16
+ axisColor: string;
17
+ };
18
+ exports: {};
19
+ bindings: "";
20
+ slots: {};
21
+ events: {};
22
+ };
23
+ declare class __sveltets_Render<TData extends Record<string, unknown>> {
24
+ props(): ReturnType<typeof $$render<TData>>['props'];
25
+ events(): ReturnType<typeof $$render<TData>>['events'];
26
+ slots(): ReturnType<typeof $$render<TData>>['slots'];
27
+ bindings(): "";
28
+ exports(): {};
29
+ }
30
+ interface $$IsomorphicComponent {
31
+ new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
32
+ $$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
33
+ } & ReturnType<__sveltets_Render<TData>['exports']>;
34
+ <TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
35
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
36
+ }
37
+ declare const ValueLabels: $$IsomorphicComponent;
38
+ type ValueLabels<TData extends Record<string, unknown>> = InstanceType<typeof ValueLabels<TData>>;
39
+ export default ValueLabels;
@@ -0,0 +1,15 @@
1
+ import type { Series } from '../../types/plots/data/common';
2
+ import type { HoverPoint } from '../../types/layout/tooltip';
3
+ import type { BarLayout } from './layout.svelte';
4
+ /**
5
+ * Builds hover-marker candidate points for every row across every series.
6
+ *
7
+ * `x`/`y` (the values matching is keyed off) stay the raw category/value so
8
+ * every series at the same category still matches together under the 'x'
9
+ * strategy; `dx`/`dy` only nudge where the matched point is *drawn* — see
10
+ * {@link import('../../types/layout/tooltip').HoverDisplayPoint}. For
11
+ * 'stacked' layout, `value` itself is adjusted to the series' cumulative
12
+ * height instead of `dx`/`dy`, since that offset is representable as a
13
+ * regular data value (unlike 'grouped''s pixel-only slice offset).
14
+ */
15
+ export declare function buildHoverPoints<TData extends Record<string, unknown>>(resolvedSeries: Series<TData>[], isHorizontal: boolean, geometry: Pick<BarLayout<TData>, 'layout' | 'stackedBaseline' | 'groupOffset'>, palette: string[]): HoverPoint<TData>[];
@@ -0,0 +1,35 @@
1
+ import { resolveAccessor } from '../utils/accessors';
2
+ import { paletteColor } from '../../utils/color';
3
+ /**
4
+ * Builds hover-marker candidate points for every row across every series.
5
+ *
6
+ * `x`/`y` (the values matching is keyed off) stay the raw category/value so
7
+ * every series at the same category still matches together under the 'x'
8
+ * strategy; `dx`/`dy` only nudge where the matched point is *drawn* — see
9
+ * {@link import('../../types/layout/tooltip').HoverDisplayPoint}. For
10
+ * 'stacked' layout, `value` itself is adjusted to the series' cumulative
11
+ * height instead of `dx`/`dy`, since that offset is representable as a
12
+ * regular data value (unlike 'grouped''s pixel-only slice offset).
13
+ */
14
+ export function buildHoverPoints(resolvedSeries, isHorizontal, geometry, palette) {
15
+ return resolvedSeries.flatMap((s, idx) => {
16
+ const sx = resolveAccessor(s.x);
17
+ const sy = resolveAccessor(s.y);
18
+ return s.data.map((d) => {
19
+ const category = sx(d);
20
+ const rawValue = Number(sy(d));
21
+ const value = geometry.layout === 'stacked' ? geometry.stackedBaseline(category, idx) + rawValue : rawValue;
22
+ const { dx, dy } = geometry.groupOffset(idx);
23
+ return {
24
+ x: isHorizontal ? value : category,
25
+ y: isHorizontal ? category : value,
26
+ dx,
27
+ dy,
28
+ row: d,
29
+ label: String(sy(d)),
30
+ series: s.name,
31
+ color: paletteColor(idx, palette),
32
+ };
33
+ });
34
+ });
35
+ }
@@ -0,0 +1,37 @@
1
+ import type { AxisValue } from '../../types/plots/axis';
2
+ import type { Series } from '../../types/plots/data/common';
3
+ import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
+ import type { BarSegmentStyle } from '../../types/plots/segments/common';
5
+ import type { VisualGroup } from '../../types/plots/segments/config';
6
+ import type { MarginConfig } from '../../types/plots/props';
7
+ export type BarSeriesLayout = 'overlap' | 'grouped' | 'stacked';
8
+ /**
9
+ * Reactive geometry for BarPlot's series-layout modes — the single source of
10
+ * truth for "where does series N's bar actually sit," shared by bar-segment
11
+ * rendering, value labels, and hover markers. Centralized here after a bug
12
+ * where hover markers reproduced (and initially missed) the same grouped/
13
+ * stacked math value labels already accounted for.
14
+ */
15
+ export declare function createBarLayout<TData extends Record<string, unknown>>(args: {
16
+ scales: () => AxisBasedScalesConfig<TData>;
17
+ isHorizontal: () => boolean;
18
+ width: () => number;
19
+ height: () => number;
20
+ margins: () => MarginConfig | undefined;
21
+ flat: () => TData[];
22
+ xAcc: () => (d: TData) => AxisValue;
23
+ groupedSeries: () => VisualGroup<Series<TData>, BarSegmentStyle>[];
24
+ }): {
25
+ readonly layout: BarSeriesLayout;
26
+ readonly valueBaseline: number;
27
+ readonly estimatedBandwidth: number;
28
+ readonly seriesCount: number;
29
+ readonly seriesStep: number;
30
+ readonly seriesGap: number;
31
+ stackedBaseline: (category: AxisValue, seriesIndex: number) => number;
32
+ groupOffset: (seriesIndex: number) => {
33
+ dx: number;
34
+ dy: number;
35
+ };
36
+ };
37
+ export type BarLayout<TData extends Record<string, unknown>> = ReturnType<typeof createBarLayout<TData>>;
@@ -0,0 +1,132 @@
1
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
2
+ import { getConfiguration } from '../../configuration/config.svelte';
3
+ import { resolveAccessor } from '../utils/accessors';
4
+ import { numericMargin } from '../../layout/plot/margins';
5
+ /**
6
+ * Reactive geometry for BarPlot's series-layout modes — the single source of
7
+ * truth for "where does series N's bar actually sit," shared by bar-segment
8
+ * rendering, value labels, and hover markers. Centralized here after a bug
9
+ * where hover markers reproduced (and initially missed) the same grouped/
10
+ * stacked math value labels already accounted for.
11
+ */
12
+ export function createBarLayout(args) {
13
+ const cfg = $derived(getConfiguration());
14
+ /**
15
+ * How series sharing the same category are arranged — see
16
+ * {@link import('../../types/plots/axis').AxisScale.seriesLayout}. Read
17
+ * off `scales.z` since it's the grouping axis, not the category axis itself.
18
+ */
19
+ const layout = $derived(args.scales().z?.seriesLayout ?? 'overlap');
20
+ const categoricalScale = $derived(args.isHorizontal() ? args.scales().y : args.scales().x);
21
+ const valueScale = $derived(args.isHorizontal() ? args.scales().x : args.scales().y);
22
+ /**
23
+ * Bars default to a 0 baseline. When the value axis's domain is set and
24
+ * excludes 0 (e.g. `[75, 85]`, to zoom in on a tight range), a literal 0
25
+ * baseline sits way outside the visible domain — the scale still maps it
26
+ * linearly, so the bar's "start" lands far past the plot's own edge and
27
+ * the bar overflows into the margin instead of appearing to end at the
28
+ * frame. Clamping the baseline to whichever domain bound is closest to 0
29
+ * makes bars cut off exactly at the visible edge instead, which is the
30
+ * point of setting a truncated domain in the first place. Left as 0 (no
31
+ * clamp) when the domain isn't set or already straddles 0 naturally.
32
+ */
33
+ const valueBaseline = $derived.by(() => {
34
+ const domain = valueScale?.domain;
35
+ if (!domain || domain.length < 2)
36
+ return 0;
37
+ const lo = Number(domain[0]);
38
+ const hi = Number(domain[domain.length - 1]);
39
+ if (Number.isNaN(lo) || Number.isNaN(hi))
40
+ return 0;
41
+ const min = Math.min(lo, hi);
42
+ const max = Math.max(lo, hi);
43
+ if (min > 0)
44
+ return min;
45
+ if (max < 0)
46
+ return max;
47
+ return 0;
48
+ });
49
+ /**
50
+ * Approximate per-category band size in pixels (width for vertical bars,
51
+ * height for horizontal ones), used only to size each series' slice in
52
+ * 'grouped' layout via inset props. Derived from this plot's own
53
+ * `width`/`height`/`margins` rather than svelteplot's actual measured
54
+ * scale (not part of its public API) — close enough for a grouped look,
55
+ * though it can be slightly off when an 'auto' margin ends up wider than
56
+ * the estimate (e.g. unusually long tick labels). Follows the same
57
+ * band-scale formula (and 0.15 default padding) svelteplot itself uses,
58
+ * so the category axis's own `paddingInner`/`paddingOuter` — the space
59
+ * *between* categories — is reflected here too, not just svelteplot's
60
+ * real render.
61
+ */
62
+ const estimatedBandwidth = $derived.by(() => {
63
+ const categoryCount = new SvelteSet(args.flat().map((d) => args.xAcc()(d))).size;
64
+ if (categoryCount === 0)
65
+ return 0;
66
+ const baseMargins = { ...cfg.margins, ...args.margins() };
67
+ const innerSize = args.isHorizontal()
68
+ ? Math.max(0, args.height() - numericMargin('top', baseMargins.top) - numericMargin('bottom', baseMargins.bottom))
69
+ : Math.max(0, args.width() - numericMargin('left', baseMargins.left) - numericMargin('right', baseMargins.right));
70
+ const paddingInner = categoricalScale?.paddingInner ?? categoricalScale?.padding ?? 0.15;
71
+ const paddingOuter = categoricalScale?.paddingOuter ?? categoricalScale?.padding ?? 0.15;
72
+ const step = innerSize / (categoryCount - paddingInner + 2 * paddingOuter);
73
+ return step * (1 - paddingInner);
74
+ });
75
+ const seriesCount = $derived(args.groupedSeries().length);
76
+ // Each series gets an equal slice of the band; `seriesPadding` (0–1, space
77
+ // *within* a group, between its series) reserves a fraction of that slice
78
+ // as a gap split evenly on either side of the bar.
79
+ const seriesStep = $derived(seriesCount > 0 ? estimatedBandwidth / seriesCount : 0);
80
+ const seriesGap = $derived(Math.max(0, Math.min(1, args.scales().z?.seriesPadding ?? 0)) * seriesStep);
81
+ /**
82
+ * Per-category cumulative sums for 'stacked' layout — `stackedPrefixSums.get(category)[i]`
83
+ * is the sum of series `0..i-1`'s value at that category, i.e. series `i`'s
84
+ * own segment spans `[prefix, prefix + value]`. Computed by hand (rather
85
+ * than svelteplot's `stackY`/`stackX` transform, which needs every series'
86
+ * rows in one call to line up cumulative offsets) so the same numbers can
87
+ * position the bar segment, its middle-anchored label, and its hover marker.
88
+ */
89
+ const stackedPrefixSums = $derived.by(() => {
90
+ const sums = new SvelteMap();
91
+ if (layout !== 'stacked')
92
+ return sums;
93
+ for (const category of new SvelteSet(args.flat().map((d) => args.xAcc()(d)))) {
94
+ let running = 0;
95
+ const prefix = [];
96
+ for (const group of args.groupedSeries()) {
97
+ prefix.push(running);
98
+ const seriesXFn = resolveAccessor(group.series.x);
99
+ const seriesYFn = resolveAccessor(group.series.y);
100
+ const row = group.series.data.find((d) => seriesXFn(d) === category);
101
+ running += row ? Number(seriesYFn(row)) : 0;
102
+ }
103
+ sums.set(category, prefix);
104
+ }
105
+ return sums;
106
+ });
107
+ function stackedBaseline(category, seriesIndex) {
108
+ return stackedPrefixSums.get(category)?.[seriesIndex] ?? 0;
109
+ }
110
+ /**
111
+ * Pixel offset for a 'grouped' series' own slice of the category band —
112
+ * shared by bar segments (`insetLeft`/`insetTop`), value labels
113
+ * (`dx`/`dy`), and hover markers, so all three stay in sync with wherever
114
+ * this series' bar actually renders.
115
+ */
116
+ function groupOffset(seriesIndex) {
117
+ if (layout !== 'grouped')
118
+ return { dx: 0, dy: 0 };
119
+ const offset = -(estimatedBandwidth / 2) + seriesIndex * seriesStep + seriesStep / 2;
120
+ return args.isHorizontal() ? { dx: 0, dy: offset } : { dx: offset, dy: 0 };
121
+ }
122
+ return {
123
+ get layout() { return layout; },
124
+ get valueBaseline() { return valueBaseline; },
125
+ get estimatedBandwidth() { return estimatedBandwidth; },
126
+ get seriesCount() { return seriesCount; },
127
+ get seriesStep() { return seriesStep; },
128
+ get seriesGap() { return seriesGap; },
129
+ stackedBaseline,
130
+ groupOffset,
131
+ };
132
+ }