@fundar/data-chart-telling 0.0.25 → 0.0.26

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 (170) hide show
  1. package/dist/charts/bar/Chart.svelte +2 -0
  2. package/dist/charts/heatmap/Chart.svelte +3 -1
  3. package/dist/charts/heatmap/Chart.svelte.d.ts +1 -2
  4. package/dist/charts/line/Chart.svelte +2 -0
  5. package/dist/charts/pyramid/Chart.svelte +3 -1
  6. package/dist/charts/pyramid/Chart.svelte.d.ts +1 -2
  7. package/dist/charts/scatter/Chart.svelte +95 -0
  8. package/dist/charts/scatter/Chart.svelte.d.ts +28 -0
  9. package/dist/configuration/config.svelte.js +2 -0
  10. package/dist/configuration/themes/index.d.ts +48 -0
  11. package/dist/index.d.ts +20 -14
  12. package/dist/index.js +9 -3
  13. package/dist/layout/coordinates/CoordinatesLayout.svelte +95 -0
  14. package/dist/layout/{plot/RuleLayout.svelte.d.ts → coordinates/CoordinatesLayout.svelte.d.ts} +13 -16
  15. package/dist/layout/coordinates/margins.d.ts +16 -0
  16. package/dist/layout/coordinates/margins.js +11 -0
  17. package/dist/{plots/geo → layout/coordinates}/projections/argentina.d.ts +1 -1
  18. package/dist/layout/{geo → coordinates}/resolveProjection.d.ts +1 -1
  19. package/dist/layout/coordinates/resolveProjection.js +24 -0
  20. package/dist/{plots/geo → layout/coordinates}/zoom.svelte.d.ts +1 -1
  21. package/dist/layout/geometry/GeometryLayout.svelte +231 -0
  22. package/dist/layout/geometry/GeometryLayout.svelte.d.ts +62 -0
  23. package/dist/layout/plot/BasePlotLayout.svelte +142 -155
  24. package/dist/layout/plot/BasePlotLayout.svelte.d.ts +61 -51
  25. package/dist/layout/plot/margins.d.ts +0 -16
  26. package/dist/layout/plot/margins.js +0 -11
  27. package/dist/layout/{plot/AxisLayout.svelte → scales/ScalesLayout.svelte} +23 -16
  28. package/dist/layout/{plot/AxisLayout.svelte.d.ts → scales/ScalesLayout.svelte.d.ts} +4 -5
  29. package/dist/layout/scales/buildScale.d.ts +8 -0
  30. package/dist/layout/scales/buildScale.js +43 -0
  31. package/dist/layout/styles/StylesLayout.svelte +75 -0
  32. package/dist/layout/styles/StylesLayout.svelte.d.ts +15 -0
  33. package/dist/{plots/geo → layout/styles}/TileLayer.svelte +2 -2
  34. package/dist/{plots/geo → layout/styles}/TileLayer.svelte.d.ts +1 -1
  35. package/dist/layout/tooltip/Tooltip.svelte +16 -20
  36. package/dist/layout/tooltip/Tooltip.svelte.d.ts +5 -0
  37. package/dist/layout/tooltip/TooltipLayout.svelte +7 -1
  38. package/dist/layout/tooltip/TooltipLayout.svelte.d.ts +4 -0
  39. package/dist/layout/tooltip/controller.svelte.d.ts +4 -5
  40. package/dist/layout/tooltip/controller.svelte.js +28 -16
  41. package/dist/layout/tooltip/resolveTooltipPlacement.d.ts +24 -0
  42. package/dist/layout/tooltip/resolveTooltipPlacement.js +49 -0
  43. package/dist/layout/tooltip/utils.d.ts +27 -19
  44. package/dist/layout/tooltip/utils.js +16 -15
  45. package/dist/markers/BarMarker.svelte +92 -0
  46. package/dist/markers/BarMarker.svelte.d.ts +7 -0
  47. package/dist/markers/CellMarker.svelte +21 -0
  48. package/dist/markers/CellMarker.svelte.d.ts +7 -0
  49. package/dist/markers/ContourMarker.svelte +28 -0
  50. package/dist/markers/ContourMarker.svelte.d.ts +7 -0
  51. package/dist/markers/DeltaMarker.svelte +32 -39
  52. package/dist/markers/DeltaMarker.svelte.d.ts +4 -5
  53. package/dist/markers/DotMarker.svelte +35 -6
  54. package/dist/markers/DotMarker.svelte.d.ts +11 -2
  55. package/dist/markers/GeoFeaturesMarker.svelte +19 -0
  56. package/dist/markers/GeoFeaturesMarker.svelte.d.ts +7 -0
  57. package/dist/markers/GeoHoverMarker.svelte +25 -0
  58. package/dist/markers/GeoHoverMarker.svelte.d.ts +9 -0
  59. package/dist/markers/GraticuleMarker.svelte +18 -0
  60. package/dist/markers/GraticuleMarker.svelte.d.ts +7 -0
  61. package/dist/markers/HoverMarker.svelte +32 -33
  62. package/dist/markers/HoverMarker.svelte.d.ts +15 -9
  63. package/dist/markers/LineMarker.svelte +19 -0
  64. package/dist/markers/LineMarker.svelte.d.ts +7 -0
  65. package/dist/markers/LinkMarker.svelte +42 -0
  66. package/dist/markers/LinkMarker.svelte.d.ts +7 -0
  67. package/dist/markers/RuleMarker.svelte +24 -0
  68. package/dist/markers/RuleMarker.svelte.d.ts +10 -0
  69. package/dist/markers/SphereMarker.svelte +15 -0
  70. package/dist/markers/SphereMarker.svelte.d.ts +7 -0
  71. package/dist/markers/TextMarker.svelte +1 -1
  72. package/dist/markers/TextMarker.svelte.d.ts +1 -1
  73. package/dist/markers/VectorMarker.svelte +20 -0
  74. package/dist/markers/VectorMarker.svelte.d.ts +7 -0
  75. package/dist/markers/inset/InsetMarker.svelte +38 -45
  76. package/dist/markers/inset/InsetMarker.svelte.d.ts +6 -2
  77. package/dist/plots/bar/Plot.svelte +54 -84
  78. package/dist/plots/bar/Plot.svelte.d.ts +4 -5
  79. package/dist/plots/bar/ValueLabels.svelte +1 -1
  80. package/dist/plots/bar/ValueLabels.svelte.d.ts +1 -1
  81. package/dist/plots/bar/buildBarMarkers.d.ts +30 -0
  82. package/dist/plots/bar/buildBarMarkers.js +100 -0
  83. package/dist/plots/bar/layout.svelte.d.ts +1 -2
  84. package/dist/plots/bar/layout.svelte.js +1 -1
  85. package/dist/plots/geo/Plot.svelte +113 -387
  86. package/dist/plots/geo/Plot.svelte.d.ts +5 -4
  87. package/dist/plots/geo/buildGeoMarkers.d.ts +33 -0
  88. package/dist/plots/geo/buildGeoMarkers.js +47 -0
  89. package/dist/plots/heatmap/Plot.svelte +24 -49
  90. package/dist/plots/heatmap/Plot.svelte.d.ts +4 -5
  91. package/dist/plots/heatmap/ValueLabels.svelte +2 -2
  92. package/dist/plots/heatmap/ValueLabels.svelte.d.ts +2 -2
  93. package/dist/plots/heatmap/buildCellMarkers.d.ts +21 -0
  94. package/dist/plots/heatmap/buildCellMarkers.js +25 -0
  95. package/dist/plots/line/Plot.svelte +68 -141
  96. package/dist/plots/line/Plot.svelte.d.ts +4 -4
  97. package/dist/plots/line/ValueLabels.svelte +29 -52
  98. package/dist/plots/line/ValueLabels.svelte.d.ts +6 -1
  99. package/dist/plots/line/buildLineMarkers.d.ts +15 -0
  100. package/dist/plots/line/buildLineMarkers.js +57 -0
  101. package/dist/plots/pyramid/Plot.svelte +41 -117
  102. package/dist/plots/pyramid/Plot.svelte.d.ts +4 -5
  103. package/dist/plots/pyramid/ValueLabels.svelte +1 -1
  104. package/dist/plots/pyramid/ValueLabels.svelte.d.ts +1 -1
  105. package/dist/plots/pyramid/buildPyramidBarMarkers.d.ts +19 -0
  106. package/dist/plots/pyramid/buildPyramidBarMarkers.js +32 -0
  107. package/dist/plots/scatter/Plot.svelte +206 -0
  108. package/dist/plots/{bar/BarSegments.svelte.d.ts → scatter/Plot.svelte.d.ts} +9 -20
  109. package/dist/plots/scatter/ValueLabels.svelte +106 -0
  110. package/dist/{layout/plot/GridLayout.svelte.d.ts → plots/scatter/ValueLabels.svelte.d.ts} +11 -5
  111. package/dist/plots/scatter/buildScatterMarkers.d.ts +11 -0
  112. package/dist/plots/scatter/buildScatterMarkers.js +37 -0
  113. package/dist/plots/scatter/resolveRadiusPaddedDomain.d.ts +10 -0
  114. package/dist/plots/scatter/resolveRadiusPaddedDomain.js +23 -0
  115. package/dist/plots/utils/declutter.d.ts +55 -37
  116. package/dist/plots/utils/declutter.js +79 -64
  117. package/dist/plots/utils/geoAccessors.d.ts +1 -1
  118. package/dist/plots/utils/geoAccessors.js +1 -1
  119. package/dist/plots/utils/segments.d.ts +17 -10
  120. package/dist/plots/utils/segments.js +26 -16
  121. package/dist/types/charts/common.d.ts +1 -1
  122. package/dist/types/charts/props.d.ts +5 -6
  123. package/dist/types/configuration/styling.d.ts +7 -2
  124. package/dist/types/{plots/scales/geo.d.ts → layout/coordinates.d.ts} +33 -1
  125. package/dist/types/layout/geometry.d.ts +17 -0
  126. package/dist/types/{plots/axis.d.ts → layout/scales.d.ts} +11 -0
  127. package/dist/types/layout/styles.d.ts +126 -0
  128. package/dist/types/layout/tooltip.d.ts +20 -1
  129. package/dist/types/markers/all.d.ts +11 -0
  130. package/dist/types/markers/base.d.ts +45 -0
  131. package/dist/types/markers/base.js +1 -0
  132. package/dist/types/markers/common.d.ts +64 -16
  133. package/dist/types/markers/geo.d.ts +75 -100
  134. package/dist/types/markers/line.d.ts +34 -0
  135. package/dist/types/markers/line.js +1 -0
  136. package/dist/types/markers/props.d.ts +1 -1
  137. package/dist/types/plots/data/common.d.ts +1 -1
  138. package/dist/types/plots/data/scatter.d.ts +13 -0
  139. package/dist/types/plots/data/scatter.js +1 -0
  140. package/dist/types/plots/props.d.ts +46 -74
  141. package/dist/types/plots/segments/common.d.ts +6 -12
  142. package/dist/types/plots/segments/config.d.ts +1 -1
  143. package/dist/types/plots/styling.d.ts +10 -0
  144. package/dist/{plots/utils → utils}/delta.d.ts +2 -2
  145. package/dist/{plots/utils → utils}/delta.js +1 -1
  146. package/dist/utils/edgeAwareAnchor.d.ts +37 -0
  147. package/dist/utils/edgeAwareAnchor.js +65 -0
  148. package/dist/utils/grouping.d.ts +1 -1
  149. package/dist/utils/interpolate.d.ts +1 -1
  150. package/package.json +3 -1
  151. package/dist/layout/geo/GeoLayout.svelte +0 -51
  152. package/dist/layout/geo/GeoLayout.svelte.d.ts +0 -24
  153. package/dist/layout/geo/resolveProjection.js +0 -48
  154. package/dist/layout/plot/GridLayout.svelte +0 -30
  155. package/dist/layout/plot/RuleLayout.svelte +0 -111
  156. package/dist/plots/bar/BarSegments.svelte +0 -69
  157. package/dist/types/plots/delta.d.ts +0 -19
  158. package/dist/types/plots/styles/common.d.ts +0 -52
  159. package/dist/types/plots/styles/geo.d.ts +0 -42
  160. /package/dist/{plots/geo → layout/coordinates}/projections/argentina.js +0 -0
  161. /package/dist/{plots/geo → layout/coordinates}/projections/fit.d.ts +0 -0
  162. /package/dist/{plots/geo → layout/coordinates}/projections/fit.js +0 -0
  163. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.d.ts +0 -0
  164. /package/dist/{plots/geo → layout/coordinates}/rotate.svelte.js +0 -0
  165. /package/dist/{plots/geo → layout/coordinates}/zoom.svelte.js +0 -0
  166. /package/dist/types/{plots/axis.js → layout/coordinates.js} +0 -0
  167. /package/dist/types/{plots/delta.js → layout/geometry.js} +0 -0
  168. /package/dist/types/{plots/scales/geo.js → layout/scales.js} +0 -0
  169. /package/dist/types/{plots/styles/common.js → layout/styles.js} +0 -0
  170. /package/dist/types/{plots/styles/geo.js → markers/all.js} +0 -0
