@fundar/data-chart-telling 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/configuration/ConfigVars.svelte +14 -0
  3. package/dist/configuration/ConfigVars.svelte.d.ts +3 -0
  4. package/dist/configuration/config.svelte.d.ts +5 -0
  5. package/dist/configuration/config.svelte.js +17 -4
  6. package/dist/configuration/themes/dark.js +1 -1
  7. package/dist/configuration/themes/editorial.js +1 -1
  8. package/dist/index.d.ts +6 -3
  9. package/dist/index.js +2 -1
  10. package/dist/layout/base/BasePlotLayout.svelte +1 -1
  11. package/dist/layout/figure/Figure.svelte +5 -12
  12. package/dist/layout/figure/FigureChrome.svelte +2 -10
  13. package/dist/layout/geometry/GeometryLayout.svelte +42 -1
  14. package/dist/layout/hover/HoverSettings.d.ts +3 -1
  15. package/dist/layout/hover/HoverSettings.js +4 -2
  16. package/dist/layout/legend/DisabledStyle.d.ts +1 -1
  17. package/dist/layout/legend/DisabledStyle.js +1 -1
  18. package/dist/layout/legend/DiscreteBadgeItem.svelte +1 -1
  19. package/dist/layout/legend/DiscreteSection.svelte +1 -1
  20. package/dist/layout/legend/LegendState.svelte.d.ts +12 -2
  21. package/dist/layout/legend/LegendState.svelte.js +18 -4
  22. package/dist/layout/legend/LegendText.d.ts +4 -1
  23. package/dist/layout/legend/LegendText.js +7 -1
  24. package/dist/layout/margins/PlotMargins.svelte.d.ts +2 -2
  25. package/dist/layout/margins/PlotMargins.svelte.js +2 -2
  26. package/dist/layout/plot/Plot.svelte +4 -2
  27. package/dist/layout/plot/Plot.svelte.d.ts +2 -1
  28. package/dist/layout/scales/ScalesLayout.svelte +1 -1
  29. package/dist/layout/styles/ValueLabels.d.ts +33 -2
  30. package/dist/layout/styles/ValueLabels.js +67 -8
  31. package/dist/layout/timeline/Timeline.svelte +5 -1
  32. package/dist/markers/area/AreaMarker.svelte +3 -0
  33. package/dist/markers/arrow/ArrowMarker.svelte +9 -5
  34. package/dist/markers/bar/BarMarker.svelte +45 -48
  35. package/dist/markers/bollinger/BollingerMarker.svelte +3 -0
  36. package/dist/markers/box/BoxMarker.svelte +3 -1
  37. package/dist/markers/cell/CellMarker.svelte +11 -7
  38. package/dist/markers/contour/ContourMarker.svelte +3 -0
  39. package/dist/markers/delaunay/DelaunayLinkMarker.svelte +9 -5
  40. package/dist/markers/delaunay/DelaunayMeshMarker.svelte +3 -0
  41. package/dist/markers/density/DensityMarker.svelte +3 -0
  42. package/dist/markers/difference/DifferenceMarker.svelte +3 -0
  43. package/dist/markers/dot/DotMarker.svelte +11 -6
  44. package/dist/markers/geo/GraticuleMarker.svelte +3 -0
  45. package/dist/markers/geo/SphereMarker.svelte +3 -0
  46. package/dist/markers/hover/HoverMarker.svelte +22 -6
  47. package/dist/markers/hull/HullMarker.svelte +3 -0
  48. package/dist/markers/image/ImageMarker.svelte +3 -0
  49. package/dist/markers/line/LineMarker.svelte +3 -0
  50. package/dist/markers/link/LinkMarker.svelte +3 -0
  51. package/dist/markers/raster/RasterMarker.svelte +3 -0
  52. package/dist/markers/rect/RectMarker.svelte +25 -17
  53. package/dist/markers/regression/RegressionMarker.svelte +3 -0
  54. package/dist/markers/rule/RuleMarker.svelte +15 -5
  55. package/dist/markers/text/TextFit.d.ts +44 -0
  56. package/dist/markers/text/TextFit.js +86 -0
  57. package/dist/markers/text/TextMarker.svelte +186 -90
  58. package/dist/markers/text/TextMarker.svelte.d.ts +2 -0
  59. package/dist/markers/tick/TickMarker.svelte +9 -3
  60. package/dist/markers/trail/TrailMarker.svelte +3 -0
  61. package/dist/markers/utils/BoxLabel.d.ts +58 -0
  62. package/dist/markers/utils/BoxLabel.js +175 -0
  63. package/dist/markers/utils/MarkerEvents.d.ts +23 -0
  64. package/dist/markers/utils/MarkerEvents.js +44 -0
  65. package/dist/markers/utils/MarkerLabel.svelte +33 -0
  66. package/dist/markers/utils/MarkerLabel.svelte.d.ts +10 -0
  67. package/dist/markers/utils/RowStroke.d.ts +9 -21
  68. package/dist/markers/utils/RowStroke.js +10 -21
  69. package/dist/markers/utils/RowStyle.d.ts +26 -0
  70. package/dist/markers/utils/RowStyle.js +55 -0
  71. package/dist/markers/vector/VectorMarker.svelte +3 -0
  72. package/dist/markers/voronoi/VoronoiMarker.svelte +9 -5
  73. package/dist/markers/voronoi/VoronoiMeshMarker.svelte +3 -0
  74. package/dist/markers/waffle/WaffleMarker.svelte +9 -5
  75. package/dist/phases/Pipeline.d.ts +3 -1
  76. package/dist/phases/Pipeline.js +1 -0
  77. package/dist/phases/data/DataPhase.js +51 -10
  78. package/dist/phases/hover/HoverPhase.js +9 -1
  79. package/dist/phases/mapping/MappingPhase.js +2 -1
  80. package/dist/phases/markers/MarkerStyles.d.ts +18 -0
  81. package/dist/phases/markers/MarkerStyles.js +111 -0
  82. package/dist/phases/markers/MarkersPhase.d.ts +1 -1
  83. package/dist/phases/markers/MarkersPhase.js +7 -5
  84. package/dist/phases/statistics/StatisticsPhase.js +1 -1
  85. package/dist/phases/transforms/Transforms.js +3 -1
  86. package/dist/phases/transforms/TransformsPhase.js +58 -4
  87. package/dist/phases/transforms/methods/TreemapTransform.d.ts +28 -0
  88. package/dist/phases/transforms/methods/TreemapTransform.js +119 -0
  89. package/dist/plots/correlation/heatmap/defaults.js +2 -2
  90. package/dist/plots/correlation/heatmap/markers.d.ts +0 -8
  91. package/dist/plots/correlation/heatmap/markers.js +8 -35
  92. package/dist/plots/correlation/scatter/markers.d.ts +1 -1
  93. package/dist/plots/correlation/scatter/markers.js +2 -2
  94. package/dist/plots/distribution/histogram/defaults.js +2 -2
  95. package/dist/plots/distribution/histogram/markers.d.ts +1 -10
  96. package/dist/plots/distribution/histogram/markers.js +17 -59
  97. package/dist/plots/distribution/pyramid/defaults.js +3 -3
  98. package/dist/plots/distribution/pyramid/markers.d.ts +0 -10
  99. package/dist/plots/distribution/pyramid/markers.js +13 -64
  100. package/dist/plots/distribution/violin/markers.js +1 -1
  101. package/dist/plots/map/geo/phases/markers.js +3 -2
  102. package/dist/plots/part-of-a-whole/treemap/Plot.svelte +47 -0
  103. package/dist/plots/part-of-a-whole/treemap/Plot.svelte.d.ts +26 -0
  104. package/dist/plots/part-of-a-whole/treemap/defaults.d.ts +10 -0
  105. package/dist/plots/part-of-a-whole/treemap/defaults.js +167 -0
  106. package/dist/plots/ranking/bar/defaults.js +2 -2
  107. package/dist/plots/ranking/bar/markers.d.ts +1 -11
  108. package/dist/plots/ranking/bar/markers.js +38 -80
  109. package/dist/plots/ranking/bullet/markers.js +2 -3
  110. package/dist/plots/ranking/cleveland/ClevelandMarkers.js +17 -13
  111. package/dist/types/configuration/styling.d.ts +27 -2
  112. package/dist/types/layout/legend.d.ts +8 -1
  113. package/dist/types/layout/mapping.d.ts +12 -3
  114. package/dist/types/layout/scales.d.ts +6 -0
  115. package/dist/types/layout/tooltip.d.ts +16 -4
  116. package/dist/types/markers/axis/arrow.d.ts +2 -8
  117. package/dist/types/markers/axis/bar.d.ts +4 -8
  118. package/dist/types/markers/axis/cell.d.ts +5 -5
  119. package/dist/types/markers/axis/delaunayLink.d.ts +2 -2
  120. package/dist/types/markers/axis/rect.d.ts +4 -8
  121. package/dist/types/markers/axis/rule.d.ts +2 -16
  122. package/dist/types/markers/axis/tick.d.ts +2 -2
  123. package/dist/types/markers/axis/voronoi.d.ts +2 -2
  124. package/dist/types/markers/axis/waffle.d.ts +4 -3
  125. package/dist/types/markers/basic/dot.d.ts +2 -2
  126. package/dist/types/markers/basic/text.d.ts +56 -14
  127. package/dist/types/markers/channels/commonMarkerConfig.d.ts +4 -1
  128. package/dist/types/markers/channels/commonMarkerConfigWithArea.d.ts +1 -2
  129. package/dist/types/markers/channels/commonMarkerConfigWithLabel.d.ts +10 -0
  130. package/dist/types/markers/channels/commonMarkerConfigWithLabel.js +1 -0
  131. package/dist/types/markers/channels/commonMarkerEvents.d.ts +17 -0
  132. package/dist/types/markers/channels/commonMarkerEvents.js +13 -0
  133. package/dist/types/markers/geo/contour.d.ts +2 -1
  134. package/dist/types/markers/geo/graticule.d.ts +2 -1
  135. package/dist/types/markers/geo/link.d.ts +2 -1
  136. package/dist/types/markers/geo/sphere.d.ts +2 -1
  137. package/dist/types/markers/geo/vector.d.ts +2 -1
  138. package/dist/types/phases/data/phase.d.ts +5 -0
  139. package/dist/types/phases/hover/phase.d.ts +2 -0
  140. package/dist/types/phases/state.d.ts +7 -0
  141. package/dist/types/phases/statistics/phase.d.ts +2 -1
  142. package/dist/types/phases/transforms/phase.d.ts +11 -0
  143. package/dist/types/plots/constants.d.ts +14 -14
  144. package/dist/types/plots/correlation/heatmap/props.d.ts +3 -3
  145. package/dist/types/plots/correlation/scatter/props.d.ts +3 -3
  146. package/dist/types/plots/data/common.d.ts +19 -6
  147. package/dist/types/plots/data/geo.d.ts +1 -1
  148. package/dist/types/plots/distribution/box/props.d.ts +3 -3
  149. package/dist/types/plots/distribution/density/props.d.ts +3 -11
  150. package/dist/types/plots/distribution/histogram/props.d.ts +3 -11
  151. package/dist/types/plots/distribution/pyramid/props.d.ts +3 -3
  152. package/dist/types/plots/distribution/violin/props.d.ts +3 -3
  153. package/dist/types/plots/evolution/area/props.d.ts +3 -3
  154. package/dist/types/plots/evolution/line/props.d.ts +3 -3
  155. package/dist/types/plots/part-of-a-whole/treemap/props.d.ts +19 -0
  156. package/dist/types/plots/part-of-a-whole/treemap/props.js +1 -0
  157. package/dist/types/plots/part-of-a-whole/treemap/state.d.ts +6 -0
  158. package/dist/types/plots/part-of-a-whole/treemap/state.js +1 -0
  159. package/dist/types/plots/props.d.ts +7 -4
  160. package/dist/types/plots/ranking/bar/props.d.ts +3 -3
  161. package/dist/types/plots/ranking/cleveland/props.d.ts +3 -3
  162. package/dist/types/plots/ranking/dumbbell/props.d.ts +3 -3
  163. package/dist/types/plots/ranking/forest/props.d.ts +3 -3
  164. package/dist/types/plots/ranking/lollipop/props.d.ts +3 -3
  165. package/dist/types/plots/styling.d.ts +24 -0
  166. package/dist/types/transforms/index.d.ts +3 -1
  167. package/dist/types/transforms/treemap.d.ts +32 -0
  168. package/dist/types/transforms/treemap.js +5 -0
  169. package/dist/utils/color/ColorTones.d.ts +19 -0
  170. package/dist/utils/color/ColorTones.js +64 -0
  171. package/dist/utils/color/CssColor.d.ts +14 -5
  172. package/dist/utils/color/CssColor.js +69 -27
  173. package/dist/utils/color/index.d.ts +4 -0
  174. package/dist/utils/color/index.js +4 -0
  175. package/dist/utils/grouping.d.ts +18 -1
  176. package/dist/utils/grouping.js +31 -0
  177. package/docs/concepts.md +3 -2
  178. package/docs/legend.md +8 -5
  179. package/docs/markers.md +76 -26
  180. package/docs/plots/part-of-a-whole/treemap.md +69 -0
  181. package/docs/plots/ranking/bar.md +1 -1
  182. package/docs/plots.md +21 -20
  183. package/docs/scales.md +3 -0
  184. package/docs/state.md +1 -1
  185. package/docs/styles-and-segments.md +19 -18
  186. package/docs/theming.md +4 -11
  187. package/docs/tooltip-and-hover.md +1 -1
  188. package/docs/transforms-and-statistics.md +17 -16
  189. package/examples/Treemap.svelte +13 -0
  190. package/examples/datasets/economic-activity.ts +40 -0
  191. package/examples/datasets/index.ts +1 -0
  192. package/llms-full.txt +5 -5
  193. package/llms.txt +2 -0
  194. package/package.json +3 -1
