@fundar/data-chart-telling 0.0.25 → 0.0.27

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 (198) hide show
  1. package/dist/charts/BaseChart.svelte +18 -10
  2. package/dist/charts/BaseChart.svelte.d.ts +4 -2
  3. package/dist/charts/bar/Chart.svelte +2 -0
  4. package/dist/charts/heatmap/Chart.svelte +5 -3
  5. package/dist/charts/heatmap/Chart.svelte.d.ts +1 -2
  6. package/dist/charts/line/Chart.svelte +2 -0
  7. package/dist/charts/pyramid/Chart.svelte +5 -3
  8. package/dist/charts/pyramid/Chart.svelte.d.ts +1 -2
  9. package/dist/charts/scatter/Chart.svelte +95 -0
  10. package/dist/charts/scatter/Chart.svelte.d.ts +28 -0
  11. package/dist/configuration/config.svelte.js +28 -0
  12. package/dist/configuration/themes/dark.js +4 -0
  13. package/dist/configuration/themes/index.d.ts +112 -0
  14. package/dist/index.d.ts +26 -16
  15. package/dist/index.js +11 -3
  16. package/dist/layout/coordinates/CoordinatesLayout.svelte +93 -0
  17. package/dist/layout/{plot/RuleLayout.svelte.d.ts → coordinates/CoordinatesLayout.svelte.d.ts} +13 -16
  18. package/dist/layout/coordinates/margins.d.ts +16 -0
  19. package/dist/layout/coordinates/margins.js +11 -0
  20. package/dist/{plots/geo → layout/coordinates}/projections/argentina.d.ts +1 -1
  21. package/dist/layout/{geo → coordinates}/resolveProjection.d.ts +1 -1
  22. package/dist/layout/coordinates/resolveProjection.js +24 -0
  23. package/dist/{plots/geo → layout/coordinates}/zoom.svelte.d.ts +1 -1
  24. package/dist/layout/facet/FacetIndicator.svelte +31 -0
  25. package/dist/layout/facet/FacetIndicator.svelte.d.ts +8 -0
  26. package/dist/layout/facet/FacetLayout.svelte +249 -24
  27. package/dist/layout/facet/FacetLayout.svelte.d.ts +7 -1
  28. package/dist/layout/geometry/GeometryLayout.svelte +231 -0
  29. package/dist/layout/geometry/GeometryLayout.svelte.d.ts +62 -0
  30. package/dist/layout/legend/ContinuousSection.svelte +1 -1
  31. package/dist/layout/legend/ContinuousSection.svelte.d.ts +1 -1
  32. package/dist/layout/legend/DiscreteSection.svelte +1 -1
  33. package/dist/layout/legend/DiscreteSection.svelte.d.ts +1 -1
  34. package/dist/layout/legend/LegendLayout.svelte +1 -1
  35. package/dist/layout/legend/LegendLayout.svelte.d.ts +1 -1
  36. package/dist/layout/legend/interaction.svelte.d.ts +9 -1
  37. package/dist/layout/legend/interaction.svelte.js +9 -1
  38. package/dist/layout/plot/BasePlotLayout.svelte +142 -155
  39. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +61 -51
  40. package/dist/layout/plot/margins.d.ts +0 -16
  41. package/dist/layout/plot/margins.js +0 -11
  42. package/dist/layout/{plot/AxisLayout.svelte → scales/ScalesLayout.svelte} +23 -16
  43. package/dist/layout/{plot/AxisLayout.svelte.d.ts → scales/ScalesLayout.svelte.d.ts} +4 -5
  44. package/dist/layout/scales/buildScale.d.ts +8 -0
  45. package/dist/layout/scales/buildScale.js +43 -0
  46. package/dist/layout/styles/StylesLayout.svelte +75 -0
  47. package/dist/layout/styles/StylesLayout.svelte.d.ts +15 -0
  48. package/dist/{plots/geo → layout/styles}/TileLayer.svelte +2 -2
  49. package/dist/{plots/geo → layout/styles}/TileLayer.svelte.d.ts +1 -1
  50. package/dist/layout/timeline/Timeline.svelte +1 -1
  51. package/dist/layout/timeline/Timeline.svelte.d.ts +1 -1
  52. package/dist/layout/timeline/TimelineLayout.svelte +1 -1
  53. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +1 -1
  54. package/dist/layout/tooltip/Tooltip.svelte +16 -20
  55. package/dist/layout/tooltip/Tooltip.svelte.d.ts +5 -0
  56. package/dist/layout/tooltip/TooltipLayout.svelte +7 -1
  57. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +4 -0
  58. package/dist/layout/tooltip/controller.svelte.d.ts +4 -5
  59. package/dist/layout/tooltip/controller.svelte.js +42 -16
  60. package/dist/layout/tooltip/resolveTooltipPlacement.d.ts +24 -0
  61. package/dist/layout/tooltip/resolveTooltipPlacement.js +49 -0
  62. package/dist/layout/tooltip/utils.d.ts +27 -19
  63. package/dist/layout/tooltip/utils.js +16 -15
  64. package/dist/markers/BarMarker.svelte +92 -0
  65. package/dist/markers/BarMarker.svelte.d.ts +7 -0
  66. package/dist/markers/CellMarker.svelte +21 -0
  67. package/dist/markers/CellMarker.svelte.d.ts +7 -0
  68. package/dist/markers/ContourMarker.svelte +28 -0
  69. package/dist/markers/ContourMarker.svelte.d.ts +7 -0
  70. package/dist/markers/DeltaMarker.svelte +32 -39
  71. package/dist/markers/DeltaMarker.svelte.d.ts +4 -5
  72. package/dist/markers/DotMarker.svelte +35 -6
  73. package/dist/markers/DotMarker.svelte.d.ts +11 -2
  74. package/dist/markers/GeoFeaturesMarker.svelte +19 -0
  75. package/dist/markers/GeoFeaturesMarker.svelte.d.ts +7 -0
  76. package/dist/markers/GeoHoverMarker.svelte +25 -0
  77. package/dist/markers/GeoHoverMarker.svelte.d.ts +9 -0
  78. package/dist/markers/GraticuleMarker.svelte +18 -0
  79. package/dist/markers/GraticuleMarker.svelte.d.ts +7 -0
  80. package/dist/markers/HoverMarker.svelte +34 -35
  81. package/dist/markers/HoverMarker.svelte.d.ts +15 -9
  82. package/dist/markers/LineMarker.svelte +19 -0
  83. package/dist/markers/LineMarker.svelte.d.ts +7 -0
  84. package/dist/markers/LinkMarker.svelte +42 -0
  85. package/dist/markers/LinkMarker.svelte.d.ts +7 -0
  86. package/dist/markers/RuleMarker.svelte +24 -0
  87. package/dist/markers/RuleMarker.svelte.d.ts +10 -0
  88. package/dist/markers/SphereMarker.svelte +15 -0
  89. package/dist/markers/SphereMarker.svelte.d.ts +7 -0
  90. package/dist/markers/TextMarker.svelte +1 -1
  91. package/dist/markers/TextMarker.svelte.d.ts +1 -1
  92. package/dist/markers/VectorMarker.svelte +20 -0
  93. package/dist/markers/VectorMarker.svelte.d.ts +7 -0
  94. package/dist/markers/inset/InsetMarker.svelte +38 -45
  95. package/dist/markers/inset/InsetMarker.svelte.d.ts +6 -2
  96. package/dist/plots/bar/Plot.svelte +96 -87
  97. package/dist/plots/bar/Plot.svelte.d.ts +4 -5
  98. package/dist/plots/bar/ValueLabels.svelte +70 -24
  99. package/dist/plots/bar/ValueLabels.svelte.d.ts +11 -2
  100. package/dist/plots/bar/buildBarMarkers.d.ts +30 -0
  101. package/dist/plots/bar/buildBarMarkers.js +100 -0
  102. package/dist/plots/bar/layout.svelte.d.ts +1 -2
  103. package/dist/plots/bar/layout.svelte.js +1 -1
  104. package/dist/plots/geo/Plot.svelte +113 -387
  105. package/dist/plots/geo/Plot.svelte.d.ts +5 -4
  106. package/dist/plots/geo/buildGeoMarkers.d.ts +33 -0
  107. package/dist/plots/geo/buildGeoMarkers.js +47 -0
  108. package/dist/plots/heatmap/Plot.svelte +27 -52
  109. package/dist/plots/heatmap/Plot.svelte.d.ts +4 -5
  110. package/dist/plots/heatmap/ValueLabels.svelte +5 -5
  111. package/dist/plots/heatmap/ValueLabels.svelte.d.ts +3 -3
  112. package/dist/plots/heatmap/buildCellMarkers.d.ts +21 -0
  113. package/dist/plots/heatmap/buildCellMarkers.js +25 -0
  114. package/dist/plots/line/Plot.svelte +73 -141
  115. package/dist/plots/line/Plot.svelte.d.ts +4 -4
  116. package/dist/plots/line/ValueLabels.svelte +84 -85
  117. package/dist/plots/line/ValueLabels.svelte.d.ts +10 -1
  118. package/dist/plots/line/buildLineMarkers.d.ts +15 -0
  119. package/dist/plots/line/buildLineMarkers.js +57 -0
  120. package/dist/plots/pyramid/Plot.svelte +80 -121
  121. package/dist/plots/pyramid/Plot.svelte.d.ts +4 -5
  122. package/dist/plots/pyramid/ValueLabels.svelte +71 -25
  123. package/dist/plots/pyramid/ValueLabels.svelte.d.ts +11 -2
  124. package/dist/plots/pyramid/buildPyramidBarMarkers.d.ts +19 -0
  125. package/dist/plots/pyramid/buildPyramidBarMarkers.js +32 -0
  126. package/dist/plots/scatter/Plot.svelte +242 -0
  127. package/dist/{layout/plot/GridLayout.svelte.d.ts → plots/scatter/Plot.svelte.d.ts} +9 -7
  128. package/dist/plots/scatter/ValueLabels.svelte +149 -0
  129. package/dist/plots/{bar/BarSegments.svelte.d.ts → scatter/ValueLabels.svelte.d.ts} +17 -15
  130. package/dist/plots/scatter/buildScatterMarkers.d.ts +11 -0
  131. package/dist/plots/scatter/buildScatterMarkers.js +37 -0
  132. package/dist/plots/scatter/resolveRadiusPaddedDomain.d.ts +10 -0
  133. package/dist/plots/scatter/resolveRadiusPaddedDomain.js +23 -0
  134. package/dist/plots/utils/declutter.d.ts +55 -37
  135. package/dist/plots/utils/declutter.js +79 -64
  136. package/dist/plots/utils/geoAccessors.d.ts +1 -1
  137. package/dist/plots/utils/geoAccessors.js +1 -1
  138. package/dist/plots/utils/labelOverflow.svelte.d.ts +51 -0
  139. package/dist/plots/utils/labelOverflow.svelte.js +116 -0
  140. package/dist/plots/utils/legendDisabled.d.ts +1 -1
  141. package/dist/plots/utils/segments.d.ts +17 -10
  142. package/dist/plots/utils/segments.js +26 -16
  143. package/dist/types/charts/common.d.ts +3 -50
  144. package/dist/types/charts/props.d.ts +8 -8
  145. package/dist/types/configuration/styling.d.ts +26 -2
  146. package/dist/types/{plots/scales/geo.d.ts → layout/coordinates.d.ts} +33 -1
  147. package/dist/types/layout/facet.d.ts +41 -0
  148. package/dist/types/layout/geometry.d.ts +17 -0
  149. package/dist/types/layout/legend.d.ts +65 -1
  150. package/dist/types/{plots/axis.d.ts → layout/scales.d.ts} +11 -0
  151. package/dist/types/layout/styles.d.ts +115 -0
  152. package/dist/types/layout/timeline.d.ts +32 -0
  153. package/dist/types/layout/tooltip.d.ts +20 -1
  154. package/dist/types/markers/all.d.ts +11 -0
  155. package/dist/types/markers/all.js +1 -0
  156. package/dist/types/markers/base.d.ts +45 -0
  157. package/dist/types/markers/base.js +1 -0
  158. package/dist/types/markers/common.d.ts +64 -16
  159. package/dist/types/markers/geo.d.ts +75 -100
  160. package/dist/types/markers/line.d.ts +34 -0
  161. package/dist/types/markers/line.js +1 -0
  162. package/dist/types/markers/props.d.ts +1 -1
  163. package/dist/types/plots/data/common.d.ts +1 -1
  164. package/dist/types/plots/data/scatter.d.ts +13 -0
  165. package/dist/types/plots/data/scatter.js +1 -0
  166. package/dist/types/plots/props.d.ts +46 -74
  167. package/dist/types/plots/segments/common.d.ts +6 -12
  168. package/dist/types/plots/segments/config.d.ts +1 -1
  169. package/dist/types/plots/styling.d.ts +10 -0
  170. package/dist/{plots/utils → utils}/delta.d.ts +2 -2
  171. package/dist/{plots/utils → utils}/delta.js +1 -1
  172. package/dist/utils/edgeAwareAnchor.d.ts +37 -0
  173. package/dist/utils/edgeAwareAnchor.js +65 -0
  174. package/dist/utils/grouping.d.ts +1 -1
  175. package/dist/utils/interpolate.d.ts +2 -2
  176. package/package.json +3 -1
  177. package/dist/layout/geo/GeoLayout.svelte +0 -51
  178. package/dist/layout/geo/GeoLayout.svelte.d.ts +0 -24
  179. package/dist/layout/geo/resolveProjection.js +0 -48
  180. package/dist/layout/plot/GridLayout.svelte +0 -30
  181. package/dist/layout/plot/RuleLayout.svelte +0 -111
  182. package/dist/plots/bar/BarSegments.svelte +0 -69
  183. package/dist/types/charts/legend.d.ts +0 -60
  184. package/dist/types/plots/delta.d.ts +0 -19
  185. package/dist/types/plots/styles/common.d.ts +0 -52
  186. package/dist/types/plots/styles/geo.d.ts +0 -42
  187. /package/dist/{plots/geo → layout/coordinates}/projections/argentina.js +0 -0
  188. /package/dist/{plots/geo → layout/coordinates}/projections/fit.d.ts +0 -0
  189. /package/dist/{plots/geo → layout/coordinates}/projections/fit.js +0 -0
  190. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.d.ts +0 -0
  191. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.js +0 -0
  192. /package/dist/{plots/geo → layout/coordinates}/zoom.svelte.js +0 -0
  193. /package/dist/types/{charts/legend.js → layout/coordinates.js} +0 -0
  194. /package/dist/types/{plots/axis.js → layout/facet.js} +0 -0
  195. /package/dist/types/{plots/delta.js → layout/geometry.js} +0 -0
  196. /package/dist/types/{plots/scales/geo.js → layout/scales.js} +0 -0
  197. /package/dist/types/{plots/styles/common.js → layout/styles.js} +0 -0
  198. /package/dist/types/{plots/styles/geo.js → layout/timeline.js} +0 -0
