@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.
- package/README.md +250 -0
- package/dist/charts/Chart.svelte +125 -0
- package/dist/charts/Chart.svelte.d.ts +42 -0
- package/dist/charts/bar/Chart.svelte +104 -0
- package/dist/charts/bar/Chart.svelte.d.ts +27 -0
- package/dist/charts/container/ChartContainer.svelte +127 -0
- package/dist/charts/container/ChartContainer.svelte.d.ts +14 -0
- package/dist/charts/heatmap/Chart.svelte +107 -0
- package/dist/charts/heatmap/Chart.svelte.d.ts +30 -0
- package/dist/charts/line/Chart.svelte +106 -0
- package/dist/charts/line/Chart.svelte.d.ts +27 -0
- package/dist/charts/pyramid/Chart.svelte +127 -0
- package/dist/charts/pyramid/Chart.svelte.d.ts +31 -0
- package/dist/configuration/config.svelte.d.ts +12 -0
- package/dist/configuration/config.svelte.js +159 -0
- package/dist/configuration/themes/compact.d.ts +2 -0
- package/dist/configuration/themes/compact.js +9 -0
- package/dist/configuration/themes/dark.d.ts +2 -0
- package/dist/configuration/themes/dark.js +17 -0
- package/dist/configuration/themes/default.d.ts +3 -0
- package/dist/configuration/themes/default.js +2 -0
- package/dist/configuration/themes/editorial.d.ts +2 -0
- package/dist/configuration/themes/editorial.js +14 -0
- package/dist/configuration/themes/index.d.ts +481 -0
- package/dist/configuration/themes/index.js +16 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +27 -0
- package/dist/layout/facet/FacetLayout.svelte +97 -0
- package/dist/layout/facet/FacetLayout.svelte.d.ts +35 -0
- package/dist/layout/legend/ContinuousSection.svelte +95 -0
- package/dist/layout/legend/ContinuousSection.svelte.d.ts +7 -0
- package/dist/layout/legend/DiscreteSection.svelte +87 -0
- package/dist/layout/legend/DiscreteSection.svelte.d.ts +7 -0
- package/dist/layout/legend/LegendLayout.svelte +47 -0
- package/dist/layout/legend/LegendLayout.svelte.d.ts +7 -0
- package/dist/layout/plot/PlotLayout.svelte +236 -0
- package/dist/layout/plot/PlotLayout.svelte.d.ts +70 -0
- package/dist/layout/timeline/Timeline.svelte +86 -0
- package/dist/layout/timeline/Timeline.svelte.d.ts +11 -0
- package/dist/layout/timeline/TimelineLayout.svelte +181 -0
- package/dist/layout/timeline/TimelineLayout.svelte.d.ts +38 -0
- package/dist/layout/tooltip/Tooltip.svelte +55 -0
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +12 -0
- package/dist/layout/tooltip/controller.svelte.d.ts +52 -0
- package/dist/layout/tooltip/controller.svelte.js +128 -0
- package/dist/layout/tooltip/hover.svelte.d.ts +7 -0
- package/dist/layout/tooltip/hover.svelte.js +28 -0
- package/dist/layout/tooltip/utils.d.ts +41 -0
- package/dist/layout/tooltip/utils.js +78 -0
- package/dist/plots/bar/Plot.svelte +176 -0
- package/dist/plots/bar/Plot.svelte.d.ts +26 -0
- package/dist/plots/heatmap/Plot.svelte +175 -0
- package/dist/plots/heatmap/Plot.svelte.d.ts +26 -0
- package/dist/plots/line/Plot.svelte +232 -0
- package/dist/plots/line/Plot.svelte.d.ts +26 -0
- package/dist/plots/markers/DeltaMarker.svelte +104 -0
- package/dist/plots/markers/DeltaMarker.svelte.d.ts +30 -0
- package/dist/plots/markers/DotMarker.svelte +41 -0
- package/dist/plots/markers/DotMarker.svelte.d.ts +31 -0
- package/dist/plots/markers/HoverMarker.svelte +78 -0
- package/dist/plots/markers/HoverMarker.svelte.d.ts +22 -0
- package/dist/plots/markers/PeakMarker.svelte +80 -0
- package/dist/plots/markers/PeakMarker.svelte.d.ts +13 -0
- package/dist/plots/markers/TextMarker.svelte +55 -0
- package/dist/plots/markers/TextMarker.svelte.d.ts +32 -0
- package/dist/plots/pyramid/Plot.svelte +282 -0
- package/dist/plots/pyramid/Plot.svelte.d.ts +26 -0
- package/dist/plots/utils/accessors.d.ts +6 -0
- package/dist/plots/utils/accessors.js +10 -0
- package/dist/plots/utils/delta.d.ts +12 -0
- package/dist/plots/utils/delta.js +69 -0
- package/dist/plots/utils/segments.d.ts +33 -0
- package/dist/plots/utils/segments.js +153 -0
- package/dist/stores/device.d.ts +7 -0
- package/dist/stores/device.js +39 -0
- package/dist/types/charts/common.d.ts +64 -0
- package/dist/types/charts/common.js +1 -0
- package/dist/types/charts/interpolate.d.ts +2 -0
- package/dist/types/charts/interpolate.js +1 -0
- package/dist/types/charts/legend.d.ts +29 -0
- package/dist/types/charts/legend.js +1 -0
- package/dist/types/charts/props.d.ts +35 -0
- package/dist/types/charts/props.js +1 -0
- package/dist/types/configuration/styling.d.ts +97 -0
- package/dist/types/configuration/styling.js +1 -0
- package/dist/types/layout/tooltip.d.ts +88 -0
- package/dist/types/layout/tooltip.js +1 -0
- package/dist/types/plots/common.d.ts +100 -0
- package/dist/types/plots/common.js +1 -0
- package/dist/types/plots/delta.d.ts +19 -0
- package/dist/types/plots/delta.js +1 -0
- package/dist/types/plots/markers.d.ts +56 -0
- package/dist/types/plots/markers.js +1 -0
- package/dist/types/plots/props.d.ts +23 -0
- package/dist/types/plots/props.js +1 -0
- package/dist/types/plots/styles.d.ts +176 -0
- package/dist/types/plots/styles.js +1 -0
- package/dist/types/viewport.d.ts +4 -0
- package/dist/types/viewport.js +1 -0
- package/dist/utils/color.d.ts +5 -0
- package/dist/utils/color.js +42 -0
- package/dist/utils/grouping.d.ts +14 -0
- package/dist/utils/grouping.js +36 -0
- package/dist/utils/interpolate.d.ts +30 -0
- package/dist/utils/interpolate.js +71 -0
- 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;
|