@fundar/data-chart-telling 0.0.1

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 (106) hide show
  1. package/README.md +250 -0
  2. package/dist/charts/Chart.svelte +125 -0
  3. package/dist/charts/Chart.svelte.d.ts +42 -0
  4. package/dist/charts/bar/Chart.svelte +104 -0
  5. package/dist/charts/bar/Chart.svelte.d.ts +27 -0
  6. package/dist/charts/container/ChartContainer.svelte +127 -0
  7. package/dist/charts/container/ChartContainer.svelte.d.ts +14 -0
  8. package/dist/charts/heatmap/Chart.svelte +107 -0
  9. package/dist/charts/heatmap/Chart.svelte.d.ts +30 -0
  10. package/dist/charts/line/Chart.svelte +106 -0
  11. package/dist/charts/line/Chart.svelte.d.ts +27 -0
  12. package/dist/charts/pyramid/Chart.svelte +127 -0
  13. package/dist/charts/pyramid/Chart.svelte.d.ts +31 -0
  14. package/dist/configuration/config.svelte.d.ts +12 -0
  15. package/dist/configuration/config.svelte.js +159 -0
  16. package/dist/configuration/themes/compact.d.ts +2 -0
  17. package/dist/configuration/themes/compact.js +9 -0
  18. package/dist/configuration/themes/dark.d.ts +2 -0
  19. package/dist/configuration/themes/dark.js +17 -0
  20. package/dist/configuration/themes/default.d.ts +3 -0
  21. package/dist/configuration/themes/default.js +2 -0
  22. package/dist/configuration/themes/editorial.d.ts +2 -0
  23. package/dist/configuration/themes/editorial.js +14 -0
  24. package/dist/configuration/themes/index.d.ts +481 -0
  25. package/dist/configuration/themes/index.js +16 -0
  26. package/dist/index.d.ts +32 -0
  27. package/dist/index.js +27 -0
  28. package/dist/layout/facet/FacetLayout.svelte +97 -0
  29. package/dist/layout/facet/FacetLayout.svelte.d.ts +35 -0
  30. package/dist/layout/legend/ContinuousSection.svelte +95 -0
  31. package/dist/layout/legend/ContinuousSection.svelte.d.ts +7 -0
  32. package/dist/layout/legend/DiscreteSection.svelte +87 -0
  33. package/dist/layout/legend/DiscreteSection.svelte.d.ts +7 -0
  34. package/dist/layout/legend/LegendLayout.svelte +47 -0
  35. package/dist/layout/legend/LegendLayout.svelte.d.ts +7 -0
  36. package/dist/layout/plot/PlotLayout.svelte +236 -0
  37. package/dist/layout/plot/PlotLayout.svelte.d.ts +70 -0
  38. package/dist/layout/timeline/Timeline.svelte +86 -0
  39. package/dist/layout/timeline/Timeline.svelte.d.ts +11 -0
  40. package/dist/layout/timeline/TimelineLayout.svelte +181 -0
  41. package/dist/layout/timeline/TimelineLayout.svelte.d.ts +38 -0
  42. package/dist/layout/tooltip/Tooltip.svelte +55 -0
  43. package/dist/layout/tooltip/Tooltip.svelte.d.ts +12 -0
  44. package/dist/layout/tooltip/controller.svelte.d.ts +52 -0
  45. package/dist/layout/tooltip/controller.svelte.js +128 -0
  46. package/dist/layout/tooltip/hover.svelte.d.ts +7 -0
  47. package/dist/layout/tooltip/hover.svelte.js +28 -0
  48. package/dist/layout/tooltip/utils.d.ts +41 -0
  49. package/dist/layout/tooltip/utils.js +78 -0
  50. package/dist/plots/bar/Plot.svelte +176 -0
  51. package/dist/plots/bar/Plot.svelte.d.ts +26 -0
  52. package/dist/plots/heatmap/Plot.svelte +175 -0
  53. package/dist/plots/heatmap/Plot.svelte.d.ts +26 -0
  54. package/dist/plots/line/Plot.svelte +232 -0
  55. package/dist/plots/line/Plot.svelte.d.ts +26 -0
  56. package/dist/plots/markers/DeltaMarker.svelte +104 -0
  57. package/dist/plots/markers/DeltaMarker.svelte.d.ts +30 -0
  58. package/dist/plots/markers/DotMarker.svelte +41 -0
  59. package/dist/plots/markers/DotMarker.svelte.d.ts +31 -0
  60. package/dist/plots/markers/HoverMarker.svelte +78 -0
  61. package/dist/plots/markers/HoverMarker.svelte.d.ts +22 -0
  62. package/dist/plots/markers/PeakMarker.svelte +80 -0
  63. package/dist/plots/markers/PeakMarker.svelte.d.ts +13 -0
  64. package/dist/plots/markers/TextMarker.svelte +55 -0
  65. package/dist/plots/markers/TextMarker.svelte.d.ts +32 -0
  66. package/dist/plots/pyramid/Plot.svelte +282 -0
  67. package/dist/plots/pyramid/Plot.svelte.d.ts +26 -0
  68. package/dist/plots/utils/accessors.d.ts +6 -0
  69. package/dist/plots/utils/accessors.js +10 -0
  70. package/dist/plots/utils/delta.d.ts +12 -0
  71. package/dist/plots/utils/delta.js +69 -0
  72. package/dist/plots/utils/segments.d.ts +33 -0
  73. package/dist/plots/utils/segments.js +153 -0
  74. package/dist/stores/device.d.ts +7 -0
  75. package/dist/stores/device.js +39 -0
  76. package/dist/types/charts/common.d.ts +64 -0
  77. package/dist/types/charts/common.js +1 -0
  78. package/dist/types/charts/interpolate.d.ts +2 -0
  79. package/dist/types/charts/interpolate.js +1 -0
  80. package/dist/types/charts/legend.d.ts +29 -0
  81. package/dist/types/charts/legend.js +1 -0
  82. package/dist/types/charts/props.d.ts +35 -0
  83. package/dist/types/charts/props.js +1 -0
  84. package/dist/types/configuration/styling.d.ts +97 -0
  85. package/dist/types/configuration/styling.js +1 -0
  86. package/dist/types/layout/tooltip.d.ts +88 -0
  87. package/dist/types/layout/tooltip.js +1 -0
  88. package/dist/types/plots/common.d.ts +100 -0
  89. package/dist/types/plots/common.js +1 -0
  90. package/dist/types/plots/delta.d.ts +19 -0
  91. package/dist/types/plots/delta.js +1 -0
  92. package/dist/types/plots/markers.d.ts +56 -0
  93. package/dist/types/plots/markers.js +1 -0
  94. package/dist/types/plots/props.d.ts +23 -0
  95. package/dist/types/plots/props.js +1 -0
  96. package/dist/types/plots/styles.d.ts +176 -0
  97. package/dist/types/plots/styles.js +1 -0
  98. package/dist/types/viewport.d.ts +4 -0
  99. package/dist/types/viewport.js +1 -0
  100. package/dist/utils/color.d.ts +5 -0
  101. package/dist/utils/color.js +42 -0
  102. package/dist/utils/grouping.d.ts +14 -0
  103. package/dist/utils/grouping.js +36 -0
  104. package/dist/utils/interpolate.d.ts +30 -0
  105. package/dist/utils/interpolate.js +71 -0
  106. package/package.json +76 -0