@@ -2,33 +2,28 @@
2
2
  lang="ts"
3
3
  generics="TData extends Record<string, unknown>"
4
4
  >
5
- import { SvelteMap } from 'svelte/reactivity';
6
5
  import { paletteColor } from '../../utils/color';
7
6
  import { buildSeries } from '../../utils/grouping';
8
7
  import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
9
8
  import { disabledFillOverride } from '../utils/legendDisabled';
10
- import { BarX, BarY } from 'svelteplot';
11
9
  import { resolveAccessor } from '../utils/accessors';
12
10
  import { getConfiguration } from '../../configuration/config.svelte';
13
- import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX } from '../utils/segments';
11
+ import { buildGroupedSeries, validateSegments, buildScopedMarkerGroups } from '../utils/segments';
14
12
  import { createBarLayout } from './layout.svelte';
13
+ import { createLabelMarginTracker } from '../utils/labelOverflow.svelte';
15
14
  import { buildHoverPoints } from './hoverPoints';
16
- import BarSegments from './BarSegments.svelte';
15
+ import { buildBarMarkers } from './buildBarMarkers';
17
16
  import ValueLabels from './ValueLabels.svelte';
18
17
  import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
19
- import AxisLayout from '../../layout/plot/AxisLayout.svelte';
20
- import GridLayout from '../../layout/plot/GridLayout.svelte';
21
- import RuleLayout from '../../layout/plot/RuleLayout.svelte';
22
- import HoverMarker from '../../markers/HoverMarker.svelte';
23
18
  import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