@@ -1,4 +1,6 @@
1
1
  <script lang="ts" generics="TData = Record<string, unknown>">
2
+ import { RowStyle } from '../utils/RowStyle';
3
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
4
  import { Dot } from 'svelteplot';
3
5
  import { MarkerChannels } from '../utils/MarkerChannels';
4
6
  import { getConfiguration } from '../../configuration/config.svelte';
@@ -12,12 +14,12 @@
12
14
  * `DeltaMarker` that place a dot at a computed point.
13
15
  */
14
16
  const { marker }: { marker: DotMarkerConfig } = $props();
17
+ const events = MarkerEvents.use(() => marker);
15
18
 
16
19
  const data = $derived((marker.data ?? []) as TData[]);
17
20
  const x = $derived(marker.x);
18
21
  const y = $derived(marker.y);
19
22
  const rProp = $derived(marker.r);
20
- const fillProp = $derived(marker.fill);
21
23
  const color = $derived(marker.color ? resolveAccessor(marker.color) : undefined);
22
24
  const opacityProp = $derived(marker.opacity ? resolveAccessor(marker.opacity) : undefined);
23
25
  const symbolProp = $derived(marker.symbol ? resolveAccessor(marker.symbol) : undefined);
@@ -61,12 +63,14 @@
61
63
  * `'none'` is handed over as unset, which svelteplot draws as no paint.