@@ -1,31 +1,14 @@
1
1
  <script lang="ts" generics="TData extends Record<string, unknown>">
2
- import { getContext } from 'svelte';
3
- import { get } from 'svelte/store';
4
2
  import { SvelteMap } from 'svelte/reactivity';
5
- import type { Writable } from 'svelte/store';
6
3
  import { Text, usePlot } from 'svelteplot';
7
4
  import { resolveAccessor } from '../utils/accessors';
8
5
  import { getConfiguration } from '../../configuration/config.svelte';
9
6
  import { paletteColor } from '../../utils/color';
10
7
  import { declutter1D, laneAssignment } from '../utils/declutter';
11
8
  import type { Series } from '../../types/plots/data/common';
12
- import type { ValuesStyle } from '../../types/plots/styles/common';
9
+ import type { ValuesStyle } from '../../types/layout/styles';
13
10
  import type { ValueAnchor } from '../../types/plots/constants';
14
11
 
15
- // svelteplot's own type for this context isn't part of its public export
16
- // surface, but the context itself (keyed by this fixed string, set by its
17
- // `<Plot>`) is — every axis mark already reads/writes it this way to
18
- // auto-size margins around what it actually renders. Mirroring that here
19
- // is what lets `margins.right: 'auto'` (the default) grow to fit these
20
- // labels on its own, instead of the caller having to guess a fixed
21
- // `margins.right` up front.
22
- type AutoMarginStores = {
23
- autoMarginTop: Writable<Map<string, number>>;
24
- autoMarginLeft: Writable<Map<string, number>>;
25
- autoMarginRight: Writable<Map<string, number>>;
26
- autoMarginBottom: Writable<Map<string, number>>;
27
- };
28
-
29
12
  /**
30
13
  * End-point value labels for `LinePlot` — one label per series at its last
31
14
  * data point, with automatic collision resolution whenever several series
@@ -37,9 +20,12 @@
37
20
  let {
38
21
  lastPoints,
39
22
  values,
23
+ onOverflow,
40
24
  }: {
41
25
  lastPoints: { series: Series<TData>; lastRow: TData; seriesIndex: number }[];
42
26
  values: ValuesStyle | undefined;
27
+ /** Reports how far the rendered labels overflow the plot's own content box, in pixels, on each side (0 = no overflow). */
28
+ onOverflow?: (overflow: { left: number; right: number }) => void;
43
29
  } = $props();