24
- import type { Marker } from '../../types/markers/common';
19
+ import type { AxisBasedMarkersConfig } from '../../types/markers/common';
25
20
  import type { BasePlotProps } from '../../types/plots/props';
26
- import type { AxisScale } from '../../types/plots/axis';
21
+ import type { AxisScale, AxisBasedScalesConfig } from '../../types/layout/scales';
27
22
  import type { Series, SeriesProps, DataProps } from '../../types/plots/data/common';
28
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
29
- import type { BasePlotStyles } from '../../types/plots/styles/common';
23
+ import type { BasePlotStylesConfig } from '../../types/layout/styles';
30
24
  import type { ValueAnchor } from '../../types/plots/constants';
31
25
  import type { BarSegmentStyle } from '../../types/plots/segments/common';
26
+ import type { LegendDisabledStyle } from '../../types/layout/legend';
32
27
 
33
28
  /**
34
29
  * A bar chart, vertical by default. Accepts either a pre-built `series`
@@ -38,9 +33,9 @@
38
33
  type Props = BasePlotProps<
39
34
  SeriesProps<TData> | DataProps<TData>,
40
35
  TData,
41
- BasePlotStyles,
36
+ BasePlotStylesConfig,
42
37
  BarSegmentStyle,
43
- Marker,
38
+ AxisBasedMarkersConfig,
44
39
  AxisBasedScalesConfig<TData>
45
40
  >;
46
41
 
@@ -70,26 +65,24 @@
70
65
  buildGroupedSeries<Series<TData>, BarSegmentStyle>(resolvedSeries, segments, false),
71
66
  );
72
67
  const showVals = $derived(styles.values?.show ?? false);
73
- const fmtVal = $derived(styles.values?.format ?? ((v: number) => `${v}`));
68
+ const formatValue = $derived(styles.values?.format ?? ((v: number) => `${v}`));
74
69
  const valAnchor = $derived<ValueAnchor>(styles.values?.anchor ?? 'outside');
75
70
 
76
- const seriesMarkers = $derived.by(() => {
77
- const map = new SvelteMap<string, Marker[]>();
78
- for (const marker of markers) {
79
- if (marker.type === 'hover' && marker.series) {
80
- if (!map.has(marker.series)) map.set(marker.series, []);
81
- map.get(marker.series)!.push(marker);
82
- }
83
- }
84
- return map;
85
- });
86
-
87
71
  // `xAcc`/`yAcc` are always data-semantic (category/value) — independent of
88
72
  // which *visual* axis ends up drawing them, decided by `categoricalAxis`.
89
73
  const xAcc = $derived(resolveAccessor(resolvedSeries[0]?.x ?? ((d: TData) => d)));
90
74
  const yAcc = $derived(resolveAccessor(resolvedSeries[0]?.y ?? ((d: TData) => d)));
91
75
  const flat = $derived(resolvedSeries.flatMap((s) => s.data));
92
76
 
77
+ const scopedGroups = $derived(
78
+ buildScopedMarkerGroups<TData, Series<TData>, BarSegmentStyle>({
79
+ groupedSeries,
80
+ segments,
81
+ colorFor: (_series, seriesIndex) => paletteColor(seriesIndex, cfg.palette),
82
+ segmentColorFor: (style, defaultColor) => style?.fill ?? defaultColor,
83
+ }),
84
+ );
85
+
93
86
  /**
94
87
  * Which visual axis draws the category, and therefore whether this renders
95
88
  * as vertical bars (`BarY`, category on X) or horizontal bars (`BarX`,
@@ -112,7 +105,6 @@
112
105
  return yCategorical ? 'y' : 'x';
113
106
  });
114
107
  const isHorizontal = $derived(categoricalAxis === 'y');
115
- const BarMark = $derived(isHorizontal ? BarX : BarY);
116
108
 
117
109
  /**
118
110
  * `'categorical'` is our own sentinel (BarPlot-only), not a real svelteplot
@@ -163,6 +155,23 @@
163
155
  // `row` (see BasePlotLayout/HoverPoint) is what lets the template recover the
164
156
  // semantic category regardless of which axis it landed on.
165
157
  const legendInteraction = $derived(getLegendInteraction());
158
+ const hoverIsolate = $derived(styles.values?.hoverIsolate ?? false);
159
+
160
+ // A value label hovered while `styles.values.hoverIsolate` is on dims
161
+ // every other series exactly like a legend toggle would — local to this
162
+ // one `<Plot>` instance (not the shared HoverStore) so the gesture can't
163
+ // leak into unrelated tooltip/crosshair behavior.
164
+ let hoverIsolatedSeries = $state<string | null>(null);
165
+
166
+ function effectiveDisabledFor(name: string): LegendDisabledStyle | undefined {
167
+ return (
168
+ legendInteraction?.disabledSeries.get(name) ??
169
+ (hoverIsolate && hoverIsolatedSeries && hoverIsolatedSeries !== name
170
+ ? { opacity: cfg.legend.disabledOpacity }
171
+ : undefined)
172
+ );
173
+ }
174
+
166
175
  // A legend-disabled series is dimmed on the bar itself, but never surfaces
167
176
  // in hover/tooltip — its points are simply absent from the candidate list.
168
177
  const hoverPoints = $derived(
@@ -171,76 +180,76 @@
171
180
  ),
172
181
  );
173
182
 
174
- const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
175
- const hoverStrategy = $derived(resolveHoverStrategy(tooltip?.strategy, markers, categoricalAxis));
176
- const hoverSync = $derived(resolveHoverSync(tooltip?.sync, markers));
183
+ const hover = $derived({
184
+ active: tooltip != null || hasHoverMarker(markers),
185
+ points: hoverPoints,
186
+ label: (r: TData) => String(yAcc(r)),
187
+ strategy: resolveHoverStrategy(tooltip?.strategy, markers, categoricalAxis),
188
+ sync: resolveHoverSync(tooltip?.sync, markers),
189
+ tooltip,
190
+ // The category axis is a band scale — `<Pointer>`'s nearest-point-by-
191
+ // distance match (svelteplot's own projection of each candidate's band
192
+ // centre) can disagree by a couple of pixels with where `<BarX>`/`<BarY>`
193
+ // (via svelteplot's `RectPath`) actually render that band, which is
194
+ // enough to mismatch the neighbouring category once bands get thin
195
+ // (many categories). The `role: 'hitArea'` bar markers `buildBarMarkers`
196
+ // emits hit-test the category bands directly instead — see its own doc
197
+ // comment.
198
+ pointMatching: 'contains' as const,
199
+ });
200
+
201
+ // A bar's value label can grow past the plot's own content box (e.g. a
202
+ // tall bar's `anchor: 'outside'` label pushing past the top margin) —
203
+ // reserving real space there is what each label's own overflow
204
+ // measurement feeds into here, via a tracker local to this one `<Plot>`
205
+ // instance. See `labelOverflow.svelte.ts`.
206
+ const labelMargin = createLabelMarginTracker(() => margins);
177
207
  </script>
178
208
 
179
209
  <BasePlotLayout
180
210
  {width}
181
211
  {height}
212
+ {styles}
182
213
  data={flat}
183
214
  getX={plotGetX}
184
215
  getY={plotGetY}
185
- {hoverPoints}
186
- label={(r) => String(yAcc(r))}
187
- {tooltip}
188
- {hoverActive}
189
- {hoverStrategy}
190
- {hoverSync}
216
+ {hover}
191
217
  scales={resolvedScales}
192
- {margins}
218
+ margins={labelMargin.resolvedMargins}
193
219
  xTickRotate={resolvedScales?.x?.tickRotate}
220
+ markers={({ setHoverMatch, clearHoverMatch }) => [
221
+ ...buildBarMarkers({
222
+ groupedSeries,
223
+ barLayout,
224
+ isHorizontal,
225
+ hoverPoints,
226
+ palette: cfg.palette,
227
+ disabledFillFor: (name) => disabledFillOverride(effectiveDisabledFor(name)),
228
+ active: hover.active,
229
+ setHoverMatch,
230
+ clearHoverMatch,
231
+ }),
232
+ ...markers,
233
+ ]}
234
+ seriesData={resolvedSeries}
235
+ {scopedGroups}
194
236
  >
195
- {#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY, numericMargins })}
196
- <AxisLayout data={flat} getX={plotGetX} getY={plotGetY} {width} {height} {numericMargins} scales={resolvedScales} />
197
- <GridLayout scales={resolvedScales} />
198
- <RuleLayout {markers} seriesData={resolvedSeries} {matchedPoints} {ruleX} {ruleY} {impliesRuleX} {impliesRuleY}>
199
- {#each groupedSeries as group, seriesIndex (group.series.name)}
200
- {@const defaultColor = paletteColor(seriesIndex, cfg.palette)}
201
- {@const disabledFill = disabledFillOverride(legendInteraction?.disabledSeries.get(group.series.name))}
202
- <BarSegments {group} {seriesIndex} {barLayout} {isHorizontal} {BarMark} {defaultColor} {disabledFill} />
203
-
204
- {#if showVals}
205
- <ValueLabels {group} {seriesIndex} {barLayout} {isHorizontal} {valAnchor} {fmtVal} values={styles.values} axisColor={cfg.axis.color} />
206
- {/if}
207
-
208
- {#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-hover-${i}`)}
209
- {#if marker.type === 'hover'}
210
- {@const seriesPoints = matchedPoints.filter((p) => p.series === group.series.name)}
211
- {#if marker.scope === 'segment'}
212
- {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
213
- {#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
214
- {@const segPoints = seriesPoints.filter((p) => matchesSegmentX(xAcc(p.row), seg))}
215
- {#if segPoints.length > 0}
216
- <HoverMarker
217
- data={segPoints}
218
- ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
219
- ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
220
- showLabels={marker.showLabels}
221
- ruleStyle={marker.ruleStyle}
222
- dotStyle={marker.dotStyle}
223
- fontStyle={marker.fontStyle}
224
- seriesColor={seg.style?.fill ?? defaultColor}
225
- />
226
- {/if}
227
- {/each}
228
- {:else}
229
- {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
230
- <HoverMarker
231
- data={seriesPoints}
232
- ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
233
- ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
234
- showLabels={marker.showLabels}
235
- ruleStyle={marker.ruleStyle}
236
- dotStyle={marker.dotStyle}
237
- fontStyle={marker.fontStyle}
238
- seriesColor={segs[0]?.style?.fill ?? defaultColor}
239
- />
240
- {/if}
241
- {/if}
237
+ {#if showVals}
238
+ {#each groupedSeries as group, seriesIndex (group.series.name)}
239
+ <ValueLabels
240
+ {group}
241
+ {seriesIndex}
242
+ {barLayout}
243
+ {isHorizontal}
244
+ {valAnchor}
245
+ {formatValue}
246
+ values={styles.values}
247
+ axisColor={cfg.axis.color}
248
+ disabled={effectiveDisabledFor(group.series.name)}
249
+ onHoverSeries={hoverIsolate ? () => { hoverIsolatedSeries = group.series.name; } : undefined}
250
+ onHoverEnd={hoverIsolate ? () => { hoverIsolatedSeries = null; } : undefined}
251
+ onOverflow={labelMargin.report}
252
+ />
242
253
  {/each}
243
- {/each}
244
- </RuleLayout>
245
- {/snippet}
254
+ {/if}
246
255
  </BasePlotLayout>
@@ -1,11 +1,10 @@
1
- import type { Marker } from '../../types/markers/common';
1
+ import type { AxisBasedMarkersConfig } from '../../types/markers/common';
2
2
  import type { BasePlotProps } from '../../types/plots/props';
3
+ import type { AxisBasedScalesConfig } from '../../types/layout/scales';
3
4
  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';
5
+ import type { BasePlotStylesConfig } from '../../types/layout/styles';
7
6
  declare function $$render<TData extends Record<string, unknown>>(): {
8
- props: BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, BasePlotStyles, BarSegmentStyle, Marker, AxisBasedScalesConfig<TData>>;
7
+ props: BasePlotProps<SeriesProps<TData> | DataProps<TData>, TData, BasePlotStylesConfig, import("../..").AreaStyle, AxisBasedMarkersConfig, AxisBasedScalesConfig<TData>>;
9
8
  exports: {};
10
9
  bindings: "";
11
10
  slots: {};
@@ -1,11 +1,15 @@
1
1
  <script lang="ts" generics="TData extends Record<string, unknown>">
2
- import { Text } from 'svelteplot';
2
+ import { Text, usePlot } from 'svelteplot';
3
3
  import { resolveAccessor } from '../utils/accessors';
4
+ import { disabledFillOverride } from '../utils/legendDisabled';
5
+ import { watchLabelOverflow } from '../utils/labelOverflow.svelte';
4
6
  import type { Series } from '../../types/plots/data/common';
5
- import type { ValuesStyle } from '../../types/plots/styles/common';
7
+ import type { ValuesStyle } from '../../types/layout/styles';
6
8
  import type { ValueAnchor } from '../../types/plots/constants';
7
9
  import type { BarSegmentStyle } from '../../types/plots/segments/common';
8
10
  import type { VisualGroup } from '../../types/plots/segments/config';
11
+ import type { LegendDisabledStyle } from '../../types/layout/legend';
12
+ import type { MarginOverflow } from '../utils/labelOverflow.svelte';
9
13
  import type { BarLayout } from './layout.svelte';
10
14
 
11
15
  /**
@@ -21,20 +25,56 @@
21
25
  barLayout,
22
26
  isHorizontal,
23
27
  valAnchor,
24
- fmtVal,
28
+ formatValue,
25
29
  values,
26
30
  axisColor,
31
+ disabled,
32
+ onHoverSeries,
33
+ onHoverEnd,
34
+ onOverflow,
27
35
  }: {
28
36
  group: VisualGroup<Series<TData>, BarSegmentStyle>;
29
37
  seriesIndex: number;
30
38
  barLayout: BarLayout<TData>;
31
39
  isHorizontal: boolean;
32
40
  valAnchor: ValueAnchor;
33
- fmtVal: (value: number, seriesName: string) => string;
41
+ formatValue: (value: number, seriesName: string) => string;
34
42
  values: ValuesStyle | undefined;
35
43
  axisColor: string;
44
+ /** Resolved legend/hover-isolate dimming style for this group's series, if any. */
45
+ disabled?: LegendDisabledStyle;
46
+ /** Fired when the pointer enters this series' label — only wired when hover-isolate is enabled. */
47
+ onHoverSeries?: () => void;
48
+ onHoverEnd?: () => void;
49
+ /** Reports how far this label overflows the plot's own content box, in pixels, on each side (0 = no overflow). */
50
+ onOverflow?: (overflow: MarginOverflow) => void;
36
51
  } = $props();