62
64
  */
63
65
  const paint = <T,>(value: T): T | undefined => (value === 'none' ? undefined : value);
64
- const fill = $derived(paint(resolvedFill ?? fillProp ?? styles?.dotFill ?? 'currentColor'));
65
- const flatFillOpacity = $derived(styles?.dotFillOpacity ?? 1);
66
+ const fill = $derived(
67
+ paint(resolvedFill ?? RowStyle.of(styles?.dotFill, displayData) ?? 'currentColor')
68
+ );
69
+ const flatFillOpacity = $derived(RowStyle.opacity(styles?.dotFillOpacity) ?? 1);
66
70
  const symbol = $derived(resolvedSymbolFn ?? styles?.dotSymbol ?? 'circle');
67
- const stroke = $derived(paint(styles?.dotStroke ?? 'currentColor'));
68
- const strokeWidth = $derived(styles?.dotStrokeWidth ?? 1);
69
- const strokeOpacity = $derived(styles?.dotStrokeOpacity ?? 1);
71
+ const stroke = $derived(paint(RowStyle.of(styles?.dotStroke, displayData) ?? 'currentColor'));
72
+ const strokeWidth = $derived(RowStyle.field(styles?.dotStrokeWidth) ?? 1);
73
+ const strokeOpacity = $derived(RowStyle.opacity(styles?.dotStrokeOpacity) ?? 1);
70
74
  const strokeDasharray = $derived(styles?.dotStrokeDasharray);