44
30
 
45
31
  const cfg = $derived(getConfiguration());
@@ -76,54 +62,43 @@
76
62
  valAnchor === 'start' ? ('start' as const) : valAnchor === 'end' ? ('end' as const) : ('middle' as const),
77
63
  );
78
64
  const defaultLA = $derived(valAnchor === 'outside' ? ('bottom' as const) : ('middle' as const));
65
+ const fontSize = $derived(typeof font?.fontSize === 'number' ? font.fontSize : 12);
79
66
 
80
67
  const plot = usePlot();
81
68
 
82
- // ── Auto margin ──────────────────────────────────────────────────────────
83
- // Registers how far these labels (+ their leader lines) actually extend
84
- // past the plot's own content box, on whichever side they grow toward, so
85
- // `margins.{left,right}: 'auto'` (the default) sizes itself to fit them
86
- // the same mechanism svelteplot's own axis labels use, rather than a
87
- // fixed margin the caller has to guess and hard-code up front.
88
- const autoMargins = getContext<AutoMarginStores | undefined>('svelteplot/autoMargins');
89
- const marginId = `line-value-labels-${Math.random().toString(36).slice(2)}`;
69
+ // ── Overflow measurement ─────────────────────────────────────────────────
70
+ // Measures how far the rendered labels extend past the plot's own content
71
+ // box and reports it to the caller (`Plot.svelte`), which grows its own
72
+ // local margin state to fit real `getBBox()` measurement, same as the
73
+ // approach this used to register directly into svelteplot's *shared*
74
+ // margin-auto-sizing context. Reporting through a plain callback instead
75
+ // keeps this entirely local to one `<Plot>` instance: nothing here is
76
+ // shared across instances, which is what made the old approach spike
77
+ // CPU/memory into unresponsiveness when many instances mounted/unmounted
78
+ // together (e.g. a multi-story docs page → single story).
90
79
  let groupEl: SVGGElement | undefined = $state();