37
52
 
53
+ const fillOverride = $derived(disabledFillOverride(disabled));
54
+ const plot = usePlot();
55
+ let groupEl: SVGGElement | undefined = $state();
56
+
57
+ watchLabelOverflow({
58
+ el: () => groupEl,
59
+ bounds: () => {
60
+ // getBBox() inside isn't itself tracked — depend on whatever actually
61
+ // changes the rendered label layout so this re-measures.
62
+ void group;
63
+ void values;
64
+ void valAnchor;
65
+ const xRange = plot.scales.x?.fn?.range?.()?.map(Number);
66
+ const yRange = plot.scales.y?.fn?.range?.()?.map(Number);
67
+ if (!xRange || !yRange) return undefined;
68
+ return {
69
+ left: Math.min(xRange[0], xRange[1]),
70
+ right: Math.max(xRange[0], xRange[1]),
71
+ top: Math.min(yRange[0], yRange[1]),
72
+ bottom: Math.max(yRange[0], yRange[1]),
73
+ };
74
+ },
75
+ onOverflow: (overflow) => onOverflow?.(overflow),
76
+ });
77
+
38
78
  const xFn = $derived(resolveAccessor(group.series.x));
39
79
  const yFn = $derived(resolveAccessor(group.series.y));
40
80
 