71
75
  </script>
72
76
 
@@ -74,6 +78,7 @@
74
78
  <RowOffsetMarks {marker} {rows}>
75
79
  {#snippet mark(own)}
76
80
  <Dot
81
+ {...events.props}
77
82
  data={own}
78
83
  x={x ?? 0}
79
84
  y={y ?? 0}
@@ -1,12 +1,15 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Graticule } from 'svelteplot';
3
4
  import type { GeoGraticuleMarkerConfig } from '../../types/markers/geo';
4
5
 
5
6
  /** Renders one `'graticule'` marker - longitude/latitude gridlines. */
6
7
  let { marker }: { marker: GeoGraticuleMarkerConfig } = $props();
8
+ const events = MarkerEvents.use(() => marker);
7
9
  </script>
8
10
 
9
11
  <Graticule
12
+ {...events.props}
10
13
  step={marker.step}
11
14
  stepX={marker.stepX}
12
15
  stepY={marker.stepY}
@@ -1,12 +1,15 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Sphere } from 'svelteplot';
3
4
  import type { GeoSphereMarkerConfig } from '../../types/markers/geo';
4
5
 
5
6
  /** Renders one `'sphere'` marker - the outer globe outline. */
6
7
  let { marker }: { marker: GeoSphereMarkerConfig } = $props();
8
+ const events = MarkerEvents.use(() => marker);
7
9
  </script>
8
10
 
9
11
  <Sphere
12
+ {...events.props}
10
13
  fill={marker.styles?.fill}
11
14
  fillOpacity={marker.styles?.fillOpacity}
12
15
  stroke={marker.styles?.stroke ?? 'currentColor'}
@@ -114,6 +114,18 @@
114
114
  // areaX/areaY have no implied default - always off unless explicitly set.
115
115
  const areaX = $derived(marker.areaX === true ? (activePoint?.x ?? null) : null);
116
116
  const areaY = $derived(marker.areaY === true ? (activePoint?.y ?? null) : null);
117
+ /**
118
+ * The part of the band an area covers: the active point's own slice (a grouped bar's series), or the whole
119
+ * band while the hover matches more than one point in that category, as its dots do.
120
+ */
121
+ const areaInsetOf = (axis: 'x' | 'y'): BandSlice | undefined => {
122
+ const at = activePoint?.[axis];
123
+ const key = (value: unknown) => (value instanceof Date ? +value : value);
124
+ const shared = data.filter((point) => key(point[axis]) === key(at)).length > 1;
125
+ return shared ? undefined : activePoint?.areaInset;
126
+ };
127
+ const areaInsetX = $derived(areaX != null ? areaInsetOf('x') : undefined);
128
+ const areaInsetY = $derived(areaY != null ? areaInsetOf('y') : undefined);
117
129
  const showLabels = $derived(marker.showLabels ?? true);