91
80
 
92
81
  $effect(() => {
93
- // Depend on whatever actually changes the rendered label layout
94
- // getBBox() below is a plain DOM read, not itself tracked, so without
95
- // this the effect would only ever measure once.
82
+ // getBBox() below is a plain DOM read, not itself tracked depend on
83
+ // whatever actually changes the rendered label layout so this re-runs.
96
84
  void lastPoints;
97
85
  void values;
98
86
  const el = groupEl;
99
- if (!autoMargins || !el) return;
87
+ if (!el || !onOverflow) return;
100
88
  const xRange = plot.scales.x?.fn?.range?.()?.map(Number);
101
89
  if (!xRange) return;
102
90
  const plotLeft = Math.min(xRange[0], xRange[1]);
103
91
  const plotRight = Math.max(xRange[0], xRange[1]);
104
- // getBBox() is in the <svg>'s own user-space coordinates — the same
105
- // pixel space `plot.scales.*.fn()` already produces everywhere else in
106
- // this file, so no viewport/DOM conversion is needed here.
107
92
  let box: { x: number; width: number };
108
93
  try {
109
94
  box = el.getBBox();
110
95
  } catch {
111
96
  return;
112
97
  }
113
- const overflowRight = Math.ceil(Math.max(0, box.x + box.width - plotRight));
114
- const overflowLeft = Math.ceil(Math.max(0, plotLeft - box.x));
115
- const rightMargins = get(autoMargins.autoMarginRight);
116
- const leftMargins = get(autoMargins.autoMarginLeft);
117
- if (rightMargins.get(marginId) !== overflowRight) rightMargins.set(marginId, overflowRight);
118
- if (leftMargins.get(marginId) !== overflowLeft) leftMargins.set(marginId, overflowLeft);
119
- });
120
-
121
- $effect(() => {
122
- return () => {
123
- if (!autoMargins) return;
124
- get(autoMargins.autoMarginRight).delete(marginId);
125
- get(autoMargins.autoMarginLeft).delete(marginId);
126
- };
98
+ onOverflow({
99
+ left: Math.ceil(Math.max(0, plotLeft - box.x)),
100
+ right: Math.ceil(Math.max(0, box.x + box.width - plotRight)),
101
+ });
127
102
  });
128
103
 