@@ -68,23 +108,29 @@
68
108
  const resolvedTextAnchor = $derived(font?.textAnchor === 'outside' ? undefined : font?.textAnchor);
69
109
  </script>
70
110
 
71
- <Text
72
- data={group.series.data}
73
- {...isHorizontal ? { y: xFn, x: valueTextFn } : { x: xFn, y: valueTextFn }}
74
- text={(d: TData) => fmtVal(Number(yFn(d)), group.series.name)}
75
- dx={font?.dx ?? (isHorizontal ? defaultOffset : groupOffset.dx)}
76
- dy={font?.dy ?? (isHorizontal ? groupOffset.dy : defaultOffset)}
77
- textAnchor={resolvedTextAnchor ?? (isHorizontal ? (valAnchor === 'outside' || valAnchor === 'end' ? 'start' : valAnchor === 'start' ? 'end' : 'middle') : 'middle')}
78
- lineAnchor={font?.lineAnchor ?? (isHorizontal ? 'middle' : defaultLA)}
79
- lineHeight={font?.lineHeight}
80
- rotate={font?.rotate}
81
- class={font?.class}
82
- textClass={font?.textClass}
83
- fill={font?.fill ?? axisColor}
84
- fontSize={font?.fontSize ?? 12}
85
- fontWeight={font?.fontWeight}
86
- fontStyle={font?.fontStyle}
87
- stroke={font?.stroke}
88
- strokeWidth={font?.strokeWidth}
89
- paintOrder={font?.paintOrder}
90
- />
111
+ <!-- svelteplot's Text mark never wires pointer handlers to its DOM node,
112
+ despite declaring them in its types — a wrapping <g> catches the
113
+ boundary crossing instead (pointerenter/leave fire on every ancestor). -->
114
+ <g bind:this={groupEl} role="presentation" onpointerenter={onHoverSeries} onpointerleave={onHoverEnd}>
115
+ <Text
116
+ data={group.series.data}
117
+ {...isHorizontal ? { y: xFn, x: valueTextFn } : { x: xFn, y: valueTextFn }}
118
+ text={(d: TData) => formatValue(Number(yFn(d)), group.series.name)}
119
+ dx={font?.dx ?? (isHorizontal ? defaultOffset : groupOffset.dx)}
120
+ dy={font?.dy ?? (isHorizontal ? groupOffset.dy : defaultOffset)}
121
+ textAnchor={resolvedTextAnchor ?? (isHorizontal ? (valAnchor === 'outside' || valAnchor === 'end' ? 'start' : valAnchor === 'start' ? 'end' : 'middle') : 'middle')}
122
+ lineAnchor={font?.lineAnchor ?? (isHorizontal ? 'middle' : defaultLA)}
123
+ lineHeight={font?.lineHeight}
124
+ rotate={font?.rotate}
125
+ class={font?.class}
126
+ textClass={font?.textClass}
127
+ fill={font?.fill ?? axisColor}
128
+ fillOpacity={fillOverride?.fillOpacity}
129
+ fontSize={font?.fontSize ?? 12}
130
+ fontWeight={font?.fontWeight}
131
+ fontStyle={font?.fontStyle}
132
+ stroke={font?.stroke}
133
+ strokeWidth={font?.strokeWidth}
134
+ paintOrder={font?.paintOrder}
135
+ />
136
+ </g>
@@ -1,8 +1,10 @@
1
1
  import type { Series } from '../../types/plots/data/common';