118
130
  const format = $derived(marker.format);
119
131
  /** The hovered value as the marker's display fields ask; `undefined` while nothing does, so the plot's own label stays. */
@@ -180,9 +192,11 @@
180
192
  isHorizontal: false,
181
193
  data: [],
182
194
  category: areaX,
183
- insetLeft: activePoint?.areaInset?.start,
184
- insetRight: activePoint?.areaInset?.end,
185
- styles: areaStyle
195
+ styles: {
196
+ ...areaStyle,
197
+ insetLeft: areaInsetX?.start,
198
+ insetRight: areaInsetX?.end
199
+ }
186
200
  }}
187
201
  />
188
202
  {/if}
@@ -195,9 +209,11 @@
195
209
  isHorizontal: true,
196
210
  data: [],
197
211
  category: areaY,
198
- insetTop: activePoint?.areaInset?.start,
199
- insetBottom: activePoint?.areaInset?.end,
200
- styles: areaStyle
212
+ styles: {
213
+ ...areaStyle,
214
+ insetTop: areaInsetY?.start,
215
+ insetBottom: areaInsetY?.end
216
+ }
201
217
  }}
202
218
  />
203
219
  {/if}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Hull } from 'svelteplot';
3
4
  import { MarkerChannels } from '../utils/MarkerChannels';
4
5
  import { getConfiguration } from '../../configuration/config.svelte';
@@ -6,6 +7,7 @@
6
7
 
7
8
  /** Renders one `'hull'` marker as `<Hull>` - the convex hull around a cluster of points. */
8
9
  let { marker }: { marker: HullMarkerConfig } = $props();
10
+ const events = MarkerEvents.use(() => marker);
9
11
 
10
12
  const cfg = $derived(getConfiguration());
11
13
  const data = $derived(marker.data ?? []);
@@ -13,6 +15,7 @@
13
15
  </script>
14
16
 
15
17
  <Hull
18
+ {...events.props}
16
19
  {data}
17
20
  x={marker.x}
18
21
  y={marker.y}
@@ -1,10 +1,12 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Image } from 'svelteplot';
3
4
  import RowOffsetMarks from '../utils/RowOffsetMarks.svelte';
4
5
  import type { ImageMarkerConfig } from '../../types/markers/common';
5
6
 
6
7
  /** Renders one `'image'` marker as `<Image>` - an image centered on each row's `x`/`y`. */
7
8
  let { marker }: { marker: ImageMarkerConfig } = $props();
9
+ const events = MarkerEvents.use(() => marker);
8
10
 
9
11
  const data = $derived(marker.data ?? []);
10
12
  </script>
@@ -12,6 +14,7 @@
12
14
  <RowOffsetMarks {marker} rows={data}>
13
15
  {#snippet mark(rows)}
14
16
  <Image
17
+ {...events.props}
15
18
  data={rows}
16
19
  x={marker.x}
17
20
  y={marker.y}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Line } from 'svelteplot';
3
4
  import { MarkerChannels } from '../utils/MarkerChannels';
4
5
  import { getConfiguration } from '../../configuration/config.svelte';
@@ -10,6 +11,7 @@
10
11
  * its color, so each `grouping`'s line takes its group's color.
11
12
  */
12
13
  let { marker }: { marker: LineMarkerConfig } = $props();
14
+ const events = MarkerEvents.use(() => marker);
13
15
 
14
16
  const cfg = $derived(getConfiguration());
15
17
  const data = $derived(marker.data ?? []);
@@ -17,6 +19,7 @@
17
19
  </script>
18
20
 
19
21
  <Line
22
+ {...events.props}
20
23
  {data}
21
24
  x={marker.x}
22
25
  y={marker.y}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Geo } from 'svelteplot';
3
4
  import { geoPath } from 'd3-geo';
4
5
  import { resolveAccessor } from '../../utils/accessors';
@@ -14,6 +15,7 @@
14
15
  * projected positions.
15
16
  */
16
17
  let { marker }: { marker: GeoLinkMarkerConfig } = $props();
18
+ const events = MarkerEvents.use(() => marker);
17
19
 
18
20
  type Point = [number, number];
19
21
  const STEPS = 64;
@@ -181,6 +183,7 @@
181
183
  {:else}
182
184
  <!-- No `fill` prop: svelteplot treats any string as scale-worthy unless it's a real CSS color, so a literal `'none'` would run through the color scale instead of staying unfilled. Omitting it leaves svelteplot's own unfilled default in place. -->
183
185
  <Geo