129
104
  /**
@@ -151,7 +126,6 @@
151
126
  if (lastPoints.length < 2) return map;
152
127
  const yScale = plot.scales.y?.fn;
153
128
  if (!yScale) return map;
154
- const fontSize = font?.fontSize ?? 12;
155
129
  const minGap = Math.max(16, fontSize * 1.4);
156
130
  const items = lastPoints.map(({ series: s, lastRow }) => ({
157
131
  name: s.name,
@@ -192,6 +166,9 @@
192
166
  {@const labelDx = offset?.inGroup
193
167
  ? dir * (elbowGap + (numLanes - 1) * laneStep + labelGap)
194
168
  : baseDx}
169
+ {@const text = fmtVal(Number(yFn(lastRow)), s.name)}
170
+ {@const preferredTA = resolvedTextAnchor ?? defaultTA}
171
+ {@const preferredLA = font?.lineAnchor ?? defaultLA}
195
172
  {#if offset?.inGroup && plot.scales.x?.fn && plot.scales.y?.fn}
196
173
  {@const px = Number(plot.scales.x.fn(xFn(lastRow)))}
197
174
  {@const py = Number(plot.scales.y.fn(Number(yFn(lastRow))))}
@@ -212,17 +189,17 @@
212
189
  data={[lastRow]}
213
190
  x={xFn}
214
191
  y={yFn}
215
- text={() => fmtVal(Number(yFn(lastRow)), s.name)}
192
+ text={() => text}
216
193
  dx={labelDx}
217
194
  dy={(font?.dy ?? defaultDy) + extraDy}
218
- textAnchor={resolvedTextAnchor ?? defaultTA}
219
- lineAnchor={font?.lineAnchor ?? defaultLA}
195
+ textAnchor={preferredTA}
196
+ lineAnchor={preferredLA}
220
197
  lineHeight={font?.lineHeight}
221
198
  rotate={font?.rotate}
222
199
  class={font?.class}
223
200
  textClass={font?.textClass}
224
201
  fill={font?.fill ?? seriesColor}
225
- fontSize={font?.fontSize ?? 12}
202
+ {fontSize}
226
203
  fontWeight={font?.fontWeight}
227
204
  fontStyle={font?.fontStyle}
228
205
  stroke={font?.stroke}
@@ -1,5 +1,5 @@
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
  declare function $$render<TData extends Record<string, unknown>>(): {
4
4
  props: {
5
5
  lastPoints: {
@@ -8,6 +8,11 @@ declare function $$render<TData extends Record<string, unknown>>(): {
8
8
  seriesIndex: number;
9
9
  }[];
10
10
  values: ValuesStyle | undefined;
11
+ /** Reports how far the rendered labels overflow the plot's own content box, in pixels, on each side (0 = no overflow). */
12
+ onOverflow?: (overflow: {
13
+ left: number;
14
+ right: number;
15
+ }) => void;
11
16
  };
12
17
  exports: {};
13
18
  bindings: "";
@@ -0,0 +1,15 @@
1
+ import type { Series } from '../../types/plots/data/common';
2
+ import type { LineSegmentStyle } from '../../types/plots/segments/common';
3
+ import type { VisualGroup } from '../../types/plots/segments/config';
4
+ import type { StrokeStyle, DotStyle } from '../../types/plots/styling';
5
+ import type { LineMarkerConfig, DotMarkerConfig } from '../../types/markers/common';
6
+ /**
7
+ * Every `'line'` (+ `'dot'`, where a segment's style asks for them) marker
8
+ * for one LinePlot render: one `'line'` marker per visual segment, plus a
9
+ * `'dot'` marker right after it when that segment sets `style.dots`.
10
+ */
11
+ export declare function buildLineMarkers<TData extends Record<string, unknown>>(args: {
12
+ groupedSeries: VisualGroup<Series<TData>, LineSegmentStyle>[];
13
+ disabledStrokeFor: (seriesName: string) => StrokeStyle | undefined;
14
+ disabledDotsFor: (seriesName: string) => DotStyle | undefined;
15
+ }): (LineMarkerConfig | DotMarkerConfig)[];
@@ -0,0 +1,57 @@
1
+ import { resolveAccessor } from '../utils/accessors';
2
+ import { paletteColor } from '../../utils/color';
3
+ import { getConfiguration } from '../../configuration/config.svelte';
4
+ /**
5
+ * Every `'line'` (+ `'dot'`, where a segment's style asks for them) marker
6
+ * for one LinePlot render: one `'line'` marker per visual segment, plus a
7
+ * `'dot'` marker right after it when that segment sets `style.dots`.
8
+ */
9
+ export function buildLineMarkers(args) {
10
+ const { groupedSeries, disabledStrokeFor, disabledDotsFor } = args;
11
+ const cfg = getConfiguration();
12
+ const markers = [];
13
+ groupedSeries.forEach((group, seriesIndex) => {
14
+ const defaultColor = paletteColor(seriesIndex, cfg.palette);
15
+ const disabledStroke = disabledStrokeFor(group.series.name);
16
+ const disabledDots = disabledDotsFor(group.series.name);
17
+ const xFn = resolveAccessor(group.series.x);
18
+ const yFn = resolveAccessor(group.series.y);
19
+ for (const seg of group.visualSegments) {
20
+ const strokeStyle = seg.style.stroke;
21
+ const dotStyle = seg.style.dots;
22
+ markers.push({
23
+ type: 'line',
24
+ data: seg.data,
25
+ x: xFn,
26
+ y: yFn,
27
+ style: {
28
+ stroke: disabledStroke?.stroke ?? strokeStyle?.stroke ?? defaultColor,
29
+ strokeWidth: disabledStroke?.strokeWidth ?? strokeStyle?.strokeWidth ?? cfg.line.strokeWidth,
30
+ strokeOpacity: disabledStroke?.strokeOpacity ?? strokeStyle?.strokeOpacity ?? 1,
31
+ strokeDasharray: disabledStroke?.strokeDasharray ?? strokeStyle?.strokeDasharray,
32
+ strokeLinejoin: disabledStroke?.strokeLinejoin ?? strokeStyle?.strokeLinejoin,
33
+ strokeLinecap: disabledStroke?.strokeLinecap ?? strokeStyle?.strokeLinecap,
34
+ },
35
+ });
36
+ if (!dotStyle)
37
+ continue;
38
+ markers.push({
39
+ type: 'dot',
40
+ data: seg.data,
41
+ x: xFn,
42
+ y: yFn,
43
+ style: {
44
+ dotRadius: disabledDots?.dotRadius ?? dotStyle.dotRadius ?? cfg.line.dotRadius,
45
+ dotFill: disabledDots?.dotFill ?? dotStyle.dotFill ?? strokeStyle?.stroke ?? defaultColor,
46
+ dotSymbol: disabledDots?.dotSymbol ?? dotStyle.dotSymbol ?? 'circle',
47
+ dotStroke: disabledDots?.dotStroke ?? dotStyle.dotStroke ?? strokeStyle?.stroke ?? defaultColor,
48
+ dotStrokeWidth: disabledDots?.dotStrokeWidth ?? dotStyle.dotStrokeWidth ?? 1,
49
+ dotFillOpacity: disabledDots?.dotFillOpacity ?? dotStyle.dotFillOpacity ?? strokeStyle?.strokeOpacity ?? 1,
50
+ dotStrokeOpacity: disabledDots?.dotStrokeOpacity ?? dotStyle.dotStrokeOpacity ?? strokeStyle?.strokeOpacity ?? 1,
51
+ dotStrokeDasharray: disabledDots?.dotStrokeDasharray ?? dotStyle.dotStrokeDasharray,
52
+ },
53
+ });
54
+ }
55
+ });
56
+ return markers;
57
+ }
@@ -1,28 +1,22 @@
1
1
  <script lang="ts" generics="TData extends Record<string, unknown>">
