@fundar/data-chart-telling 0.0.4 → 0.0.6
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 +63 -28
- package/dist/charts/heatmap/Chart.svelte +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/plots/heatmap/Plot.svelte +7 -4
- package/dist/plots/utils/segments.d.ts +12 -14
- package/dist/plots/utils/segments.js +49 -24
- package/dist/types/charts/props.d.ts +2 -2
- package/dist/types/plots/props.d.ts +2 -2
- package/dist/types/plots/styles.d.ts +26 -15
- package/package.json +75 -76
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ Plots accept either `series` (pre-built `Series[]`) or the `data` + `x`/`y`/`z`
|
|
|
116
116
|
| `x` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
|
|
117
117
|
| `y` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
|
|
118
118
|
| `z` | `Accessor` | — | ✅ | ✅ | ✅ | ✅ |
|
|
119
|
-
| `segments` | `
|
|
119
|
+
| `segments` | `Segments<TStyle>` | `[]` | ✅ | ✅ | ✅ | ✅ |
|
|
120
120
|
| `markers` | `Marker[]` | `[]` | ✅ | ✅ | ✅ | ✅ |
|
|
121
121
|
| `scales` | `ScalesConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
|
|
122
122
|
| `margins` | `MarginConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
|
|
@@ -158,7 +158,7 @@ Plots accept either `series` (pre-built `Series[]`) or the `data` + `x`/`y`/`z`
|
|
|
158
158
|
| `subtitle` | `string` | | ✅ | ✅ | ✅ | ✅ |
|
|
159
159
|
| `caption` | `string` | | ✅ | ✅ | ✅ | ✅ |
|
|
160
160
|
| `legend` | `LegendSection[]` | | ✅ | ✅ | ✅ | ✅ |
|
|
161
|
-
| `segments` | `
|
|
161
|
+
| `segments` | `Segments<TStyle>` | `[]` | ✅ | ✅ | ✅ | ✅ |
|
|
162
162
|
| `markers` | `Marker[]` | `[]` | ✅ | ✅ | ✅ | ✅ |
|
|
163
163
|
| `scales` | `ScalesConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
|
|
164
164
|
| `margins` | `MarginConfig` | `{}` | ✅ | ✅ | ✅ | ✅ |
|
|
@@ -177,49 +177,80 @@ The `segments` prop separates data ranges from visual styling. Every plot kind s
|
|
|
177
177
|
|
|
178
178
|
| Plot | `segments` type |
|
|
179
179
|
| ---- | --------------- |
|
|
180
|
-
| `LinePlot` / `LineChart` | `
|
|
181
|
-
| `BarPlot` / `BarChart` | `
|
|
182
|
-
| `PyramidPlot` / `PyramidChart` | `
|
|
183
|
-
| `HeatmapPlot` / `HeatmapChart` | `
|
|
180
|
+
| `LinePlot` / `LineChart` | `Segments<LineSegmentStyle>` |
|
|
181
|
+
| `BarPlot` / `BarChart` | `Segments<BarSegmentStyle>` |
|
|
182
|
+
| `PyramidPlot` / `PyramidChart` | `Segments<BarSegmentStyle>` |
|
|
183
|
+
| `HeatmapPlot` / `HeatmapChart` | `Segments<CellSegmentStyle>` (`'default'` key only) |
|
|
184
184
|
|
|
185
185
|
#### Core types
|
|
186
186
|
|
|
187
187
|
```ts
|
|
188
|
-
type Range
|
|
189
|
-
type Area
|
|
188
|
+
type Range = { from?: AxisValue; to?: AxisValue }; // inclusive on both sides
|
|
189
|
+
type Area = { x?: Range; y?: Range }; // 2-D rectangular region
|
|
190
190
|
type Segment<TStyle> = { areas?: Area[]; style?: TStyle };
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
A **`Segment`** matches a data point (or heatmap cell) when the point falls inside **any** of its `areas`. Omitting `areas`
|
|
193
|
+
A **`Segment`** matches a data point (or heatmap cell) when the point falls inside **any** of its `areas`. Omitting `areas` creates a **catch-all** that matches every data point — but catch-alls are always evaluated **last**, regardless of their position in the list, so area-specific segments always win. This lets a catch-all placed first act as a fallback baseline that highlighted ranges override.
|
|
194
194
|
|
|
195
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
196
|
|
|
197
|
-
#### `
|
|
197
|
+
#### `Segments` structure
|
|
198
|
+
|
|
199
|
+
`segments` is always an **array of groups**. Each group is a plain object whose keys map to `Segment[]` lists:
|
|
198
200
|
|
|
199
201
|
```ts
|
|
200
|
-
//
|
|
201
|
-
type
|
|
202
|
+
// 'default' key — applies its segments to every series
|
|
203
|
+
type DefaultSegmentsMap<TStyle> = { default: Segment<TStyle>[] };
|
|
204
|
+
|
|
205
|
+
// named keys — applies segments only to the named series
|
|
206
|
+
type SeriesAwareSegmentsMap<TStyle> = Record<string, Segment<TStyle>[]>;
|
|
202
207
|
|
|
203
|
-
//
|
|
204
|
-
type
|
|
205
|
-
|
|
208
|
+
// one element of the array — either form, or a mix of both key types
|
|
209
|
+
type SegmentsStyle<TStyle> = DefaultSegmentsMap<TStyle> | SeriesAwareSegmentsMap<TStyle>;
|
|
210
|
+
|
|
211
|
+
// the prop type: an ordered list of groups
|
|
212
|
+
type Segments<TStyle> = SegmentsStyle<TStyle>[];
|
|
206
213
|
```
|
|
207
214
|
|
|
215
|
+
Having multiple groups lets you organise segments by concern — for example one group for per-series stroke colours and a second for shared dot markers applied via `'default'`. Groups are processed in order and their resolved segments are concatenated per series before evaluation.
|
|
216
|
+
|
|
208
217
|
#### Examples
|
|
209
218
|
|
|
210
|
-
**Line chart — colour a
|
|
219
|
+
**Line chart — baseline colour with a highlighted range (catch-all as fallback):**
|
|
211
220
|
|
|
212
221
|
```svelte
|
|
213
222
|
<LinePlot
|
|
214
223
|
{width} {height}
|
|
215
224
|
series={[{ name: 'gdp', x: 'year', y: 'value', data: GDP }]}
|
|
216
|
-
segments={
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
segments={[
|
|
226
|
+
{
|
|
227
|
+
gdp: [
|
|
228
|
+
{ style: { stroke: { stroke: '#94a3b8', strokeWidth: 1 } } },
|
|
229
|
+
{ areas: [{ x: { from: 2008, to: 2010 } }],
|
|
230
|
+
style: { stroke: { stroke: '#ef4444', strokeWidth: 3 } } },
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
]}
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Line chart — per-series baseline colours + shared dot markers (two-group approach):**
|
|
238
|
+
|
|
239
|
+
```svelte
|
|
240
|
+
<LinePlot
|
|
241
|
+
{width} {height}
|
|
242
|
+
data={LIFE_EXPECTANCY} x="year" y="value" z="sex"
|
|
243
|
+
segments={[
|
|
244
|
+
// Group 1: per-series catch-all stroke colours
|
|
245
|
+
{ female: [{ style: { stroke: { stroke: 'steelblue' } } }],
|
|
246
|
+
male: [{ style: { stroke: { stroke: 'tomato' } } }] },
|
|
247
|
+
// Group 2: dots for all series in a specific range
|
|
248
|
+
{ default: [
|
|
249
|
+
{ areas: [{ x: { from: 2010, to: 2012 } }],
|
|
250
|
+
style: { dots: { dotRadius: 5, dotSymbol: 'circle' } } },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
]}
|
|
223
254
|
/>
|
|
224
255
|
```
|
|
225
256
|
|
|
@@ -229,15 +260,19 @@ type SegmentsMap<TStyle> = SeriesAwareSegmentsMap<TStyle>;
|
|
|
229
260
|
<HeatmapChart
|
|
230
261
|
{width} {height} {data} {x} {y} {z}
|
|
231
262
|
segments={[
|
|
232
|
-
{
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
263
|
+
{
|
|
264
|
+
default: [
|
|
265
|
+
{ areas: [{ x: { from: 'Q1', to: 'Q1' } }, { x: { from: 'Q3', to: 'Q3' } }],
|
|
266
|
+
style: { stroke: '#f59e0b', strokeWidth: 2 } },
|
|
267
|
+
{ areas: [{ x: { from: 'Q2', to: 'Q2' }, y: { from: 1, to: 2 } }],
|
|
268
|
+
style: { fill: '#ef4444', fillOpacity: 0.8 } },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
236
271
|
]}
|
|
237
272
|
/>
|
|
238
273
|
```
|
|
239
274
|
|
|
240
|
-
Omitting `segments` renders every mark in its default palette colour.
|
|
275
|
+
Omitting `segments` (or passing `[]`) renders every mark in its default palette colour.
|
|
241
276
|
|
|
242
277
|
---
|
|
243
278
|
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { groupBy, buildSeries } from './utils/grouping';
|
|
|
26
26
|
export { makeColorScale, normalize, resolveCssColor, paletteColor } from './utils/color';
|
|
27
27
|
export type { AxisValue, Accessor, AxisScale, ScalesConfig, Series, SeriesProps, DataProps } from './types/plots/common';
|
|
28
28
|
export type { DeltaConfig, } from './types/plots/delta';
|
|
29
|
-
export type { SymbolType, LineStyle, StrokeStyle, DotStyle, FontStyle, LineSegmentStyle, BarSegmentStyle, CellSegmentStyle, Range, Area, Segment, SeriesAwareSegmentsMap,
|
|
29
|
+
export type { SymbolType, LineStyle, StrokeStyle, DotStyle, FontStyle, LineSegmentStyle, BarSegmentStyle, CellSegmentStyle, Range, Area, Segment, SeriesAwareSegmentsMap, DefaultSegmentsMap, SegmentsStyle, Segments, ValueAnchor, ValuesStyle, ColorsStyle, PlotStyles, } from './types/plots/styles';
|
|
30
30
|
export type { Marker, MarkerProps } from './types/plots/markers';
|
|
31
31
|
export type { PlotProps } from './types/plots/props';
|
|
32
32
|
export type { TimeValue, Orientation, FacetColumns, FacetConfig, TimelineConfig, ChartPlotContext, ChartPlotSnippet, } from './types/charts/common';
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
y,
|
|
28
28
|
z,
|
|
29
29
|
styles = {},
|
|
30
|
-
segments =
|
|
30
|
+
segments = {},
|
|
31
31
|
scales = {},
|
|
32
32
|
margins,
|
|
33
33
|
markers = [],
|
|
@@ -43,9 +43,12 @@
|
|
|
43
43
|
'HeatmapPlot: markers scoped to a `series` are ignored — heatmaps have no series concept.',
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
const seriesKeys = segments.flatMap((g) =>
|
|
47
|
+
Object.keys(g as Record<string, unknown>).filter((k) => k !== 'default'),
|
|
48
|
+
);
|
|
49
|
+
if (seriesKeys.length > 0) {
|
|
47
50
|
console.warn(
|
|
48
|
-
|
|
51
|
+
`HeatmapPlot: segment groups have series-specific keys (${[...new Set(seriesKeys)].join(', ')}) — heatmaps have no named series. Use the 'default' key instead.`,
|
|
49
52
|
);
|
|
50
53
|
}
|
|
51
54
|
});
|
|
@@ -87,7 +90,7 @@
|
|
|
87
90
|
makeColorScale(valueDomain[0], valueDomain[1], minColor, maxColor),
|
|
88
91
|
);
|
|
89
92
|
|
|
90
|
-
const segList = $derived(
|
|
93
|
+
const segList = $derived(segments.flatMap((g) => (g as Record<string, CellSegmentStyle[]>)['default'] ?? []));
|
|
91
94
|
|
|
92
95
|
const segStyleMap = $derived.by(() => {
|
|
93
96
|
const map = new Map<string, CellSegmentStyle>();
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { AxisValue, Series } from '../../types/plots/common';
|
|
2
|
-
import type { Segment,
|
|
3
|
-
/** Narrows a {@link SegmentsMap} to its per-series record form. */
|
|
4
|
-
export declare function isSeriesAware<TStyle>(segments: SegmentsMap<TStyle>): segments is SeriesAwareSegmentsMap<TStyle>;
|
|
2
|
+
import type { Segment, Segments, LineSegmentStyle, VisualGroup } from '../../types/plots/styles';
|
|
5
3
|
/**
|
|
6
|
-
* Returns the {@link Segment} list
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Returns the flat {@link Segment} list for one series.
|
|
5
|
+
* Series-specific segments are added before `'default'` segments so that
|
|
6
|
+
* series-specific catch-alls win over default catch-alls on tie-breaks.
|
|
9
7
|
*/
|
|
10
|
-
export declare function resolveSegmentsForSeries<TStyle>(segments:
|
|
8
|
+
export declare function resolveSegmentsForSeries<TStyle>(segments: Segments<TStyle>, seriesName: string): Segment<TStyle>[];
|
|
11
9
|
/** Returns true when `xVal` falls inside any of a segment's x ranges. */
|
|
12
10
|
export declare function matchesSegmentX<TStyle>(xVal: AxisValue, seg: Segment<TStyle>): boolean;
|
|
13
11
|
/**
|
|
@@ -20,14 +18,14 @@ export declare function matchesSegmentX<TStyle>(xVal: AxisValue, seg: Segment<TS
|
|
|
20
18
|
*/
|
|
21
19
|
export declare function getDataForSegments<TData, TStyle>(data: TData[], xAccessor: (d: TData) => AxisValue, segs: Segment<TStyle>[]): TData[][];
|
|
22
20
|
/**
|
|
23
|
-
* Warns about obvious configuration mistakes:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* Warns about obvious configuration mistakes: multiple catch-all segments
|
|
22
|
+
* (no `areas`) in the same series list. Since catch-alls are evaluated last
|
|
23
|
+
* and in order, only the first one can ever match; later ones are dead code.
|
|
26
24
|
*/
|
|
27
|
-
export declare function validateSegments<TStyle>(segments:
|
|
25
|
+
export declare function validateSegments<TStyle>(segments: Segments<TStyle>): void;
|
|
28
26
|
/**
|
|
29
27
|
* Builds the full list of visual groups — one per series — ready to render.
|
|
30
|
-
* Each group's `visualSegments` is derived
|
|
31
|
-
*
|
|
28
|
+
* Each group's `visualSegments` is derived by resolving the flat segment list
|
|
29
|
+
* for that series from all groups in `segments`.
|
|
32
30
|
*/
|
|
33
|
-
export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments:
|
|
31
|
+
export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments: Segments<TStyle>): VisualGroup<S, TStyle>[];
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { resolveAccessor } from './accessors';
|
|
2
|
-
|
|
3
|
-
export function isSeriesAware(segments) {
|
|
4
|
-
return !Array.isArray(segments);
|
|
5
|
-
}
|
|
2
|
+
const DEFAULT_KEY = 'default';
|
|
6
3
|
/**
|
|
7
|
-
* Returns the {@link Segment} list
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Returns the flat {@link Segment} list for one series.
|
|
5
|
+
* Series-specific segments are added before `'default'` segments so that
|
|
6
|
+
* series-specific catch-alls win over default catch-alls on tie-breaks.
|
|
10
7
|
*/
|
|
11
8
|
export function resolveSegmentsForSeries(segments, seriesName) {
|
|
12
|
-
|
|
9
|
+
const specific = segments[seriesName] ?? [];
|
|
10
|
+
const defaults = segments[DEFAULT_KEY] ?? [];
|
|
11
|
+
return [...specific, ...defaults];
|
|
13
12
|
}
|
|
14
13
|
/** Returns true when `xVal` falls inside any of a segment's x ranges. */
|
|
15
14
|
export function matchesSegmentX(xVal, seg) {
|
|
@@ -53,25 +52,20 @@ export function getDataForSegments(data, xAccessor, segs) {
|
|
|
53
52
|
});
|
|
54
53
|
}
|
|
55
54
|
/**
|
|
56
|
-
* Warns about obvious configuration mistakes:
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* Warns about obvious configuration mistakes: multiple catch-all segments
|
|
56
|
+
* (no `areas`) in the same series list. Since catch-alls are evaluated last
|
|
57
|
+
* and in order, only the first one can ever match; later ones are dead code.
|
|
59
58
|
*/
|
|
60
59
|
export function validateSegments(segments) {
|
|
61
60
|
const check = (label, segs) => {
|
|
62
61
|
const catchAlls = segs.filter((s) => !s.areas || s.areas.length === 0).length;
|
|
63
62
|
if (catchAlls > 1) {
|
|
64
63
|
console.warn(`segments ${label}: ${catchAlls} entries have no areas — ` +
|
|
65
|
-
`
|
|
64
|
+
`only the first catch-all's style will be used.`);
|
|
66
65
|
}
|
|
67
66
|
};
|
|
68
|
-
|
|
69
|
-
check('(
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
for (const [name, segs] of Object.entries(segments)) {
|
|
73
|
-
check(`"${name}"`, segs);
|
|
74
|
-
}
|
|
67
|
+
for (const [key, segs] of Object.entries(segments)) {
|
|
68
|
+
check(key === DEFAULT_KEY ? '(default)' : `"${key}"`, segs);
|
|
75
69
|
}
|
|
76
70
|
}
|
|
77
71
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -81,16 +75,29 @@ function resolveSeriesSegments(series, segs) {
|
|
|
81
75
|
}
|
|
82
76
|
const getX = resolveAccessor(series.x);
|
|
83
77
|
const getY = resolveAccessor(series.y);
|
|
78
|
+
// Evaluate area-specific segments before catch-alls (no areas), preserving
|
|
79
|
+
// relative order within each group. This lets a catch-all placed first in
|
|
80
|
+
// the list act as a fallback baseline rather than matching everything.
|
|
81
|
+
const evalOrder = segs
|
|
82
|
+
.map((seg, i) => ({ seg, i }))
|
|
83
|
+
.sort((a, b) => {
|
|
84
|
+
const aSpecific = a.seg.areas != null && a.seg.areas.length > 0;
|
|
85
|
+
const bSpecific = b.seg.areas != null && b.seg.areas.length > 0;
|
|
86
|
+
if (aSpecific === bSpecific)
|
|
87
|
+
return a.i - b.i;
|
|
88
|
+
return aSpecific ? -1 : 1;
|
|
89
|
+
});
|
|
84
90
|
// For each data point, find the first matching segment index (-1 = unmatched).
|
|
85
91
|
const matchIdx = series.data.map((d) => {
|
|
86
92
|
const xVal = getX(d);
|
|
87
93
|
const yVal = getY(d);
|
|
88
|
-
|
|
94
|
+
const found = evalOrder.find(({ seg }) => {
|
|
89
95
|
const areas = seg.areas;
|
|
90
96
|
if (!areas || areas.length === 0)
|
|
91
97
|
return true;
|
|
92
98
|
return areas.some((area) => areaMatchesXY(xVal, yVal, area));
|
|
93
99
|
});
|
|
100
|
+
return found !== undefined ? found.i : -1;
|
|
94
101
|
});
|
|
95
102
|
const runs = [];
|
|
96
103
|
for (let i = 0; i < series.data.length; i++) {
|
|
@@ -103,6 +110,16 @@ function resolveSeriesSegments(series, segs) {
|
|
|
103
110
|
runs.push({ data: [series.data[i]], segIdx: idx });
|
|
104
111
|
}
|
|
105
112
|
}
|
|
113
|
+
// Connector lines need to match the catch-all's stroke so they don't reveal
|
|
114
|
+
// the default palette color when the user has set a baseline via catch-all.
|
|
115
|
+
// Dots are excluded to avoid duplicate marks at the shared boundary points.
|
|
116
|
+
const catchAll = segs.find((s) => !s.areas || s.areas.length === 0);
|
|
117
|
+
const connectorStyle = (() => {
|
|
118
|
+
if (!catchAll?.style)
|
|
119
|
+
return {};
|
|
120
|
+
const { dots: _, ...rest } = catchAll.style;
|
|
121
|
+
return rest;
|
|
122
|
+
})();
|
|
106
123
|
const defaultVisual = [];
|
|
107
124
|
const connectors = [];
|
|
108
125
|
const userVisual = [];
|
|
@@ -120,7 +137,15 @@ function resolveSeriesSegments(series, segs) {
|
|
|
120
137
|
defaultVisual.push({ data: bridged, style: {} });
|
|
121
138
|
}
|
|
122
139
|
else {
|
|
123
|
-
|
|
140
|
+
const seg = segs[run.segIdx];
|
|
141
|
+
const segStyle = seg.style ?? {};
|
|
142
|
+
// Area-specific segments merge the catch-all as a baseline: catch-all
|
|
143
|
+
// keys fill in whatever the area segment omits; the area wins on conflict.
|
|
144
|
+
const isAreaSpecific = seg.areas != null && seg.areas.length > 0;
|
|
145
|
+
const resolvedStyle = isAreaSpecific && catchAll?.style
|
|
146
|
+
? { ...catchAll.style, ...segStyle }
|
|
147
|
+
: segStyle;
|
|
148
|
+
userVisual.push({ data: run.data, style: resolvedStyle });
|
|
124
149
|
// When the next run is also a user segment (no unmatched data between them),
|
|
125
150
|
// insert a line-only connector so the lines join without a gap. Using a
|
|
126
151
|
// separate mark (rather than appending a bridge point to the current run)
|
|
@@ -129,7 +154,7 @@ function resolveSeriesSegments(series, segs) {
|
|
|
129
154
|
if (nextRun && nextRun.segIdx !== -1) {
|
|
130
155
|
connectors.push({
|
|
131
156
|
data: [run.data[run.data.length - 1], nextRun.data[0]],
|
|
132
|
-
style:
|
|
157
|
+
style: connectorStyle,
|
|
133
158
|
});
|
|
134
159
|
}
|
|
135
160
|
}
|
|
@@ -142,8 +167,8 @@ function resolveSeriesSegments(series, segs) {
|
|
|
142
167
|
}
|
|
143
168
|
/**
|
|
144
169
|
* Builds the full list of visual groups — one per series — ready to render.
|
|
145
|
-
* Each group's `visualSegments` is derived
|
|
146
|
-
*
|
|
170
|
+
* Each group's `visualSegments` is derived by resolving the flat segment list
|
|
171
|
+
* for that series from all groups in `segments`.
|
|
147
172
|
*/
|
|
148
173
|
export function buildGroupedSeries(seriesList, segments) {
|
|
149
174
|
return seriesList.map((s) => ({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Accessor, AxisValue, ScalesConfig, MarginConfig } from '../plots/common';
|
|
2
|
-
import type { PlotStyles,
|
|
2
|
+
import type { PlotStyles, Segments } from '../plots/styles';
|
|
3
3
|
import type { Marker } from '../plots/markers';
|
|
4
4
|
import type { TooltipProp } from '../layout/tooltip';
|
|
5
5
|
import type { LegendSection } from './legend';
|
|
@@ -25,7 +25,7 @@ export type ChartProps<TRow extends Record<string, unknown>, TSegmentStyle exten
|
|
|
25
25
|
y: Accessor<TRow, AxisValue>;
|
|
26
26
|
z?: Accessor<TRow, unknown>;
|
|
27
27
|
styles?: PlotStyles;
|
|
28
|
-
segments?:
|
|
28
|
+
segments?: Segments<TSegmentStyle>;
|
|
29
29
|
markers?: Marker[];
|
|
30
30
|
scales?: ScalesConfig;
|
|
31
31
|
margins?: MarginConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Marker } from './markers';
|
|
2
2
|
import type { TooltipRuntime } from '../layout/tooltip';
|
|
3
|
-
import type { PlotStyles,
|
|
3
|
+
import type { PlotStyles, Segments } from './styles';
|
|
4
4
|
import type { SeriesProps, DataProps, ScalesConfig, MarginConfig } from './common';
|
|
5
5
|
/**
|
|
6
6
|
* Common props accepted by every plot component (bar, line, heatmap, pyramid,
|
|
@@ -15,7 +15,7 @@ export type PlotProps<TData extends Record<string, unknown>, TSegmentStyle exten
|
|
|
15
15
|
width: number;
|
|
16
16
|
height: number;
|
|
17
17
|
styles?: PlotStyles;
|
|
18
|
-
segments?:
|
|
18
|
+
segments?: Segments<TSegmentStyle>;
|
|
19
19
|
markers?: Marker[];
|
|
20
20
|
scales?: ScalesConfig;
|
|
21
21
|
margins?: MarginConfig;
|
|
@@ -134,30 +134,41 @@ export type Area = {
|
|
|
134
134
|
/**
|
|
135
135
|
* One styled rule for a plot. A data point (or cell) is styled when it falls
|
|
136
136
|
* inside **any** of the listed `areas`. Omitting `areas` (or passing an empty
|
|
137
|
-
* array) makes the segment
|
|
138
|
-
* `TStyle` is the per-mark shape ({@link LineSegmentStyle},
|
|
139
|
-
* {@link CellSegmentStyle}, …).
|
|
137
|
+
* array) makes the segment a catch-all that matches every data point — useful
|
|
138
|
+
* as a baseline style. `TStyle` is the per-mark shape ({@link LineSegmentStyle},
|
|
139
|
+
* {@link BarSegmentStyle}, {@link CellSegmentStyle}, …).
|
|
140
140
|
*
|
|
141
|
-
*
|
|
141
|
+
* Catch-alls are evaluated last so that area-specific segments always take
|
|
142
|
+
* priority, regardless of their position in the list. A catch-all placed first
|
|
143
|
+
* therefore acts as a fallback baseline that area-specific segments override.
|
|
142
144
|
*/
|
|
143
145
|
export type Segment<TStyle> = {
|
|
144
146
|
areas?: Area[];
|
|
145
147
|
style?: TStyle;
|
|
146
148
|
};
|
|
147
|
-
/** Per-series list of {@link Segment}s, keyed by series name. */
|
|
148
|
-
export type SeriesAwareSegmentsMap<TStyle> = Record<string, Segment<TStyle>[]>;
|
|
149
149
|
/**
|
|
150
|
-
*
|
|
150
|
+
* The `segments` prop accepted by every plot kind.
|
|
151
151
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
152
|
+
* A record mapping series names (or `'default'` for all series) to lists of
|
|
153
|
+
* {@link Segment}s. Segments in a named key apply only to that series;
|
|
154
|
+
* segments under `'default'` apply to every series.
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* segments: {
|
|
158
|
+
* female: [{ style: { stroke: { stroke: 'tomato' } } }], // catch-all for female
|
|
159
|
+
* male: [{ style: { stroke: { stroke: 'steelblue' } } }], // catch-all for male
|
|
160
|
+
* default: [{ areas: [{ x: { from: 2012, to: 2014 } }], style: { dots: { dotSymbol: 'circle' } } }],
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* Within each resolved series list, catch-alls (no `areas`) are evaluated last
|
|
165
|
+
* so that area-specific segments always override them, regardless of position.
|
|
166
|
+
* Area-specific segments also inherit any style key absent from their own style
|
|
167
|
+
* by merging the catch-all as a baseline — the area's own style wins on conflict.
|
|
168
|
+
* This lets you set a series colour once in a catch-all and add per-range dots
|
|
169
|
+
* or dash patterns without repeating the colour on every area segment.
|
|
159
170
|
*/
|
|
160
|
-
export type
|
|
171
|
+
export type Segments<TStyle> = Record<string, Segment<TStyle>[]>;
|
|
161
172
|
/** One contiguous, styled slice of a series' data — ready to draw as its own mark. */
|
|
162
173
|
export type VisualSegment<T extends Record<string, unknown>, TStyle = LineSegmentStyle> = {
|
|
163
174
|
data: T[];
|
package/package.json
CHANGED
|
@@ -1,77 +1,76 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
2
|
+
"name": "@fundar/data-chart-telling",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"sideEffects": [
|
|
9
|
+
"**/*.css"
|
|
10
|
+
],
|
|
11
|
+
"svelte": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"svelte": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"d3-interpolate": "^3.0.1"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"svelte": "^5.0.0",
|
|
25
|
+
"svelteplot": "^0.14.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@eslint/compat": "^2.0.4",
|
|
29
|
+
"@eslint/js": "^10.0.1",
|
|
30
|
+
"@sveltejs/package": "^2.5.7",
|
|
31
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
32
|
+
"@types/d3-interpolate": "^3.0.4",
|
|
33
|
+
"@types/node": "^22",
|
|
34
|
+
"@vitest/browser": "^4.1.7",
|
|
35
|
+
"@vitest/browser-playwright": "^4.1.7",
|
|
36
|
+
"eslint": "^10.2.0",
|
|
37
|
+
"eslint-config-prettier": "^10.1.8",
|
|
38
|
+
"eslint-plugin-svelte": "^3.17.0",
|
|
39
|
+
"globals": "^17.4.0",
|
|
40
|
+
"playwright": "^1.60.0",
|
|
41
|
+
"prettier": "^3.8.1",
|
|
42
|
+
"prettier-plugin-svelte": "^3.5.1",
|
|
43
|
+
"publint": "^0.3.18",
|
|
44
|
+
"svelte": "^5.55.2",
|
|
45
|
+
"svelte-check": "^4.4.6",
|
|
46
|
+
"svelteplot": "^0.14.0",
|
|
47
|
+
"typescript": "^6.0.2",
|
|
48
|
+
"typescript-eslint": "^8.58.1",
|
|
49
|
+
"vite": "^8.0.7",
|
|
50
|
+
"vitest": "^4.1.3",
|
|
51
|
+
"vitest-browser-svelte": "^2.1.1",
|
|
52
|
+
"storybook": "^10.4.1",
|
|
53
|
+
"@storybook/svelte-vite": "^10.4.1",
|
|
54
|
+
"@storybook/addon-svelte-csf": "^5.1.2",
|
|
55
|
+
"@chromatic-com/storybook": "^5.2.1",
|
|
56
|
+
"@storybook/addon-vitest": "^10.4.1",
|
|
57
|
+
"@storybook/addon-a11y": "^10.4.1",
|
|
58
|
+
"@storybook/addon-docs": "^10.4.1",
|
|
59
|
+
"eslint-plugin-storybook": "^10.4.1",
|
|
60
|
+
"@vitest/coverage-v8": "4.1.7"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"svelte"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "svelte-package",
|
|
67
|
+
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
68
|
+
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
|
69
|
+
"lint": "prettier --check . && eslint .",
|
|
70
|
+
"format": "prettier --write .",
|
|
71
|
+
"test": "vitest run --project=unit --project=browser",
|
|
72
|
+
"test:stories": "vitest run --project=storybook",
|
|
73
|
+
"storybook": "storybook dev -p 6006",
|
|
74
|
+
"build-storybook": "storybook build"
|
|
75
|
+
}
|
|
76
|
+
}
|