186
+ {...events.props}
184
187
  class="link"
185
188
  data={arcFeatures}
186
189
  stroke={marker.styles?.stroke ?? 'currentColor'}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { Raster } from 'svelteplot';
3
4
  import type { RasterMarkerConfig } from '../../types/markers/common';
4
5
 
@@ -8,11 +9,13 @@
8
9
  * so it is forwarded as `null` rather than an empty array.
9
10
  */
10
11
  let { marker }: { marker: RasterMarkerConfig } = $props();
12
+ const events = MarkerEvents.use(() => marker);
11
13
 
12
14
  const data = $derived(marker.data?.length ? marker.data : null);
13
15
  </script>
14
16
 
15
17
  <Raster
18
+ {...events.props}
16
19
  {data}
17
20
  x={marker.x}
18
21
  y={marker.y}
@@ -1,7 +1,9 @@
1
1
  <script lang="ts">
2
+ import { RowStyle } from '../utils/RowStyle';
2
3
  import { Rect } from 'svelteplot';
3
4
  import { MarkerChannels } from '../utils/MarkerChannels';
4
5
  import { getConfiguration } from '../../configuration/config.svelte';
6
+ import { MarkerEvents } from '../utils/MarkerEvents';
5
7
  import type { RectMarkerConfig } from '../../types/markers/common';
6
8
 
7
9
  /** Renders one `'rect'` marker as a `<Rect>` call - an arbitrary rectangle, unlike `BarMarker`'s category-band-anchored bar. */
@@ -13,6 +15,8 @@
13
15
  const opacityBuckets = $derived(
14
16
  MarkerChannels.buildOpacityBuckets(data, marker.opacity, marker.scales, cfg)
15
17
  );
18
+
19
+ const events = MarkerEvents.use(() => marker);
16
20
  </script>
17
21
 