2
- import { SvelteMap } from 'svelte/reactivity';
3
- import { BarX } from 'svelteplot';
4
2
  import { resolveAccessor } from '../utils/accessors';
5
3
  import { getConfiguration } from '../../configuration/config.svelte';
6
4
  import { paletteColor } from '../../utils/color';
7
5
  import { buildSeries } from '../../utils/grouping';
8
6
  import { getLegendInteraction } from '../../layout/legend/interaction.svelte';
9
7
  import { disabledFillOverride } from '../utils/legendDisabled';
10
- import { buildGroupedSeries, validateSegments, resolveSegmentsForSeries, matchesSegmentX, getDataForSegments } from '../utils/segments';
8
+ import { buildPyramidBarMarkers } from './buildPyramidBarMarkers';
9
+ import { buildGroupedSeries, validateSegments, buildScopedMarkerGroups } from '../utils/segments';
11
10
  import BasePlotLayout from '../../layout/plot/BasePlotLayout.svelte';
12
- import AxisLayout from '../../layout/plot/AxisLayout.svelte';
13
- import GridLayout from '../../layout/plot/GridLayout.svelte';
14
- import RuleLayout from '../../layout/plot/RuleLayout.svelte';
15
- import HoverMarker from '../../markers/HoverMarker.svelte';
16
11
  import ValueLabels from './ValueLabels.svelte';
17
12
  import { hasHoverMarker, resolveHoverStrategy, resolveHoverSync } from '../../layout/tooltip/utils';
18
13
  import type { BasePlotProps } from '../../types/plots/props';
19
- import type { AxisValue } from '../../types/plots/axis';
14
+ import type { AxisValue, AxisBasedScalesConfig } from '../../types/layout/scales';
20
15
  import type { Series, SeriesProps, DataProps } from '../../types/plots/data/common';
21
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
22
- import type { BasePlotStyles } from '../../types/plots/styles/common';
16
+ import type { BasePlotStylesConfig } from '../../types/layout/styles';
23
17
  import type { ValueAnchor } from '../../types/plots/constants';
24
18
  import type { BarSegmentStyle } from '../../types/plots/segments/common';
25
- import type { Marker } from '../../types/markers/common';
19
+ import type { AxisBasedMarkersConfig } from '../../types/markers/common';
26
20
 