2
- import type { ValuesStyle } from '../../types/plots/styles/common';
2
+ import type { ValuesStyle } from '../../types/layout/styles';
3
3
  import type { ValueAnchor } from '../../types/plots/constants';
4
4
  import type { BarSegmentStyle } from '../../types/plots/segments/common';
5
5
  import type { VisualGroup } from '../../types/plots/segments/config';
6
+ import type { LegendDisabledStyle } from '../../types/layout/legend';
7
+ import type { MarginOverflow } from '../utils/labelOverflow.svelte';
6
8
  import type { BarLayout } from './layout.svelte';
7
9
  declare function $$render<TData extends Record<string, unknown>>(): {
8
10
  props: {
@@ -11,9 +13,16 @@ declare function $$render<TData extends Record<string, unknown>>(): {
11
13
  barLayout: BarLayout<TData>;
12
14
  isHorizontal: boolean;
13
15
  valAnchor: ValueAnchor;
14
- fmtVal: (value: number, seriesName: string) => string;
16
+ formatValue: (value: number, seriesName: string) => string;
15
17
  values: ValuesStyle | undefined;
16
18
  axisColor: string;
19
+ /** Resolved legend/hover-isolate dimming style for this group's series, if any. */
20
+ disabled?: LegendDisabledStyle;
21
+ /** Fired when the pointer enters this series' label — only wired when hover-isolate is enabled. */
22
+ onHoverSeries?: () => void;
23
+ onHoverEnd?: () => void;
24
+ /** Reports how far this label overflows the plot's own content box, in pixels, on each side (0 = no overflow). */
25
+ onOverflow?: (overflow: MarginOverflow) => void;
17
26
  };
18
27
  exports: {};
19
28
  bindings: "";
@@ -0,0 +1,30 @@
1
+ import type { Series } from '../../types/plots/data/common';
2
+ import type { BarSegmentStyle } from '../../types/plots/segments/common';
3
+ import type { VisualGroup } from '../../types/plots/segments/config';
4
+ import type { HoverPoint } from '../../types/layout/tooltip';
5
+ import type { BarMarkerConfig } from '../../types/markers/common';
6
+ import type { BarLayout } from './layout.svelte';
7
+ /**
8
+ * Every `'bar'` marker for one BarPlot render: one `role: 'segment'` marker
9
+ * per series (stacked) or per visual segment (grouped/overlap), plus one
10
+ * `role: 'hitArea'` marker per category, so a chart never gets segment
11
+ * markers without their hit areas.
12
+ *
13
+ * `'hitArea'` geometry is left unresolved (just `category` + `data`) —
14
+ * resolving it to pixels needs svelteplot's live scale, only available
15
+ * where the marker actually renders.
16
+ */
17
+ export declare function buildBarMarkers<TData extends Record<string, unknown>>(args: {
18
+ groupedSeries: VisualGroup<Series<TData>, BarSegmentStyle>[];
19
+ barLayout: BarLayout<TData>;
20
+ isHorizontal: boolean;
21
+ hoverPoints: HoverPoint<TData>[];
22
+ palette: string[];
23
+ disabledFillFor: (seriesName: string) => {
24
+ fill?: string;
25
+ fillOpacity?: number;
26
+ } | undefined;
27
+ active: boolean;
28
+ setHoverMatch: (rows: TData[]) => void;
29
+ clearHoverMatch: () => void;
30
+ }): BarMarkerConfig[];
@@ -0,0 +1,100 @@
1
+ import { resolveAccessor } from '../utils/accessors';
2
+ import { paletteColor } from '../../utils/color';
3
+ /**
4
+ * Every `'bar'` marker for one BarPlot render: one `role: 'segment'` marker
5
+ * per series (stacked) or per visual segment (grouped/overlap), plus one
6
+ * `role: 'hitArea'` marker per category, so a chart never gets segment
7
+ * markers without their hit areas.
8
+ *
9
+ * `'hitArea'` geometry is left unresolved (just `category` + `data`) —
10
+ * resolving it to pixels needs svelteplot's live scale, only available
11
+ * where the marker actually renders.
12
+ */
13
+ export function buildBarMarkers(args) {
14
+ const { groupedSeries, barLayout, isHorizontal, hoverPoints, palette, disabledFillFor, active, setHoverMatch, clearHoverMatch } = args;
15
+ const segmentMarkers = [];
16
+ groupedSeries.forEach((group, seriesIndex) => {
17
+ const xFn = resolveAccessor(group.series.x);
18
+ const yFn = resolveAccessor(group.series.y);
19
+ const defaultColor = paletteColor(seriesIndex, palette);
20
+ const disabledFill = disabledFillFor(group.series.name);
21
+ if (barLayout.layout === 'stacked') {
22
+ const stackedBaselineFn = (d) => barLayout.stackedBaseline(xFn(d), seriesIndex);
23
+ const stackedTopFn = (d) => barLayout.stackedBaseline(xFn(d), seriesIndex) + Number(yFn(d));
24
+ const marker = {
25
+ type: 'bar',
26
+ role: 'segment',
27
+ isHorizontal,
28
+ data: group.series.data,
29
+ style: {
30
+ fill: disabledFill?.fill ?? defaultColor,
31
+ fillOpacity: disabledFill?.fillOpacity ?? 1,
32
+ },
33
+ };
34
+ if (isHorizontal) {
35
+ marker.y = xFn;
36
+ marker.x1 = stackedBaselineFn;
37
+ marker.x2 = stackedTopFn;
38
+ }
39
+ else {
40
+ marker.x = xFn;
41
+ marker.y1 = stackedBaselineFn;
42
+ marker.y2 = stackedTopFn;
43
+ }
44
+ segmentMarkers.push(marker);
45
+ return;
46
+ }
47
+ for (const seg of group.visualSegments) {
48
+ const marker = {
49
+ type: 'bar',
50
+ role: 'segment',
51
+ isHorizontal,
52
+ data: seg.data,
53
+ style: {
54
+ fill: disabledFill?.fill ?? seg.style.fill ?? defaultColor,
55
+ fillOpacity: disabledFill?.fillOpacity ?? seg.style.fillOpacity ?? 1,
56
+ },
57
+ };
58
+ if (isHorizontal) {
59
+ marker.y = xFn;
60
+ marker.x1 = barLayout.valueBaseline;
61
+ marker.x2 = yFn;
62
+ }
63
+ else {
64
+ marker.x = xFn;
65
+ marker.y1 = barLayout.valueBaseline;
66
+ marker.y2 = yFn;
67
+ }
68
+ if (barLayout.layout === 'grouped') {
69
+ if (isHorizontal) {
70
+ marker.insetTop = seriesIndex * barLayout.seriesStep + barLayout.seriesGap / 2;
71
+ marker.insetBottom = (barLayout.seriesCount - 1 - seriesIndex) * barLayout.seriesStep + barLayout.seriesGap / 2;
72
+ }
73
+ else {
74
+ marker.insetLeft = seriesIndex * barLayout.seriesStep + barLayout.seriesGap / 2;
75
+ marker.insetRight = (barLayout.seriesCount - 1 - seriesIndex) * barLayout.seriesStep + barLayout.seriesGap / 2;
76
+ }
77
+ }
78
+ segmentMarkers.push(marker);
79
+ }
80
+ });
81
+ const byCategory = new Map();
82
+ for (const p of hoverPoints) {
83
+ const key = isHorizontal ? p.y : p.x;
84
+ const list = byCategory.get(key);
85
+ if (list)
86
+ list.push(p);
87
+ else
88
+ byCategory.set(key, [p]);
89
+ }
90
+ const hitAreaMarkers = [...byCategory.entries()].map(([category, points]) => ({
91
+ type: 'bar',
92
+ role: 'hitArea',
93
+ isHorizontal,
94
+ category,
95
+ data: points.map((p) => p.row),
96
+ onpointerenter: active ? () => setHoverMatch(points.map((p) => p.row)) : undefined,
97
+ onpointerleave: active ? clearHoverMatch : undefined,
98
+ }));
99
+ return [...segmentMarkers, ...hitAreaMarkers];
100
+ }
@@ -1,6 +1,5 @@
1
- import type { AxisValue } from '../../types/plots/axis';
1
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
2
2
  import type { Series } from '../../types/plots/data/common';
3
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
3
  import type { BarSegmentStyle } from '../../types/plots/segments/common';
5
4
  import type { VisualGroup } from '../../types/plots/segments/config';
6
5
  import type { MarginConfig } from '../../types/plots/props';
@@ -13,7 +13,7 @@ export function createBarLayout(args) {
13
13
  const cfg = $derived(getConfiguration());
14
14
  /**
15
15
  * How series sharing the same category are arranged — see
16
- * {@link import('../../types/plots/axis').AxisScale.seriesLayout}. Read
16
+ * {@link import('../../types/layout/scales').AxisScale.seriesLayout}. Read
17
17
  * off `scales.z` since it's the grouping axis, not the category axis itself.
18
18
  */
19
19
  const layout = $derived(args.scales().z?.seriesLayout ?? 'overlap');