18
22
  {#if opacityBuckets}
@@ -26,16 +30,18 @@
26
30
  y1={marker.y1}
27
31
  y2={marker.y2}
28
32
  interval={marker.interval}
29
- insetLeft={marker.insetLeft}
30
- insetRight={marker.insetRight}
31
- insetTop={marker.insetTop}
32
- insetBottom={marker.insetBottom}
33
- fill={channelFill ?? marker.styles?.fill}
33
+ insetLeft={RowStyle.field(marker.styles?.insetLeft)}
34
+ insetRight={RowStyle.field(marker.styles?.insetRight)}
35
+ insetTop={RowStyle.field(marker.styles?.insetTop)}
36
+ insetBottom={RowStyle.field(marker.styles?.insetBottom)}
37
+ fill={channelFill ?? RowStyle.of(marker.styles?.fill, data)}
34
38
  fillOpacity={bucket.value}
35
- stroke={marker.styles?.stroke}
36
- strokeWidth={marker.styles?.strokeWidth}
37
- strokeOpacity={marker.styles?.strokeOpacity}
39
+ stroke={RowStyle.of(marker.styles?.stroke, data)}
40
+ strokeWidth={RowStyle.field(marker.styles?.strokeWidth)}
41
+ strokeOpacity={RowStyle.opacity(marker.styles?.strokeOpacity)}
38
42
  strokeDasharray={marker.styles?.strokeDasharray}
43
+ borderRadius={RowStyle.field(marker.styles?.borderRadius)}
44
+ {...events.props}
39
45
  />
40
46
  {/each}
41
47
  {:else}
@@ -48,15 +54,17 @@
48
54
  y1={marker.y1}
49
55
  y2={marker.y2}
50
56
  interval={marker.interval}
51
- insetLeft={marker.insetLeft}
52
- insetRight={marker.insetRight}
53
- insetTop={marker.insetTop}
54
- insetBottom={marker.insetBottom}
55
- fill={channelFill ?? marker.styles?.fill}
56
- fillOpacity={marker.styles?.fillOpacity ?? 1}
57
- stroke={marker.styles?.stroke}
58
- strokeWidth={marker.styles?.strokeWidth}
59
- strokeOpacity={marker.styles?.strokeOpacity}
57
+ insetLeft={RowStyle.field(marker.styles?.insetLeft)}
58
+ insetRight={RowStyle.field(marker.styles?.insetRight)}
59
+ insetTop={RowStyle.field(marker.styles?.insetTop)}
60
+ insetBottom={RowStyle.field(marker.styles?.insetBottom)}
61
+ fill={channelFill ?? RowStyle.of(marker.styles?.fill, data)}
62
+ fillOpacity={RowStyle.opacity(marker.styles?.fillOpacity) ?? 1}
63
+ stroke={RowStyle.of(marker.styles?.stroke, data)}
64
+ strokeWidth={RowStyle.field(marker.styles?.strokeWidth)}
65
+ strokeOpacity={RowStyle.opacity(marker.styles?.strokeOpacity)}
60
66
  strokeDasharray={marker.styles?.strokeDasharray}
67
+ borderRadius={RowStyle.field(marker.styles?.borderRadius)}
68
+ {...events.props}
61
69
  />
62
70
  {/if}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
3
  import { RegressionX, RegressionY } from 'svelteplot';
3
4
  import { usePlot } from '../../layout/plot/usePlot';
4
5
  import type { RegressionMarkerConfig } from '../../types/markers/common';
@@ -18,6 +19,7 @@
18
19
  * active, never exposed as its own style option.
19
20
  */
20
21
  let { marker }: { marker: RegressionMarkerConfig } = $props();
22
+ const events = MarkerEvents.use(() => marker);
21
23
 
22
24
  const plot = usePlot();
23
25
  const isHorizontal = $derived(plot.axes?.isHorizontal ?? false);
@@ -27,6 +29,7 @@
27
29
  </script>
28
30
 
29
31
  <RegressionMark
32
+ {...events.props}
30
33
  {data}
31
34
  x={marker.x}
32
35
  y={marker.y}
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { RowStyle } from '../utils/RowStyle';
3
+ import { MarkerEvents } from '../utils/MarkerEvents';
2
4
  import { RuleX, RuleY } from 'svelteplot';
3
5
  import type { AxisValue } from '../../types/layout/mapping';
4
6
  import { RowStroke } from '../utils/RowStroke';
@@ -14,6 +16,7 @@
14
16
  * straight through to svelteplot's own `RuleX`/`RuleY`.
15
17
  */
16
18
  const { marker }: { marker: RuleMarkerConfig } = $props();
19
+ const events = MarkerEvents.use(() => marker);
17
20
 
18
21
  const axis = $derived(marker.axis);
19
22
  const data = $derived(marker.data);
@@ -34,21 +37,28 @@
34
37
  */
35
38
  const position = $derived(data ? value : (d: { v: AxisValue }) => d.v);
36
39
 
37
- /** Each row's own stroke, over the marker's `styles`. */
40
+ /** Each row's stroke: the marker's `styles`, which can be functions of the row. */
38
41
  const row = $derived(
39
- RowStroke.of(marker, styles, { stroke: 'currentColor', strokeWidth: 1, strokeOpacity: 1 })
42
+ RowStroke.of(styles, rows, { stroke: 'currentColor', strokeWidth: 1, strokeOpacity: 1 })
40
43
  );
41
44
  </script>
42
45
 
43
46
  <RuleMark
47
+ {...events.props}
44
48
  data={rows}
45
49
  {...axis === 'x'
46
50
  ? { x: position, y1: marker.y1, y2: marker.y2 }
47
51
  : { y: position, x1: marker.x1, x2: marker.x2 }}
48
- inset={marker.inset}
52
+ inset={RowStyle.field(marker.styles?.inset)}
49
53
  {...axis === 'x'
50
- ? { insetTop: marker.insetTop, insetBottom: marker.insetBottom }
51
- : { insetLeft: marker.insetLeft, insetRight: marker.insetRight }}
54
+ ? {
55
+ insetTop: RowStyle.field(marker.styles?.insetTop),
56
+ insetBottom: RowStyle.field(marker.styles?.insetBottom)
57
+ }
58
+ : {
59
+ insetLeft: RowStyle.field(marker.styles?.insetLeft),
60
+ insetRight: RowStyle.field(marker.styles?.insetRight)
61
+ }}
52
62
  stroke={row.stroke}
53
63
  strokeWidth={row.strokeWidth}
54
64
  strokeOpacity={row.strokeOpacity}