27
21
  /**
28
22
  * A population-pyramid-style diverging bar chart: each series is mirrored
@@ -43,9 +37,9 @@
43
37
  type Props = BasePlotProps<
44
38
  SeriesProps<TData> | DataProps<TData>,
45
39
  TData,
46
- BasePlotStyles,
40
+ BasePlotStylesConfig,
47
41
  BarSegmentStyle,
48
- Marker,
42
+ AxisBasedMarkersConfig,
49
43
  AxisBasedScalesConfig<TData>
50
44
  >;
51
45
 
@@ -166,17 +160,6 @@
166
160
  return vals.length ? Math.max(...vals) : 1;
167
161
  });
168
162
 
169
- const seriesMarkers = $derived.by(() => {
170
- const map = new SvelteMap<string, Marker[]>();
171
- for (const marker of markers) {
172
- if ((marker.type === 'component' || marker.type === 'hover') && marker.series) {
173
- if (!map.has(marker.series)) map.set(marker.series, []);
174
- map.get(marker.series)!.push(marker);
175
- }
176
- }
177
- return map;
178
- });
179
-
180
163
  // The single, fully-resolved scales object BasePlotLayout reads for
181
164
  // everything (ticks, grid, sort, and the real svelteplot scale) — avoids
182
165
  // keeping a second, separately-defaulted copy of `scales.x`/`scales.y`
@@ -192,111 +175,52 @@
192
175
  y: { type: 'band', padding: 0.2, domain: yDomain, ...(scales.y ?? {}) },
193
176
  });
194
177
 
195
- const hoverActive = $derived(tooltip != null || hasHoverMarker(markers));
196
- const hoverStrategy = $derived(resolveHoverStrategy(tooltip?.strategy, markers, 'y'));
197
- const hoverSync = $derived(resolveHoverSync(tooltip?.sync, markers));
178
+ const hover = $derived({
179
+ active: tooltip != null || hasHoverMarker(markers),
180
+ points: hoverPoints,
181
+ label: labelFor,
182
+ strategy: resolveHoverStrategy(tooltip?.strategy, markers, 'y'),
183
+ sync: resolveHoverSync(tooltip?.sync, markers),
184
+ tooltip,
185
+ });
186
+
187
+ const pyramidMarkers = $derived(
188
+ buildPyramidBarMarkers({
189
+ groupedSeries,
190
+ valueFor: signedValue,
191
+ colorFor: (s) => (s.side === 'left' ? leftColor : rightColor),
192
+ disabledFillFor: (name) => disabledFillOverride(legendInteraction?.disabledSeries.get(name)),
193
+ }),
194
+ );
195
+
196
+ const scopedGroups = $derived(
197
+ buildScopedMarkerGroups<TData, InternalSeries, BarSegmentStyle>({
198
+ groupedSeries,
199
+ segments,
200
+ colorFor: (series) => (series.side === 'left' ? leftColor : rightColor),
201
+ segmentColorFor: (style, defaultColor) => style?.fill ?? defaultColor,
202
+ }),
203
+ );
198
204
  </script>
199
205
 
200
206
  <BasePlotLayout
201
207
  {width}
202
208
  {height}
209
+ {styles}
203
210
  data={flat}
204
211
  {getX}
205
212
  {getY}
206
- {hoverPoints}
207
- label={labelFor}
208
- {tooltip}
209
- {hoverActive}
210
- {hoverStrategy}
211
- {hoverSync}
213
+ {hover}
212
214
  scales={resolvedScales}
213
215
  {margins}
214
216
  xTickRotate={resolvedScales?.x?.tickRotate}
217
+ markers={[...pyramidMarkers, ...markers]}
218
+ seriesData={resolvedSeries}
219
+ {scopedGroups}
215
220
  >
216
- {#snippet children({ matchedPoints, ruleX, ruleY, impliesRuleX, impliesRuleY, numericMargins })}
217
- <AxisLayout data={flat} {getX} {getY} {width} {height} {numericMargins} scales={resolvedScales} />
218
- <GridLayout scales={resolvedScales} />
219
- <RuleLayout {markers} seriesData={resolvedSeries} {matchedPoints} {ruleX} {ruleY} {impliesRuleX} {impliesRuleY}>
221
+ {#if showVals}
220
222
  {#each groupedSeries as group (group.series.name)}
221
- {@const defaultColor = group.series.side === 'left' ? leftColor : rightColor}
222
- {@const getCategory = resolveAccessor(group.series.x)}
223
- {@const disabledFill = disabledFillOverride(legendInteraction?.disabledSeries.get(group.series.name))}
224
- {#each group.visualSegments as seg, i (`${group.series.name}-${i}`)}
225
- <BarX
226
- data={seg.data}
227
- x1={0}
228
- x2={(d: TData) => signedValue(group.series, d)}
229
- y={getCategory}
230
- fill={disabledFill?.fill ?? seg.style.fill ?? defaultColor}
231
- fillOpacity={disabledFill?.fillOpacity ?? seg.style.fillOpacity ?? 1}
232
- />
233
- {/each}
234
-
235
- {#if showVals}
236
- <ValueLabels {group} {valAnchor} {fmtVal} values={styles.values} axisColor={cfg.axis.color} />
237
- {/if}
238
-
239
- {#each seriesMarkers.get(group.series.name) ?? [] as marker, i (`series-${group.series.name}-${marker.type}-${i}`)}
240
- {#if marker.type === 'component'}
241
- {#if marker.scope === 'segment'}
242
- {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
243
- {@const segmentDataArrays = getDataForSegments(group.series.data, getCategory, segs)}
244
- {#each segmentDataArrays as segmentData, segIdx (`${group.series.name}-marker-segment-${segIdx}`)}
245
- {#if segmentData.length > 0}
246
- {@const segmentColor = segs[segIdx]?.style?.fill ?? defaultColor}
247
- <marker.component
248
- {...marker.props}
249
- series={group.series.name}
250
- data={segmentData}
251
- seriesColor={segmentColor}
252
- />
253
- {/if}
254
- {/each}
255
- {:else}
256
- {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
257
- {@const seriesColor = segs[0]?.style?.fill ?? defaultColor}
258
- <marker.component
259
- {...marker.props}
260
- series={group.series.name}
261
- data={group.series.data}
262
- seriesColor={seriesColor}
263
- />
264
- {/if}
265
- {:else if marker.type === 'hover'}
266
- {@const seriesPoints = matchedPoints.filter((p) => p.series === group.series.name)}
267
- {#if marker.scope === 'segment'}
268
- {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
269
- {#each segs as seg, segIdx (`${group.series.name}-hover-segment-${segIdx}`)}
270
- {@const segPoints = seriesPoints.filter((p) => matchesSegmentX(p.x, seg))}
271
- {#if segPoints.length > 0}
272
- <HoverMarker
273
- data={segPoints}
274
- ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
275
- ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
276
- showLabels={marker.showLabels}
277
- ruleStyle={marker.ruleStyle}
278
- dotStyle={marker.dotStyle}
279
- fontStyle={marker.fontStyle}
280
- seriesColor={seg.style?.fill ?? defaultColor}
281
- />
282
- {/if}
283
- {/each}
284
- {:else}
285
- {@const segs = resolveSegmentsForSeries(segments, group.series.name)}
286
- <HoverMarker
287
- data={seriesPoints}
288
- ruleX={marker.ruleX !== false && (impliesRuleX || marker.ruleX === true) ? ruleX : null}
289
- ruleY={marker.ruleY !== false && (impliesRuleY || marker.ruleY === true) ? ruleY : null}
290
- showLabels={marker.showLabels}
291
- ruleStyle={marker.ruleStyle}
292
- dotStyle={marker.dotStyle}
293
- fontStyle={marker.fontStyle}
294
- seriesColor={segs[0]?.style?.fill ?? defaultColor}
295
- />
296
- {/if}
297
- {/if}
298
- {/each}
223
+ <ValueLabels {group} {valAnchor} {fmtVal} values={styles.values} axisColor={cfg.axis.color} />
299
224
  {/each}
300
- </RuleLayout>
301
- {/snippet}
225
+ {/if}
302
226
  </BasePlotLayout>
@@ -1,11 +1,10 @@
1
1
  import type { BasePlotProps } from '../../types/plots/props';
2
+ import type { AxisBasedScalesConfig } from '../../types/layout/scales';
2
3
  import type { SeriesProps, DataProps } from '../../types/plots/data/common';
3
- import type { AxisBasedScalesConfig } from '../../types/plots/props';
4
- import type { BasePlotStyles } from '../../types/plots/styles/common';
5
- import type { BarSegmentStyle } from '../../types/plots/segments/common';
6
- import type { Marker } from '../../types/markers/common';
4
+ import type { BasePlotStylesConfig } from '../../types/layout/styles';
5
+ import type { AxisBasedMarkersConfig } from '../../types/markers/common';
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: {};
@@ -2,7 +2,7 @@
2
2
  import { Text } from 'svelteplot';
3
3
  import { resolveAccessor } from '../utils/accessors';
4
4
  import type { Series } from '../../types/plots/data/common';
5
- import type { ValuesStyle } from '../../types/plots/styles/common';
5
+ import type { ValuesStyle } from '../../types/layout/styles';
6
6
  import type { ValueAnchor } from '../../types/plots/constants';
7
7
  import type { BarSegmentStyle } from '../../types/plots/segments/common';
8
8
  import type { VisualGroup } from '../../types/plots/segments/config';
@@ -1,5 +1,5 @@
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';
@@ -0,0 +1,19 @@
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 { BarMarkerConfig } from '../../types/markers/common';
5
+ /**
6
+ * Every `'bar'` marker for one PyramidPlot render — reuses `BarMarkerConfig`
7
+ * directly, always horizontal (category on y, signed magnitude on x) and
8
+ * with no insets. Emits no `role: 'hitArea'` markers, since PyramidPlot's
9
+ * hover matches nearest points rather than hit-testing category bands.
10
+ */
11
+ export declare function buildPyramidBarMarkers<TData extends Record<string, unknown>, S extends Series<TData>>(args: {
12
+ groupedSeries: VisualGroup<S, BarSegmentStyle>[];
13
+ valueFor: (series: S, d: TData) => number;
14
+ colorFor: (series: S) => string;
15
+ disabledFillFor: (seriesName: string) => {
16
+ fill?: string;
17
+ fillOpacity?: number;
18
+ } | undefined;
19
+ }): BarMarkerConfig[];
@@ -0,0 +1,32 @@
1
+ import { resolveAccessor } from '../utils/accessors';
2
+ /**
3
+ * Every `'bar'` marker for one PyramidPlot render — reuses `BarMarkerConfig`
4
+ * directly, always horizontal (category on y, signed magnitude on x) and
5
+ * with no insets. Emits no `role: 'hitArea'` markers, since PyramidPlot's
6
+ * hover matches nearest points rather than hit-testing category bands.
7
+ */
8
+ export function buildPyramidBarMarkers(args) {
9
+ const { groupedSeries, valueFor, colorFor, disabledFillFor } = args;
10
+ const markers = [];
11
+ for (const group of groupedSeries) {
12
+ const getCategory = resolveAccessor(group.series.x);
13
+ const defaultColor = colorFor(group.series);
14
+ const disabledFill = disabledFillFor(group.series.name);
15
+ for (const seg of group.visualSegments) {
16
+ markers.push({
17
+ type: 'bar',
18
+ role: 'segment',
19
+ isHorizontal: true,
20
+ data: seg.data,
21
+ y: getCategory,
22
+ x1: 0,
23
+ x2: (d) => valueFor(group.series, d),
24
+ style: {
25
+ fill: disabledFill?.fill ?? seg.style.fill ?? defaultColor,
26
+ fillOpacity: disabledFill?.fillOpacity ?? seg.style.fillOpacity ?? 1,
27
+ },
28
+ });
29
+ }
30
+ }
31
+ return markers;
32
+ }