package/README.md ADDED
@@ -0,0 +1,250 @@
1
+ # data-chart-telling
2
+
3
+ A Svelte 5 component library for building data visualisation charts, built on top of [svelteplot](https://svelteplot.dev). It provides low-level **plot** primitives (single graphs) and will grow to include higher-level **chart** compositions with titles, legends, facets, and timelines.
4
+
5
+ ---
6
+
7
+ ## Build & publish
8
+
9
+ After making changes, rebuild the package distributable:
10
+
11
+ ```bash
12
+ pnpm build
13
+ ```
14
+
15
+ `prepack` runs `svelte-package` followed by `publint` automatically before packing, so a publish will always ship a clean build:
16
+
17
+ ```bash
18
+ # dry-run — inspect what will be uploaded
19
+ pnpm pack
20
+
21
+ # publish to the npm registry
22
+ pnpm publish --access public
23
+ ```
24
+
25
+ > Bump `version` in `package.json` before each publish.
26
+
27
+ ---
28
+
29
+ ## Tests
30
+
31
+ ### Unit tests (node)
32
+
33
+ Pure logic tests — no browser required.
34
+
35
+ ```bash
36
+ pnpm test
37
+ ```
38
+
39
+ This runs both the **node unit** project and the **browser component** project (Chromium via Playwright).
40
+
41
+ To run only the node unit tests:
42
+
43
+ ```bash
44
+ pnpm vitest run --project=unit
45
+ ```
46
+
47
+ To run only the browser component tests:
48
+
49
+ ```bash
50
+ pnpm vitest run --project=browser
51
+ ```
52
+
53
+ ### Storybook interaction tests
54
+
55
+ Run stories as Vitest tests (requires Storybook to boot internally):
56
+
57
+ ```bash
58
+ pnpm test:stories
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Storybook
64
+
65
+ Serve the component explorer locally:
66
+
67
+ ```bash
68
+ pnpm storybook
69
+ # → http://localhost:6006
70
+ ```
71
+
72
+ Build a static Storybook for deployment:
73
+
74
+ ```bash
75
+ pnpm build-storybook
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Chart types
81
+
82
+ > **Vocabulary**: a _plot_ is a single graph (axes + marks). A _chart_ wraps a plot with a title, caption, legend, facets, and/or a timeline.
83
+
84
+ ### Charts
85
+
86
+ | Component | Import path | Description |
87
+ | --- | --- | --- |
88
+ | `BarChart` | `$lib/charts/bar/Chart.svelte` | Vertical bar chart with title/caption/legend/facet/timeline chrome. |
89
+ | `HeatmapChart` | `$lib/charts/heatmap/Chart.svelte` | Grid of colour-coded cells. Colour domain is computed over the full dataset for cross-facet comparability. |
90
+ | `LineChart` | `$lib/charts/line/Chart.svelte` | Line chart. Default timeline mode is `upTo` (cumulative reveal). |
91
+ | `PyramidChart` | `$lib/charts/pyramid/Chart.svelte` | Population-pyramid-style diverging bar chart with a shared zero baseline. |
92
+
93
+ ### Plots
94
+
95
+ | Component | Import path | Description |
96
+ | --- | --- | --- |
97
+ | `BarPlot` | `$lib/plots/bar/Plot.svelte` | Vertical bar chart. Supports multiple series and per-series range segments. |
98
+ | `HeatmapPlot` | `$lib/plots/heatmap/Plot.svelte` | Grid of colour-coded cells. |
99
+ | `LinePlot` | `$lib/plots/line/Plot.svelte` | Line chart. Supports multiple series and per-series range segments with independent stroke styles. |
100
+ | `PyramidPlot` | `$lib/plots/pyramid/Plot.svelte` | Diverging horizontal bar chart; each series is mirrored left or right of a shared zero baseline. |
101
+
102
+ All components accept explicit `width` and `height` in pixels (svelteplot does not auto-size).
103
+
104
+ ---
105
+
106
+ ### Plot props
107
+
108
+ Plots accept either `series` (pre-built `Series[]`) or the `data` + `x`/`y`/`z` flat-rows path — these are mutually exclusive.
109
+
110
+ | Prop | Type | Default | BarPlot | HeatmapPlot | LinePlot | PyramidPlot |
111
+ | ---- | ---- | ------- | ------- | ----------- | -------- | ----------- |
112
+ | `width` | `number` | — | ✅ | ✅ | ✅ | ✅ |
113
+ | `height` | `number` | — | ✅ | ✅ | ✅ | ✅ |
114
+ | `series` | `Series[]` | — | ✅ | ✅ | ✅ | ✅ |
115
+ | `data` | `TData[]` | — | ✅ | ✅ | ✅ | ✅ |
116
+ | `x` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
117
+ | `y` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
118
+ | `z` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
119
+ | `segments` | `SegmentsMap<TStyle>` | `{}` / `[]` | ✅ | ✅ | ✅ | ✅ |
120
+ | `markers` | `Marker[]` | `[]` | ✅ | ✅ | ✅ | ✅ |
121
+ | `scales` | `ScalesConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
122
+ | `margins` | `MarginConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
123
+ | `tooltip` | `TooltipRuntime` | | ✅ | ✅ | ✅ | ✅ |
124
+ | `styles` | `PlotStyles` | | ✅ | ✅ | ✅ | ✅ |
125
+
126
+ #### `PlotStyles` sub-properties (plots)
127
+
128
+ | Sub-prop | Type | BarPlot default | HeatmapPlot default | LinePlot default | PyramidPlot default |
129
+ | -------- | ---- | --------------- | ------------------- | ---------------- | ------------------- |
130
+ | `styles.values.show` | `boolean` | `false` | `true` | `false` (last point) | `false` |
131
+ | `styles.values.format` | `(v: number, seriesName: string) => string` | `v => \`${v}\`` | `v => \`${v}\`` | `(v, name) => \`${name} - ${v}\`` | `v => \`${v}\`` |
132
+ | `styles.values.anchor` | `'outside' \| 'start' \| 'middle' \| 'end'` | `'outside'` | — | `'start'` | `'outside'` |
133
+ | `styles.values.dx` | `number` | | | | |
134
+ | `styles.values.dy` | `number` | | | | |
135
+ | `styles.values.textAnchor` | `'start' \| 'end' \| 'middle'` | | `'middle'` | | |
136
+ | `styles.values.lineAnchor` | `'top' \| 'bottom' \| 'middle'` | | `'middle'` | | |
137
+ | `styles.values.rotate` | `number` | | | | |
138
+ | `styles.values.class` | `string` | | | | |
139
+ | `styles.values.textClass` | `string` | | | | |
140
+ | `styles.colors.min` | `string` | — | cfg `continuous.min` | — | — |
141
+ | `styles.colors.max` | `string` | — | cfg `continuous.max` | — | — |
142
+ | `styles.colors.left` | `string` | — | — | — | palette index 0 |
143
+ | `styles.colors.right` | `string` | — | — | — | palette index 1 |
144
+
145
+ ---
146
+
147
+ ### Chart props
148
+
149
+ | Prop | Type | Default | BarChart | HeatmapChart | LineChart | PyramidChart |
150
+ | ---- | ---- | ------- | -------- | ------------ | --------- | ------------ |
151
+ | `width` | `number` | — | ✅ | ✅ | ✅ | ✅ |
152
+ | `height` | `number` | — | ✅ | ✅ | ✅ | ✅ |
153
+ | `data` | `TRow[]` | — | ✅ | ✅ | ✅ | ✅ |
154
+ | `x` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
155
+ | `y` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
156
+ | `z` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
157
+ | `title` | `string` | | ✅ | ✅ | ✅ | ✅ |
158
+ | `subtitle` | `string` | | ✅ | ✅ | ✅ | ✅ |
159
+ | `caption` | `string` | | ✅ | ✅ | ✅ | ✅ |
160
+ | `legend` | `LegendSection[]` | | ✅ | ✅ | ✅ | ✅ |
161
+ | `segments` | `SegmentsMap<TStyle>` | `{}` / `[]` | ✅ | ✅ | ✅ | ✅ |
162
+ | `markers` | `Marker[]` | `[]` | ✅ | ✅ | ✅ | ✅ |
163
+ | `scales` | `ScalesConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
164
+ | `margins` | `MarginConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
165
+ | `facet` | `FacetConfig` | | ✅ | ✅ | ✅ | ✅ |
166
+ | `timeline` | `TimelineConfig` | | ✅ | ✅ | ✅ | ✅ |
167
+ | `tooltip` | `TooltipProp` | | ✅ | ✅ | ✅ | ✅ |
168
+ | `styles` | `PlotStyles` | | ✅ | ✅ | ✅ | ✅ |
169
+
170
+ See the [PlotStyles sub-properties table](#plotstyles-sub-properties-plots) above — charts accept the same `styles` shape and pass it through to their underlying plot.
171
+
172
+ ---
173
+
174
+ ### Segments
175
+
176
+ The `segments` prop separates data ranges from visual styling. Every plot kind shares the same underlying types; the style shape (`TStyle`) differs per mark.
177
+
178
+ | Plot | `segments` type |
179
+ | ---- | --------------- |
180
+ | `LinePlot` / `LineChart` | `SegmentsMap<LineSegmentStyle>` |
181
+ | `BarPlot` / `BarChart` | `SegmentsMap<BarSegmentStyle>` |
182
+ | `PyramidPlot` / `PyramidChart` | `SegmentsMap<BarSegmentStyle>` |
183
+ | `HeatmapPlot` / `HeatmapChart` | `SegmentsMap<CellSegmentStyle>` (array form only) |
184
+
185
+ #### Core types
186
+
187
+ ```ts
188
+ type Range = { from?: AxisValue; to?: AxisValue }; // half-open on either side
189
+ type Area = { x?: Range; y?: Range }; // 2-D rectangular region
190
+ type Segment<TStyle> = { areas?: Area[]; style?: TStyle };
191
+ ```
192
+
193
+ A **`Segment`** matches a data point (or heatmap cell) when the point falls inside **any** of its `areas`. Omitting `areas` (or providing an empty array) makes the segment match **all** data — useful as a baseline style. Segments are rendered in order; later ones appear on top.
194
+
195
+ An **`Area`** restricts by x, y, or both. Both bounds of a `Range` are **inclusive**. Omitting `x` or `y` leaves that axis unrestricted.
196
+
197
+ #### `SegmentsMap` forms
198
+
199
+ ```ts
200
+ // Array form — same segments apply to every series (required for heatmaps)
201
+ type SegmentsMap<TStyle> = Segment<TStyle>[];
202
+
203
+ // Record form — segments keyed by series name (line / bar / pyramid)
204
+ type SegmentsMap<TStyle> = SeriesAwareSegmentsMap<TStyle>;
205
+ = Record<string, Segment<TStyle>[]>;
206
+ ```
207
+
208
+ #### Examples
209
+
210
+ **Line chart — colour a period and leave the rest as the palette default:**
211
+
212
+ ```svelte
213
+ <LinePlot
214
+ {width} {height}
215
+ series={[{ name: 'gdp', x: 'year', y: 'value', data: GDP }]}
216
+ segments={{
217
+ gdp: [
218
+ { style: { stroke: { stroke: '#94a3b8', strokeWidth: 1 } } },
219
+ { areas: [{ x: { from: 2008, to: 2010 } }],
220
+ style: { stroke: { stroke: '#ef4444', strokeWidth: 3 } } },
221
+ ],
222
+ }}
223
+ />
224
+ ```
225
+
226
+ **Heatmap — highlight specific columns, rows, and individual cells:**
227
+
228
+ ```svelte
229
+ <HeatmapChart
230
+ {width} {height} {data} {x} {y} {z}
231
+ segments={[
232
+ { areas: [{ x: { from: 'Q1', to: 'Q1' } }, { x: { from: 'Q3', to: 'Q3' } }],
233
+ style: { stroke: '#f59e0b', strokeWidth: 2 } },
234
+ { areas: [{ x: { from: 'Q2', to: 'Q2' }, y: { from: 1, to: 2 } }],
235
+ style: { fill: '#ef4444', fillOpacity: 0.8 } },
236
+ ]}
237
+ />
238
+ ```
239
+
240
+ Omitting `segments` renders every mark in its default palette colour.
241
+
242
+ ---
243
+
244
+ ## Public API
245
+
246
+ ```ts
247
+ import {
248
+ ...
249
+ } from '@fundar/data-chart-telling';
250
+ ```
@@ -0,0 +1,125 @@
1
+ <script lang="ts" generics="TRow extends Record<string, unknown>">
2
+ import ChartContainer from './container/ChartContainer.svelte';
3
+ import TimelineLayout from '../layout/timeline/TimelineLayout.svelte';
4
+ import FacetLayout from '../layout/facet/FacetLayout.svelte';
5
+ import LegendLayout from '../layout/legend/LegendLayout.svelte';
6
+ import { getConfiguration } from '../configuration/config.svelte';
7
+ import { provideHover } from '../layout/tooltip/hover.svelte';
8
+ import type { LegendSection } from '../types/charts/legend';
9
+ import type { HoverStrategy } from '../types/layout/tooltip';
10
+ import type {
11
+ ChartPlotSnippet,
12
+ FacetConfig,
13
+ TimeValue,
14
+ TimelineConfig,
15
+ } from '../types/charts/common';
16
+
17
+ /**
18
+ * The orchestrator. It wires together the four interchangeable layout
19
+ * primitives — container, timeline, facet, legend — around a single leaf
20
+ * `plot` snippet. The composition order is fixed and meaningful:
21
+ *
22
+ * container ▸ timeline (one shared scrubber) ▸ facet (split per small-multiple) ▸ plot
23
+ *
24
+ * The leaf snippet receives `{ data, width, height, facetValue?, selectedValue? }`
25
+ * and knows nothing about how that box or that data slice was produced, which
26
+ * is what lets any plot (line, bar, heatmap, …) drop in unchanged.
27
+ */
28
+ let {
29
+ width,
30
+ height,
31
+ title,
32
+ subtitle,
33
+ caption,
34
+ legend,
35
+ data,
36
+ facet,
37
+ timeline,
38
+ hover,
39
+ plot,
40
+ }: {
41
+ width: number;
42
+ height: number;
43
+ title?: string;
44
+ subtitle?: string;
45
+ caption?: string;
46
+ legend?: LegendSection[];
47
+ data: TRow[];
48
+ facet?: FacetConfig<TRow>;
49
+ timeline?: TimelineConfig<TRow>;
50
+ hover?: { strategy: HoverStrategy; sync: boolean };
51
+ plot: ChartPlotSnippet<TRow>;
52
+ } = $props();
53
+
54
+ const cfg = $derived(getConfiguration());
55
+
56
+ const hoverStore = provideHover();
57
+ $effect(() => {
58
+ hoverStore.strategy = hover?.strategy ?? 'x';
59
+ hoverStore.sync = hover?.sync ?? false;
60
+ });
61
+ </script>
62
+
63
+ {#snippet body(rows: TRow[], w: number, h: number, selectedValue: TimeValue | undefined)}
64
+ {#if facet}
65
+ <FacetLayout
66
+ data={rows}
67
+ by={facet.by}
68
+ columns={facet.columns}
69
+ values={facet.values}
70
+ gap={facet.gap}
71
+ width={w}
72
+ height={h}
73
+ >
74
+ {#snippet cell({ facetValue, data: cellData, width: cw, height: ch })}
75
+ {@render plot({
76
+ data: cellData,
77
+ width: cw,
78
+ height: ch,
79
+ facetValue,
80
+ selectedValue
81
+ })}
82
+ {/snippet}
83
+ </FacetLayout>
84
+ {:else}
85
+ {@render plot({ data: rows, width: w, height: h, selectedValue })}
86
+ {/if}
87
+ {/snippet}
88
+
89
+ {#snippet legendSnippet()}
90
+ {#if legend && legend.length > 0}
91
+ <LegendLayout sections={legend} />
92
+ {/if}
93
+ {/snippet}
94
+
95
+ <ChartContainer
96
+ {width}
97
+ {height}
98
+ {title}
99
+ {subtitle}
100
+ {caption}
101
+ legend={legend && legend.length > 0 ? legendSnippet : undefined}
102
+ >
103
+ {#snippet plot({ width: w, height: h })}
104
+ {#if timeline}
105
+ <TimelineLayout
106
+ {data}
107
+ accessor={timeline.accessor}
108
+ progress={timeline.progress}
109
+ values={timeline.values}
110
+ orientation={timeline.orientation}
111
+ thickness={timeline.thickness ?? cfg.timeline.thickness}
112
+ filterMode={timeline.filterMode}
113
+ interpolate={typeof timeline.interpolate === 'object' ? timeline.interpolate : undefined}
114
+ width={w}
115
+ height={h}
116
+ >
117
+ {#snippet child({ selectedValue, data: tData, width: tw, height: th })}
118
+ {@render body(tData, tw, th, selectedValue)}
119
+ {/snippet}
120
+ </TimelineLayout>
121
+ {:else}
122
+ {@render body(data, w, h, undefined)}
123
+ {/if}
124
+ {/snippet}
125
+ </ChartContainer>
@@ -0,0 +1,42 @@
1
+ import type { LegendSection } from '../types/charts/legend';
2
+ import type { HoverStrategy } from '../types/layout/tooltip';
3
+ import type { ChartPlotSnippet, FacetConfig, TimelineConfig } from '../types/charts/common';
4
+ declare function $$render<TRow extends Record<string, unknown>>(): {
5
+ props: {
6
+ width: number;
7
+ height: number;
8
+ title?: string;
9
+ subtitle?: string;
10
+ caption?: string;
11
+ legend?: LegendSection[];
12
+ data: TRow[];
13
+ facet?: FacetConfig<TRow>;
14
+ timeline?: TimelineConfig<TRow>;
15
+ hover?: {
16
+ strategy: HoverStrategy;
17
+ sync: boolean;
18
+ };
19
+ plot: ChartPlotSnippet<TRow>;
20
+ };
21
+ exports: {};
22
+ bindings: "";
23
+ slots: {};
24
+ events: {};
25
+ };
26
+ declare class __sveltets_Render<TRow extends Record<string, unknown>> {
27
+ props(): ReturnType<typeof $$render<TRow>>['props'];
28
+ events(): ReturnType<typeof $$render<TRow>>['events'];
29
+ slots(): ReturnType<typeof $$render<TRow>>['slots'];
30
+ bindings(): "";
31
+ exports(): {};
32
+ }
33
+ interface $$IsomorphicComponent {
34
+ new <TRow extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TRow>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TRow>['props']>, ReturnType<__sveltets_Render<TRow>['events']>, ReturnType<__sveltets_Render<TRow>['slots']>> & {
35
+ $$bindings?: ReturnType<__sveltets_Render<TRow>['bindings']>;
36
+ } & ReturnType<__sveltets_Render<TRow>['exports']>;
37
+ <TRow extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TRow>['props']> & {}): ReturnType<__sveltets_Render<TRow>['exports']>;
38
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
39
+ }
40
+ declare const Chart: $$IsomorphicComponent;
41
+ type Chart<TRow extends Record<string, unknown>> = InstanceType<typeof Chart<TRow>>;
42
+ export default Chart;
@@ -0,0 +1,104 @@
1
+ <script lang="ts" generics="TRow extends Record<string, unknown>">
2
+ import Chart from '../Chart.svelte';
3
+ import BarPlot from '../../plots/bar/Plot.svelte';
4
+ import {
5
+ resolveTooltip,
6
+ resolveHoverStrategy,
7
+ resolveHoverSync,
8
+ hasHoverMarker
9
+ } from '../../layout/tooltip/utils';
10
+ import { resolveAccessor } from '../../plots/utils/accessors';
11
+ import { resolveTimelineInterpolation } from '../../utils/interpolate';
12
+ import type { BarSegmentStyle } from '../../types/plots/styles';
13
+ import type { ChartProps } from '../../types/charts/props';
14
+
15
+ /**
16
+ * High-level bar chart. Groups flat rows by `z` into one series per group
17
+ * and delegates all chrome to {@link Chart}. Styling lives entirely in
18
+ * `segments` — there's no separate colour shorthand, so a series' look is
19
+ * always defined in one place.
20
+ */
21
+ let {
22
+ width,
23
+ height,
24
+ title,
25
+ subtitle,
26
+ caption,
27
+ legend,
28
+ data,
29
+ x,
30
+ y,
31
+ z,
32
+ styles = {},
33
+ scales = {},
34
+ margins,
35
+ segments,
36
+ markers = [],
37
+ facet,
38
+ timeline,
39
+ tooltip,
40
+ }: ChartProps<TRow, BarSegmentStyle> = $props();
41
+
42
+ const tip = $derived(resolveTooltip(tooltip, 'x'));
43
+ const hoverEnabled = $derived(tip.enabled || hasHoverMarker(markers));
44
+ const resolvedStrategy = $derived(resolveHoverStrategy(tip.enabled ? tip.strategy : undefined, markers, 'x'));
45
+ const resolvedSync = $derived(resolveHoverSync(tip.enabled ? tip.sync : undefined, markers));
46
+ const getX = $derived(resolveAccessor<TRow>(x));
47
+ const getY = $derived(resolveAccessor<TRow>(y));
48
+ const getZ = $derived(z ? resolveAccessor<TRow>(z) : null);
49
+ const defaultFormat = $derived((row: TRow) => `${String(getX(row))}: ${getY(row)}`);
50
+
51
+ // A bar timeline snapshots one frame per step; interpolation needs to match
52
+ // each bar across steps by its category (`x`) — and by `z` too, when the
53
+ // chart groups categories into series — not by the timeline's own time field.
54
+ const timelineConfig = $derived(
55
+ timeline
56
+ ? {
57
+ ...timeline,
58
+ interpolate: resolveTimelineInterpolation(timeline.interpolate, {
59
+ value: y,
60
+ key: getZ ? (d: TRow) => `${String(getX(d))}::${String(getZ(d))}` : x,
61
+ }),
62
+ }
63
+ : undefined,
64
+ );
65
+ </script>
66
+
67
+ <Chart
68
+ {width}
69
+ {height}
70
+ {title}
71
+ {subtitle}
72
+ {caption}
73
+ {legend}
74
+ {data}
75
+ {facet}
76
+ timeline={timelineConfig}
77
+ hover={hoverEnabled ? { strategy: resolvedStrategy, sync: resolvedSync } : undefined}
78
+ >
79
+ {#snippet plot({ data: rows, width: w, height: h, facetValue })}
80
+ <BarPlot
81
+ width={w}
82
+ height={h}
83
+ data={rows}
84
+ {x}
85
+ {y}
86
+ {z}
87
+ {styles}
88
+ {scales}
89
+ {margins}
90
+ {segments}
91
+ {markers}
92
+ tooltip={tip.enabled
93
+ ? {
94
+ format: tip.format ?? defaultFormat,
95
+ facetId: facetValue ?? '',
96
+ strategy: tip.strategy,
97
+ sync: tip.sync,
98
+ series: tip.series,
99
+ content: tip.content,
100
+ }
101
+ : undefined}
102
+ />
103
+ {/snippet}
104
+ </Chart>
@@ -0,0 +1,27 @@
1
+ import Chart from '../Chart.svelte';
2
+ import type { BarSegmentStyle } from '../../types/plots/styles';
3
+ import type { ChartProps } from '../../types/charts/props';
4
+ declare function $$render<TRow extends Record<string, unknown>>(): {
5
+ props: ChartProps<TRow, BarSegmentStyle>;
6
+ exports: {};
7
+ bindings: "";
8
+ slots: {};
9
+ events: {};
10
+ };
11
+ declare class __sveltets_Render<TRow extends Record<string, unknown>> {
12
+ props(): ReturnType<typeof $$render<TRow>>['props'];
13
+ events(): ReturnType<typeof $$render<TRow>>['events'];
14
+ slots(): ReturnType<typeof $$render<TRow>>['slots'];
15
+ bindings(): "";
16
+ exports(): {};
17
+ }
18
+ interface $$IsomorphicComponent {
19
+ new <TRow extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TRow>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TRow>['props']>, ReturnType<__sveltets_Render<TRow>['events']>, ReturnType<__sveltets_Render<TRow>['slots']>> & {
20
+ $$bindings?: ReturnType<__sveltets_Render<TRow>['bindings']>;
21
+ } & ReturnType<__sveltets_Render<TRow>['exports']>;
22
+ <TRow extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TRow>['props']> & {}): ReturnType<__sveltets_Render<TRow>['exports']>;
23
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
24
+ }
25
+ declare const Chart: $$IsomorphicComponent;
26
+ type Chart<TRow extends Record<string, unknown>> = InstanceType<typeof Chart<TRow>>;
27
+ export default Chart;
@@ -0,0 +1,127 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { ChartBoxSnippet } from '../../types/charts/common';
4
+ import { getConfiguration, configToCssVars, cssVarsStyle } from '../../configuration/config.svelte';
5
+
6
+ /**
7
+ * The outermost chart frame. It owns the fixed footprint (`width`/`height`),
8
+ * lays out the header, optional legend and caption, and measures whatever
9
+ * space is left for the plot. The actual drawing is delegated to the `plot`
10
+ * snippet, which receives that measured box — so this component knows nothing
11
+ * about what kind of plot it wraps.
12
+ */
13
+ let {
14
+ width,
15
+ height,
16
+ title,
17
+ subtitle,
18
+ caption,
19
+ legend,
20
+ plot,
21
+ }: {
22
+ width: number;
23
+ height: number;
24
+ title?: string;
25
+ subtitle?: string;
26
+ caption?: string;
27
+ legend?: Snippet;
28
+ plot: ChartBoxSnippet;
29
+ } = $props();
30
+
31
+ let bodyWidth = $state(0);
32
+ let bodyHeight = $state(0);
33
+
34
+ // Stamp every `--dct-*` custom property on the root so the scoped styles below
35
+ // (and any nested plot/legend/timeline) resolve them from the live config.
36
+ const themeStyle = $derived(cssVarsStyle(configToCssVars(getConfiguration())));
37
+ </script>
38
+
39
+ <div class="dct-chart" style={themeStyle} style:width="{width}px" style:height="{height}px">
40
+ {#if title || subtitle}
41
+ <div class="dct-chart__header">
42
+ {#if title}<h3 class="dct-chart__title">{title}</h3>{/if}
43
+ {#if subtitle}<span class="dct-chart__subtitle">{subtitle}</span>{/if}
44
+ </div>
45
+ {/if}
46
+
47
+ {#if legend}
48
+ <div class="dct-chart__legend">{@render legend()}</div>
49
+ {/if}
50
+
51
+ <div
52
+ class="dct-chart__body"
53
+ bind:clientWidth={bodyWidth}
54
+ bind:clientHeight={bodyHeight}
55
+ >
56
+ {#if bodyWidth > 0 && bodyHeight > 0}
57
+ {@render plot({ width: bodyWidth, height: bodyHeight })}
58
+ {/if}
59
+ </div>
60
+
61
+ {#if caption}
62
+ <div class="dct-chart__caption">
63
+ <span>{caption}</span>
64
+ </div>
65
+ {/if}
66
+ </div>
67
+
68
+ <style>
69
+ .dct-chart {
70
+ display: flex;
71
+ flex-direction: column;
72
+ max-width: 100%;
73
+ max-height: 100%;
74
+ overflow: hidden;
75
+ color: var(--dct-fg, currentColor);
76
+ background: var(--dct-bg, transparent);
77
+ font-family: var(--dct-font, inherit);
78
+ }
79
+
80
+ .dct-chart__header {
81
+ flex: 0 0 auto;
82
+ padding: var(--dct-header-pad, 0.75rem 0 1rem);
83
+ border-bottom: 1px solid var(--dct-border, color-mix(in srgb, currentColor 15%, transparent));
84
+ }
85
+
86
+ .dct-chart__title {
87
+ margin: 0 0 0.4rem;
88
+ font-family: var(--dct-title-font, inherit);
89
+ font-size: var(--dct-title-size, 1.5rem);
90
+ font-weight: var(--dct-title-weight, 500);
91
+ line-height: var(--dct-title-leading, 1.2);
92
+ letter-spacing: var(--dct-title-spacing, normal);
93
+ color: var(--dct-title-color, inherit);
94
+ }
95
+
96
+ .dct-chart__subtitle {
97
+ font-family: var(--dct-subtitle-font, inherit);
98
+ font-size: var(--dct-subtitle-size, 1rem);
99
+ font-weight: var(--dct-subtitle-weight, 400);
100
+ line-height: var(--dct-subtitle-leading, 1.3);
101
+ letter-spacing: var(--dct-subtitle-spacing, normal);
102
+ color: var(--dct-subtitle-color, inherit);
103
+ }
104
+
105
+ .dct-chart__legend {
106
+ flex: 0 0 auto;
107
+ padding: var(--dct-legend-pad, 0.75rem 0);
108
+ }
109
+
110
+ .dct-chart__body {
111
+ flex: 1 1 auto;
112
+ min-height: 0;
113
+ min-width: 0;
114
+ }
115
+
116
+ .dct-chart__caption {
117
+ flex: 0 0 auto;
118
+ padding: var(--dct-caption-pad, 0.5rem 0);
119
+ font-family: var(--dct-caption-font, inherit);
120
+ font-size: var(--dct-caption-size, 0.75rem);
121
+ font-weight: var(--dct-caption-weight, 400);
122
+ line-height: var(--dct-caption-leading, 1.3);
123
+ letter-spacing: var(--dct-caption-spacing, normal);
124
+ color: var(--dct-caption-color, inherit);
125
+ white-space: pre-line;
126
+ }
127
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ChartBoxSnippet } from '../../types/charts/common';
3
+ type $$ComponentProps = {
4
+ width: number;
5
+ height: number;
6
+ title?: string;
7
+ subtitle?: string;
8
+ caption?: string;
9
+ legend?: Snippet;
10
+ plot: ChartBoxSnippet;
11
+ };
12
+ declare const ChartContainer: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type ChartContainer = ReturnType<typeof ChartContainer>;
14
+ export default ChartContainer;