@@ -0,0 +1,44 @@
1
+ /** What a label is once it is fitted: the text to write, on as many lines as it takes, and the size to write it at. */
2
+ export type FittedText = {
3
+ text: string;
4
+ fontSize: number;
5
+ };
6
+ /** What a label is fitted to. */
7
+ export type TextFitOptions = {
8
+ text: string;
9
+ /** What to write instead when `text` does not fit even cut short: a value alone, say. Hidden if that does not fit either. */
10
+ fallback?: string;
11
+ /** The room the label has, in px. */
12
+ width: number;
13
+ height: number;
14
+ /** The size the label is written at when it has the room for it. */
15
+ fontSize: number;
16
+ /** The smallest size it is written at, shrunk one px at a time down to it. */
17
+ minFontSize: number;
18
+ /** The height of a line, as a multiple of the font size. */
19
+ lineHeight: number;
20
+ /** How wide `text` writes at `fontSize`, in px. */
21
+ measure: (text: string, fontSize: number) => number;
22
+ /** The fewest letters of a text worth writing when it is cut short. */
23
+ minLetters: number;
24
+ };
25
+ /**
26
+ * How a label that may be too big for its box is written: a smaller size first; failing that the text cut short
27
+ * with an ellipsis on its last line, at the smallest size; failing that its `fallback`; failing that nothing.
28
+ */
29
+ export declare class TextFit {
30
+ #private;
31
+ static fit(options: TextFitOptions): FittedText | undefined;
32
+ /**
33
+ * `text` on at most `room.lines` lines of `room.width`: whole words per line, a new line after each newline. With `cut`,
34
+ * what does not fit is cut short - the rest of the text on the last line, or a word wider than any line - with an
35
+ * ellipsis; without it, a text that does not fit comes to `undefined`, and so does one whose first line shows
36
+ * under `minLetters` letters.
37
+ */
38
+ static wrap(text: string, room: {
39
+ width: number;
40
+ lines: number;
41
+ }, cut: boolean, minLetters: number, measure: (text: string) => number): string[] | undefined;
42
+ /** `text` as it fits `width`: as it is, or cut short with an ellipsis. */
43
+ static cut(text: string, width: number, measure: (text: string) => number): string;
44
+ }
@@ -0,0 +1,86 @@
1
+ const ELLIPSIS = '…';
2
+ /**
3
+ * How a label that may be too big for its box is written: a smaller size first; failing that the text cut short
4
+ * with an ellipsis on its last line, at the smallest size; failing that its `fallback`; failing that nothing.
5
+ */
6
+ export class TextFit {
7
+ static fit(options) {
8
+ const whole = TextFit.#shrunk(options.text, options);
9
+ if (whole)
10
+ return whole;
11
+ const size = options.minFontSize;
12
+ const lines = TextFit.wrap(options.text, TextFit.#room(options, size), true, options.minLetters, (t) => options.measure(t, size));
13
+ if (lines)
14
+ return { text: lines.join('\n'), fontSize: size };
15
+ return options.fallback === undefined ? undefined : TextFit.#shrunk(options.fallback, options);
16
+ }
17
+ /** `text` as it is, at the biggest size that holds it all: no cut, or `undefined` at none. */
18
+ static #shrunk(text, options) {
19
+ for (let size = options.fontSize; size >= options.minFontSize; size--) {
20
+ const lines = TextFit.wrap(text, TextFit.#room(options, size), false, options.minLetters, (t) => options.measure(t, size));
21
+ if (lines)
22
+ return { text: lines.join('\n'), fontSize: size };
23
+ }
24
+ return undefined;
25
+ }
26
+ /** How wide the lines are, and how many there are, at `size`. */
27
+ static #room(options, size) {
28
+ return {
29
+ width: options.width,
30
+ lines: Math.floor(options.height / (size * options.lineHeight))
31
+ };
32
+ }
33
+ /**
34
+ * `text` on at most `room.lines` lines of `room.width`: whole words per line, a new line after each newline. With `cut`,
35
+ * what does not fit is cut short - the rest of the text on the last line, or a word wider than any line - with an
36
+ * ellipsis; without it, a text that does not fit comes to `undefined`, and so does one whose first line shows
37
+ * under `minLetters` letters.
38
+ */
39
+ static wrap(text, room, cut, minLetters, measure) {
40
+ if (room.lines < 1)
41
+ return undefined;
42
+ const all = [];
43
+ let overlong = false;
44
+ for (const paragraph of text.split('\n')) {
45
+ let line = '';
46
+ for (const word of paragraph.split(/ +/).filter(Boolean)) {
47
+ const next = line ? `${line} ${word}` : word;
48
+ if (measure(next) <= room.width) {
49
+ line = next;
50
+ continue;
51
+ }
52
+ if (line)
53
+ all.push(line);
54
+ // A word wider than a line goes on one of its own.
55
+ if (measure(word) > room.width) {
56
+ overlong = true;
57
+ all.push(word);
58
+ line = '';
59
+ }
60
+ else {
61
+ line = word;
62
+ }
63
+ }
64
+ if (line)
65
+ all.push(line);
66
+ }
67
+ if (!cut)
68
+ return !overlong && all.length <= room.lines ? all : undefined;
69
+ const lines = all.slice(0, room.lines);
70
+ // The rest of the text goes on the last line, to be cut short there.
71
+ if (all.length > room.lines)
72
+ lines[room.lines - 1] = all.slice(room.lines - 1).join(' ');
73
+ const written = lines.map((line) => TextFit.cut(line, room.width, measure));
74
+ const first = written[0]?.replace(ELLIPSIS, '').replace(/\s/g, '') ?? '';
75
+ return first.length >= minLetters ? written : undefined;
76
+ }
77
+ /** `text` as it fits `width`: as it is, or cut short with an ellipsis. */
78
+ static cut(text, width, measure) {
79
+ if (measure(text) <= width)
80
+ return text;
81
+ let end = text.length;
82
+ while (end > 0 && measure(`${text.slice(0, end).trimEnd()}${ELLIPSIS}`) > width)
83
+ end--;
84
+ return end > 0 ? `${text.slice(0, end).trimEnd()}${ELLIPSIS}` : '';
85
+ }
86
+ }