@fundar/data-chart-telling 1.8.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/configuration/config.svelte.js +3 -5
- package/dist/layout/interaction/PlotInteraction.svelte.js +1 -1
- package/dist/layout/legend/ContinuousSection.svelte +90 -21
- package/dist/layout/legend/DisabledStyle.d.ts +1 -1
- package/dist/layout/legend/DisabledStyle.js +9 -9
- package/dist/layout/styles/BasicStyles.d.ts +13 -0
- package/dist/layout/styles/BasicStyles.js +31 -0
- package/dist/markers/arrow/ArrowMarker.svelte +2 -0
- package/dist/markers/rule/RuleMarker.svelte +2 -0
- package/dist/phases/data/DataPhase.js +5 -1
- package/dist/phases/transforms/TransformsPhase.js +2 -2
- package/dist/plots/correlation/heatmap/defaults.js +1 -1
- package/dist/plots/correlation/heatmap/markers.js +5 -1
- package/dist/plots/correlation/scatter/defaults.js +2 -1
- package/dist/plots/distribution/box/defaults.js +5 -1
- package/dist/plots/distribution/box/markers.js +9 -2
- package/dist/plots/distribution/density/defaults.js +7 -1
- package/dist/plots/distribution/density/markers.js +4 -0
- package/dist/plots/distribution/histogram/defaults.js +7 -1
- package/dist/plots/distribution/histogram/markers.js +10 -2
- package/dist/plots/distribution/pyramid/defaults.js +5 -1
- package/dist/plots/distribution/pyramid/markers.js +10 -1
- package/dist/plots/distribution/violin/defaults.js +5 -1
- package/dist/plots/distribution/violin/markers.js +4 -0
- package/dist/plots/evolution/area/defaults.js +3 -1
- package/dist/plots/evolution/area/markers.js +18 -9
- package/dist/plots/evolution/line/defaults.js +6 -1
- package/dist/plots/evolution/line/markers.d.ts +2 -2
- package/dist/plots/evolution/line/markers.js +5 -5
- package/dist/plots/evolution/slope/markers.js +1 -1
- package/dist/plots/ranking/bar/defaults.js +5 -1
- package/dist/plots/ranking/bar/markers.js +10 -2
- package/dist/plots/ranking/cleveland/ClevelandMarkers.js +37 -12
- package/dist/plots/ranking/cleveland/defaults.js +4 -2
- package/dist/plots/utils/segments.d.ts +3 -1
- package/dist/plots/utils/segments.js +40 -19
- package/dist/types/configuration/styling.d.ts +5 -7
- package/dist/types/layout/legend.d.ts +1 -1
- package/dist/types/layout/styles/base.d.ts +27 -1
- package/dist/types/phases/data/phase.d.ts +3 -0
- package/dist/types/phases/state.d.ts +2 -0
- package/dist/types/phases/transforms/phase.d.ts +2 -0
- package/dist/types/plots/segments/common.d.ts +2 -2
- package/docs/plots/ranking/dumbbell.md +1 -1
- package/docs/plots/ranking/lollipop.md +1 -1
- package/docs/styles-and-segments.md +32 -2
- package/docs/theming.md +28 -29
- package/examples/SegmentsBySeries.svelte +1 -1
- package/package.json +1 -1
|
@@ -26,14 +26,23 @@ export function buildMarks(state, ctx) {
|
|
|
26
26
|
};
|
|
27
27
|
// The dim channel: each area reads its own series name, looked up in `opacityScale`.
|
|
28
28
|
const dimByName = (name) => dimmed ? { opacity: () => name, scales: { opacity: opacityScale } } : {};
|
|
29
|
-
const areas = (state.processedGroups ?? []).flatMap((group) => group.visualSegments.map((seg) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
const areas = (state.processedGroups ?? []).flatMap((group) => group.visualSegments.map((seg) => {
|
|
30
|
+
// A stroke the segment sets is not a reason to lose the series' fill, as the marker would take it for the color.
|
|
31
|
+
const fill = seg.styles.fill ?? state.colorForSeries(group.series.name);
|
|
32
|
+
return Transforms.finish({
|
|
33
|
+
type: 'area',
|
|
34
|
+
series: group.series.name,
|
|
35
|
+
data: seg.data,
|
|
36
|
+
...channels,
|
|
37
|
+
...options,
|
|
38
|
+
styles: {
|
|
39
|
+
// An outline asked for without a color is the fill's.
|
|
40
|
+
...(seg.styles.strokeWidth !== undefined ? { stroke: fill } : {}),
|
|
41
|
+
...seg.styles,
|
|
42
|
+
fill
|
|
43
|
+
},
|
|
44
|
+
...dimByName(group.series.name)
|
|
45
|
+
}, transforms);
|
|
46
|
+
}));
|
|
38
47
|
return areas;
|
|
39
48
|
}
|
|
@@ -3,7 +3,12 @@ import { ValueLabels } from '../../../layout/styles/ValueLabels';
|
|
|
3
3
|
/** LinePlot on the shared pipeline: continuous segments colored by their stroke, and its own markers. Its plot defaults to: hover along x, cumulative timeline tweening `y` per series. */
|
|
4
4
|
export function buildLineDefaults() {
|
|
5
5
|
return {
|
|
6
|
-
data: {
|
|
6
|
+
data: {
|
|
7
|
+
segments: {
|
|
8
|
+
color: (style) => style?.stroke?.stroke,
|
|
9
|
+
base: { nested: ['stroke', 'dot'] }
|
|
10
|
+
}
|
|
11
|
+
},
|
|
7
12
|
statistics: {
|
|
8
13
|
// An unscoped marker takes the line's own stroke when its first segment restyles it,
|
|
9
14
|
// else the first series' color.
|
|
@@ -10,7 +10,7 @@ import type { AxisBasedMarkersConfig } from '../../../types/markers/common';
|
|
|
10
10
|
/**
|
|
11
11
|
* Every `'line'` (+ `'dot'`, where a segment's style asks for them) marker
|
|
12
12
|
* for one LinePlot render: one `'line'` marker per visual segment, plus a
|
|
13
|
-
* `'dot'` marker right after it when that segment sets `style.
|
|
13
|
+
* `'dot'` marker right after it when that segment sets `style.dot`.
|
|
14
14
|
*/
|
|
15
15
|
export declare function buildLineMarkers<TData extends Record<string, unknown>>(args: {
|
|
16
16
|
groupedSeries: VisualGroup<Series<TData>, LineSegmentStyle>[];
|
|
@@ -21,7 +21,7 @@ export declare function buildLineMarkers<TData extends Record<string, unknown>>(
|
|
|
21
21
|
curve?: CurveName;
|
|
22
22
|
}): (LineMarkerConfig | DotMarkerConfig)[];
|
|
23
23
|
/**
|
|
24
|
-
* One `'line'` (+ `'dot'`, when its zone sets `style.
|
|
24
|
+
* One `'line'` (+ `'dot'`, when its zone sets `style.dot`) marker per
|
|
25
25
|
* detected, configured gap run across every series - the interpolated
|
|
26
26
|
* bridge drawn over a missing-data hole. `gaps` is resolved per series the
|
|
27
27
|
* same way `segments` is (a named entry, falling back to `'default'` at
|
|
@@ -3,7 +3,7 @@ import { resolveGapRuns, resolveGapsForSeries } from '../../utils/gaps';
|
|
|
3
3
|
/**
|
|
4
4
|
* Every `'line'` (+ `'dot'`, where a segment's style asks for them) marker
|
|
5
5
|
* for one LinePlot render: one `'line'` marker per visual segment, plus a
|
|
6
|
-
* `'dot'` marker right after it when that segment sets `style.
|
|
6
|
+
* `'dot'` marker right after it when that segment sets `style.dot`.
|
|
7
7
|
*/
|
|
8
8
|
export function buildLineMarkers(args) {
|
|
9
9
|
const { groupedSeries, colorFor, disabledStrokeFor, disabledDotsFor, curve } = args;
|
|
@@ -23,7 +23,7 @@ export function buildLineMarkers(args) {
|
|
|
23
23
|
const y = group.series.y;
|
|
24
24
|
for (const seg of group.visualSegments) {
|
|
25
25
|
const strokeStyle = seg.styles.stroke;
|
|
26
|
-
const dotStyle = seg.styles.
|
|
26
|
+
const dotStyle = seg.styles.dot;
|
|
27
27
|
markers.push({
|
|
28
28
|
type: 'line',
|
|
29
29
|
data: seg.data,
|
|
@@ -70,7 +70,7 @@ export function buildLineMarkers(args) {
|
|
|
70
70
|
const gx = (p) => p.x;
|
|
71
71
|
const gy = (p) => p.y;
|
|
72
72
|
/**
|
|
73
|
-
* One `'line'` (+ `'dot'`, when its zone sets `style.
|
|
73
|
+
* One `'line'` (+ `'dot'`, when its zone sets `style.dot`) marker per
|
|
74
74
|
* detected, configured gap run across every series - the interpolated
|
|
75
75
|
* bridge drawn over a missing-data hole. `gaps` is resolved per series the
|
|
76
76
|
* same way `segments` is (a named entry, falling back to `'default'` at
|
|
@@ -100,7 +100,7 @@ export function buildGapMarkers(args) {
|
|
|
100
100
|
const disabledDots = disabledDotsFor(series.name);
|
|
101
101
|
for (const run of runs) {
|
|
102
102
|
const strokeStyle = run.zone.style?.stroke;
|
|
103
|
-
const dotStyle = run.zone.style?.
|
|
103
|
+
const dotStyle = run.zone.style?.dot;
|
|
104
104
|
markers.push({
|
|
105
105
|
type: 'line',
|
|
106
106
|
data: run.points,
|
|
@@ -128,7 +128,7 @@ export function buildGapMarkers(args) {
|
|
|
128
128
|
});
|
|
129
129
|
if (!dotStyle)
|
|
130
130
|
continue;
|
|
131
|
-
const dotDefaults = gapDefaults.
|
|
131
|
+
const dotDefaults = gapDefaults.dot;
|
|
132
132
|
const endpoints = [run.points[0], run.points[run.points.length - 1]];
|
|
133
133
|
markers.push({
|
|
134
134
|
type: 'dot',
|
|
@@ -20,7 +20,7 @@ export function buildMarks(state, ctx) {
|
|
|
20
20
|
styles: {
|
|
21
21
|
...segment.styles,
|
|
22
22
|
stroke: { strokeWidth: slope.strokeWidth, ...segment.styles.stroke },
|
|
23
|
-
|
|
23
|
+
dot: { dotRadius: slope.dotRadius, ...segment.styles.dot }
|
|
24
24
|
}
|
|
25
25
|
}))
|
|
26
26
|
}));
|
|
@@ -45,7 +45,11 @@ export function buildBarDefaults() {
|
|
|
45
45
|
return {
|
|
46
46
|
data: {
|
|
47
47
|
discreteAxes: 1,
|
|
48
|
-
segments: {
|
|
48
|
+
segments: {
|
|
49
|
+
color: (style) => style?.fill,
|
|
50
|
+
connect: false,
|
|
51
|
+
base: { flat: 'area' }
|
|
52
|
+
}
|
|
49
53
|
},
|
|
50
54
|
scales: {
|
|
51
55
|
zero: ['value'],
|
|
@@ -195,9 +195,17 @@ export function buildMarks(state, ctx) {
|
|
|
195
195
|
const defaultColor = colorForSeries(name);
|
|
196
196
|
const disabledFill = ctx.interaction?.fillFor(name);
|
|
197
197
|
for (const seg of group.visualSegments) {
|
|
198
|
+
const fill = disabledFill?.fill ?? seg.styles.fill ?? defaultColor;
|
|
198
199
|
const marker = makeMarker(seg.data.filter(isReal), slice, valueOf(group.series[valueAxis]), {
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
// An outline asked for without a color is the fill's.
|
|
201
|
+
...(seg.styles.strokeWidth !== undefined ? { stroke: fill } : {}),
|
|
202
|
+
...seg.styles,
|
|
203
|
+
fill,
|
|
204
|
+
fillOpacity: disabledFill?.fillOpacity ?? seg.styles.fillOpacity ?? 1,
|
|
205
|
+
// A dimmed series fades its outline with its fill.
|
|
206
|
+
...(disabledFill?.fillOpacity !== undefined
|
|
207
|
+
? { strokeOpacity: disabledFill.fillOpacity }
|
|
208
|
+
: {})
|
|
201
209
|
});
|
|
202
210
|
if (marker)
|
|
203
211
|
segmentMarkers.push(marker);
|
|
@@ -4,6 +4,7 @@ import { SERIES_KEY } from '../../../phases/transforms/TransformsPhase';
|
|
|
4
4
|
import { ValueLabels } from '../../../layout/styles/ValueLabels';
|
|
5
5
|
import { ClevelandRanges } from './ClevelandRanges';
|
|
6
6
|
import { ClevelandConnector } from './ClevelandConnector';
|
|
7
|
+
import { BasicStyles } from '../../../layout/styles/BasicStyles';
|
|
7
8
|
/** The messages already warned about, so a plot that redraws warns once. */
|
|
8
9
|
const warned = new Set();
|
|
9
10
|
/**
|
|
@@ -45,14 +46,20 @@ export class ClevelandMarkers {
|
|
|
45
46
|
const size = state.channels?.r;
|
|
46
47
|
const dotRadius = typeof size === 'number' ? size : style.dotRadius;
|
|
47
48
|
const neutral = style.connectorColor ?? cfg.axis.color;
|
|
49
|
+
// `styles.stroke` is the connectors' look below everything the author sets for them: a segment's
|
|
50
|
+
// stroke wins over `styles.connector`, which wins over it. A function of the series styles only
|
|
51
|
+
// the connector of a lone dot, which has a series; the connector of several dots has none.
|
|
52
|
+
const baseStroke = state.styles?.stroke;
|
|
48
53
|
const looks = ranges.entries.map((entry) => {
|
|
49
54
|
const range = connector.rangeOf(entry);
|
|
50
55
|
const owner = range.series === undefined ? undefined : segmentOf.get(entry.dots[0].row);
|
|
51
56
|
const stem = owner?.styles.stroke;
|
|
57
|
+
const base = BasicStyles.read(baseStroke, range.series);
|
|
52
58
|
const disabled = owner && interaction?.strokeFor(owner.series);
|
|
53
59
|
const strokeWidth = disabled?.strokeWidth ??
|
|
54
60
|
stem?.strokeWidth ??
|
|
55
61
|
connector.width(range, dotRadius) ??
|
|
62
|
+
base?.strokeWidth ??
|
|
56
63
|
(style.stemWidth === 'dots' ? dotRadius * 2 : style.stemWidth);
|
|
57
64
|
// The stretch between the `from` and `to` dots, in the connector's look...
|
|
58
65
|
const [start, end] = range.span ?? [entry.from, entry.to];
|
|
@@ -65,9 +72,14 @@ export class ClevelandMarkers {
|
|
|
65
72
|
stroke: disabled?.stroke ??
|
|
66
73
|
stem?.stroke ??
|
|
67
74
|
connector.color(range) ??
|
|
75
|
+
base?.stroke ??
|
|
68
76
|
(owner ? state.colorForSeries(owner.series) : neutral),
|
|
69
77
|
strokeWidth,
|
|
70
|
-
strokeOpacity: disabled?.strokeOpacity ??
|
|
78
|
+
strokeOpacity: disabled?.strokeOpacity ??
|
|
79
|
+
stem?.strokeOpacity ??
|
|
80
|
+
connector.opacity(range) ??
|
|
81
|
+
base?.strokeOpacity ??
|
|
82
|
+
1
|
|
71
83
|
};
|
|
72
84
|
// ...as an arrow from one dot to the other, when the connector asks for one...
|
|
73
85
|
const arrow = connector.arrow;
|
|
@@ -103,14 +115,19 @@ export class ClevelandMarkers {
|
|
|
103
115
|
// A stretch drawn as an arrow is not one of the lines.
|
|
104
116
|
rows: drawn ? beyond : [span, ...beyond],
|
|
105
117
|
arrow: drawn,
|
|
106
|
-
|
|
118
|
+
// What a stroke draws all of a marker's rows alike with, which a marker takes once for them.
|
|
119
|
+
pattern: ClevelandMarkers.#pattern(disabled?.strokeDasharray ?? stem?.strokeDasharray ?? base?.strokeDasharray, disabled?.strokeLinecap ?? stem?.strokeLinecap ?? base?.strokeLinecap, disabled?.strokeLinejoin ?? stem?.strokeLinejoin ?? base?.strokeLinejoin)
|
|
107
120
|
};
|
|
108
121
|
});
|
|
109
|
-
// One rule marker per dash pattern, which a marker takes once for all its rows.
|
|
110
|
-
const
|
|
111
|
-
for (const look of looks)
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
// One rule marker per dash, cap and join pattern, which a marker takes once for all its rows.
|
|
123
|
+
const byPattern = new Map();
|
|
124
|
+
for (const look of looks) {
|
|
125
|
+
const key = JSON.stringify(look.pattern);
|
|
126
|
+
const group = byPattern.get(key) ?? { pattern: look.pattern, looks: [] };
|
|
127
|
+
group.looks.push(look);
|
|
128
|
+
byPattern.set(key, group);
|
|
129
|
+
}
|
|
130
|
+
const rules = [...byPattern.values()].map(({ pattern, looks: group }) => ({
|
|
114
131
|
type: 'rule',
|
|
115
132
|
// A connector is not what the series look like: its dots are.
|
|
116
133
|
legend: false,
|
|
@@ -121,11 +138,11 @@ export class ClevelandMarkers {
|
|
|
121
138
|
stroke: 'stroke',
|
|
122
139
|
strokeWidth: 'strokeWidth',
|
|
123
140
|
strokeOpacity: 'strokeOpacity',
|
|
124
|
-
...(
|
|
141
|
+
...(Object.keys(pattern).length > 0 ? { styles: pattern } : {})
|
|
125
142
|
}));
|
|
126
|
-
// The arrows, one marker per
|
|
143
|
+
// The arrows, one marker per pattern like the lines, each from its `from` dot to its `to` dot.
|
|
127
144
|
const head = connector.arrow;
|
|
128
|
-
const arrows = [...
|
|
145
|
+
const arrows = [...byPattern.values()].flatMap(({ pattern, looks: group }) => {
|
|
129
146
|
const rows = group.flatMap((look) => (look.arrow ? [look.arrow] : []));
|
|
130
147
|
if (rows.length === 0 || !head)
|
|
131
148
|
return [];
|
|
@@ -145,7 +162,7 @@ export class ClevelandMarkers {
|
|
|
145
162
|
stroke: 'stroke',
|
|
146
163
|
strokeWidth: 'strokeWidth',
|
|
147
164
|
strokeOpacity: 'strokeOpacity',
|
|
148
|
-
...(
|
|
165
|
+
...(Object.keys(pattern).length > 0 ? { styles: pattern } : {})
|
|
149
166
|
}
|
|
150
167
|
];
|
|
151
168
|
});
|
|
@@ -161,7 +178,7 @@ export class ClevelandMarkers {
|
|
|
161
178
|
const disabled = interaction?.dotsFor(name);
|
|
162
179
|
return group.visualSegments.map((segment) => {
|
|
163
180
|
const stem = segment.styles.stroke;
|
|
164
|
-
const dot = segment.styles.
|
|
181
|
+
const dot = segment.styles.dot;
|
|
165
182
|
return {
|
|
166
183
|
type: 'dot',
|
|
167
184
|
series: name,
|
|
@@ -188,6 +205,14 @@ export class ClevelandMarkers {
|
|
|
188
205
|
...this.#connectorLabels(state, ctx, connector, looks, isHorizontal)
|
|
189
206
|
];
|
|
190
207
|
};
|
|
208
|
+
/** The dash, cap and join a connector is drawn with, leaving out what is not set. */
|
|
209
|
+
static #pattern(strokeDasharray, strokeLinecap, strokeLinejoin) {
|
|
210
|
+
return {
|
|
211
|
+
...(strokeDasharray ? { strokeDasharray } : {}),
|
|
212
|
+
...(strokeLinecap ? { strokeLinecap } : {}),
|
|
213
|
+
...(strokeLinejoin ? { strokeLinejoin } : {})
|
|
214
|
+
};
|
|
215
|
+
}
|
|
191
216
|
/** The caps of the connectors: a tick at each end of those that have a length, one marker per stroke look. */
|
|
192
217
|
#caps(entries, looks, length, isHorizontal) {
|
|
193
218
|
const byLook = new Map();
|
|
@@ -39,8 +39,10 @@ export function buildClevelandDefaults(options = { name: 'ClevelandPlot', config
|
|
|
39
39
|
data: {
|
|
40
40
|
discreteAxes: 1,
|
|
41
41
|
segments: {
|
|
42
|
-
color: (style) => style?.
|
|
43
|
-
connect: false
|
|
42
|
+
color: (style) => style?.dot?.dotFill ?? style?.stroke?.stroke,
|
|
43
|
+
connect: false,
|
|
44
|
+
// The stem's `styles.stroke` is not a segment's: a connector the author styles wins over it (see `ClevelandMarkers`).
|
|
45
|
+
base: { nested: ['dot'] }
|
|
44
46
|
}
|
|
45
47
|
},
|
|
46
48
|
scales: {
|
|
@@ -24,7 +24,7 @@ export declare function matchesSegmentX<TStyle>(xVal: AxisValue, seg: Segment<TS
|
|
|
24
24
|
* which it overrides in its own panels.
|
|
25
25
|
*/
|
|
26
26
|
export declare function validateSegments<TStyle>(segments: SegmentsConfig<TStyle>): void;
|
|
27
|
-
export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments?: SegmentsConfig<TStyle>, connect?: boolean): VisualGroup<S, TStyle>[];
|
|
27
|
+
export declare function buildGroupedSeries<S extends Series<any>, TStyle = LineSegmentStyle>(seriesList: S[], segments?: SegmentsConfig<TStyle>, connect?: boolean, baseFor?: (seriesName: string) => TStyle | undefined): VisualGroup<S, TStyle>[];
|
|
28
28
|
/**
|
|
29
29
|
* Builds the `ScopedMarkerGroup[]` a `Plot.svelte` hands to `GeometryLayout`
|
|
30
30
|
* for rendering `series`-scoped `hover`/`component` markers - one group per
|
|
@@ -39,4 +39,6 @@ export declare function buildScopedMarkerGroups<TData extends Record<string, unk
|
|
|
39
39
|
segments?: SegmentsConfig<TStyle>;
|
|
40
40
|
colorFor: (series: S, seriesIndex: number) => string;
|
|
41
41
|
segmentColorFor: (styles: TStyle | undefined, defaultColor: string) => string;
|
|
42
|
+
/** The style each series' segments are drawn over, as `buildGroupedSeries` takes it. */
|
|
43
|
+
baseFor?: (seriesName: string) => TStyle | undefined;
|
|
42
44
|
}): ScopedMarkerGroup<TData>[];
|
|
@@ -75,9 +75,17 @@ export function validateSegments(segments) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
-
function resolveSeriesSegments(series,
|
|
78
|
+
function resolveSeriesSegments(series, segmentsOfSeries, connect, base) {
|
|
79
|
+
// The style every segment is drawn over: each one's own wins, field by field.
|
|
80
|
+
const baseStyle = (base ?? {});
|
|
81
|
+
const segs = base
|
|
82
|
+
? segmentsOfSeries.map((seg) => ({
|
|
83
|
+
...seg,
|
|
84
|
+
styles: merge(base, (seg.styles ?? {}))
|
|
85
|
+
}))
|
|
86
|
+
: segmentsOfSeries;
|
|
79
87
|
if (!segs.length) {
|
|
80
|
-
return [{ data: series.data, styles:
|
|
88
|
+
return [{ data: series.data, styles: baseStyle, scales: undefined }];
|
|
81
89
|
}
|
|
82
90
|
const getX = readChannel(series.x);
|
|
83
91
|
const getY = readChannel(series.y);
|
|
@@ -121,10 +129,8 @@ function resolveSeriesSegments(series, segs, connect) {
|
|
|
121
129
|
// Dots are excluded to avoid duplicate marks at the shared boundary points.
|
|
122
130
|
const catchAll = segs.find((s) => !s.areas || s.areas.length === 0);
|
|
123
131
|
const connectorStyle = (() => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const rest = { ...catchAll.styles };
|
|
127
|
-
delete rest.dots;
|
|
132
|
+
const rest = { ...(catchAll?.styles ?? baseStyle) };
|
|
133
|
+
delete rest.dot;
|
|
128
134
|
return rest;
|
|
129
135
|
})();
|
|
130
136
|
const defaultVisual = [];
|
|
@@ -133,19 +139,33 @@ function resolveSeriesSegments(series, segs, connect) {
|
|
|
133
139
|
for (let r = 0; r < runs.length; r++) {
|
|
134
140
|
const run = runs[r];
|
|
135
141
|
if (run.segIdx === -1) {
|
|
136
|
-
// Unmatched run:
|
|
137
|
-
// it
|
|
138
|
-
//
|
|
142
|
+
// Unmatched run: drawn with its own rows only. For a continuous mark
|
|
143
|
+
// (line), line-only connectors join it to the adjacent user segments
|
|
144
|
+
// without a visual gap, like those between two user segments, so no
|
|
145
|
+
// neighbor's boundary row is drawn - or dotted - twice. Discrete marks
|
|
146
|
+
// (bar, pyramid) have no such gap to bridge - bridging would just
|
|
139
147
|
// duplicate the neighboring runs' boundary rows as an extra, wrongly
|
|
140
148
|
// styled mark at that same category.
|
|
141
|
-
|
|
149
|
+
defaultVisual.push({ data: run.data, styles: baseStyle, scales: undefined });
|
|
142
150
|
if (connect) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (r
|
|
146
|
-
|
|
151
|
+
const first = run.data[0];
|
|
152
|
+
const last = run.data[run.data.length - 1];
|
|
153
|
+
if (r > 0) {
|
|
154
|
+
const before = runs[r - 1].data;
|
|
155
|
+
connectors.push({
|
|
156
|
+
data: [before[before.length - 1], first],
|
|
157
|
+
styles: connectorStyle,
|
|
158
|
+
scales: undefined
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (r < runs.length - 1) {
|
|
162
|
+
connectors.push({
|
|
163
|
+
data: [last, runs[r + 1].data[0]],
|
|
164
|
+
styles: connectorStyle,
|
|
165
|
+
scales: undefined
|
|
166
|
+
});
|
|
167
|
+
}
|
|
147
168
|
}
|
|
148
|
-
defaultVisual.push({ data: bridged, styles: {}, scales: undefined });
|
|
149
169
|
}
|
|
150
170
|
else {
|
|
151
171
|
const seg = segs[run.segIdx];
|
|
@@ -196,10 +216,10 @@ function resolveSeriesSegments(series, segs, connect) {
|
|
|
196
216
|
function readChannel(accessor) {
|
|
197
217
|
return accessor === undefined ? () => undefined : resolveAccessor(accessor);
|
|
198
218
|
}
|
|
199
|
-
export function buildGroupedSeries(seriesList, segments = {}, connect = true) {
|
|
219
|
+
export function buildGroupedSeries(seriesList, segments = {}, connect = true, baseFor) {
|
|
200
220
|
return seriesList.map((s) => ({
|
|
201
221
|
series: s,
|
|
202
|
-
visualSegments: resolveSeriesSegments(s, resolveSegmentsForSeries(segments, s), connect)
|
|
222
|
+
visualSegments: resolveSeriesSegments(s, resolveSegmentsForSeries(segments, s), connect, baseFor?.(s.name))
|
|
203
223
|
}));
|
|
204
224
|
}
|
|
205
225
|
/**
|
|
@@ -212,11 +232,12 @@ export function buildGroupedSeries(seriesList, segments = {}, connect = true) {
|
|
|
212
232
|
* that isn't shared across plot kinds.
|
|
213
233
|
*/
|
|
214
234
|
export function buildScopedMarkerGroups(args) {
|
|
215
|
-
const { groupedSeries, segments = {}, colorFor, segmentColorFor } = args;
|
|
235
|
+
const { groupedSeries, segments = {}, colorFor, segmentColorFor, baseFor } = args;
|
|
216
236
|
return groupedSeries.map((group, seriesIndex) => {
|
|
217
237
|
const defaultColor = colorFor(group.series, seriesIndex);
|
|
218
238
|
const xFor = readChannel(group.series.x);
|
|
219
239
|
const segs = resolveSegmentsForSeries(segments, group.series);
|
|
240
|
+
const base = baseFor?.(group.series.name);
|
|
220
241
|
return {
|
|
221
242
|
name: group.series.name,
|
|
222
243
|
...(group.series.id !== undefined ? { id: group.series.id } : {}),
|
|
@@ -225,7 +246,7 @@ export function buildScopedMarkerGroups(args) {
|
|
|
225
246
|
color: defaultColor,
|
|
226
247
|
segments: segs.map((seg) => ({
|
|
227
248
|
match: (row) => matchesSegmentX(xFor(row), seg),
|
|
228
|
-
color: segmentColorFor(seg.styles, defaultColor)
|
|
249
|
+
color: segmentColorFor(base ? merge(base, (seg.styles ?? {})) : seg.styles, defaultColor)
|
|
229
250
|
}))
|
|
230
251
|
};
|
|
231
252
|
});
|
|
@@ -209,16 +209,14 @@ export type PlotStylesConfig = {
|
|
|
209
209
|
rotate: string;
|
|
210
210
|
/** font-size of the tick/edge/handle labels. */
|
|
211
211
|
size: string;
|
|
212
|
-
/** font-weight of the draggable lo/hi
|
|
212
|
+
/** font-weight of every label: the edges, the ticks and the draggable lo/hi handles. */
|
|
213
213
|
weight: number | string;
|
|
214
|
-
/**
|
|
214
|
+
/** Labels that would touch are thinned out: a tick label gives way to an edge label and to the ticks before it, and, in the interactive mode, the lo/hi handle label of a handle that rests on the domain's end gives way to the moving one. */
|
|
215
215
|
overlap: {
|
|
216
|
-
/** Whether
|
|
216
|
+
/** Whether labels are thinned out at all; false always writes every label, however close together. */
|
|
217
217
|
enabled: boolean;
|
|
218
|
-
/** How many px of horizontal slack
|
|
218
|
+
/** How many px of horizontal slack two labels need between them before they are considered touching. */
|
|
219
219
|
margin: number;
|
|
220
|
-
/** Rotation magnitude applied once overlapping - lo rotates by this amount, hi by its negation, so both still grow inward toward the bar instead of off its edges. */
|
|
221
|
-
rotate: string;
|
|
222
220
|
};
|
|
223
221
|
};
|
|
224
222
|
};
|
|
@@ -456,7 +454,7 @@ export type PlotStylesConfig = {
|
|
|
456
454
|
*/
|
|
457
455
|
gap: {
|
|
458
456
|
stroke: StrokeStyle;
|
|
459
|
-
|
|
457
|
+
dot: DotStyle;
|
|
460
458
|
};
|
|
461
459
|
};
|
|
462
460
|
/** Default lollipop-plot styling. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FrameProp } from './frame';
|
|
2
2
|
import type { ValuesStyle } from './values';
|
|
3
|
+
import type { StrokeStyle, DotStyle, AreaStyle } from '../../plots/styling';
|
|
3
4
|
/**
|
|
4
5
|
* The fields every plot kind's own `styles` shape shares - the common parent
|
|
5
6
|
* of {@link AxisBasedStylesConfig} and {@link GeoStylesConfig}.
|
|
@@ -8,11 +9,36 @@ export type BasePlotStylesConfig = {
|
|
|
8
9
|
/** Decorative border around the plot's content box - see {@link FrameStyle}. Available on every plot kind. */
|
|
9
10
|
frame?: FrameProp;
|
|
10
11
|
};
|
|
12
|
+
/** A style as it is for every series, or a function of the series' name - `undefined` leaves that series as it is. */
|
|
13
|
+
export type PerSeries<TStyle> = TStyle | ((series: string) => TStyle | undefined);
|
|
14
|
+
/**
|
|
15
|
+
* The basic styles any plot's marks are made of, as the style every series is
|
|
16
|
+
* drawn with, before its `segments` restyle a stretch of it. Each takes a style
|
|
17
|
+
* as it is, or a function of the series' name, to set one series or two apart.
|
|
18
|
+
* A plot uses the ones its own marks are made of and ignores the rest, so one
|
|
19
|
+
* `styles` can be shared between plots: `stroke` and `dot` style a line plot's
|
|
20
|
+
* lines and dots, `area` a bar's. What a segment sets wins over them, field by
|
|
21
|
+
* field; whatever a legend turned off wins over both.
|
|
22
|
+
*/
|
|
23
|
+
export type BasicStylesConfig = {
|
|
24
|
+
/** The line of every series: its width, opacity, dashes... The color comes from the `color` scale unless set here. */
|
|
25
|
+
stroke?: PerSeries<StrokeStyle>;
|
|
26
|
+
/** The dots of every series. Unset, a series has none but those a segment gives it. */
|
|
27
|
+
dot?: PerSeries<DotStyle>;
|
|
28
|
+
/** The area of every series - a bar, a band: its fill, opacity, outline... */
|
|
29
|
+
area?: PerSeries<AreaStyle>;
|
|
30
|
+
};
|
|
31
|
+
/** The basic styles a plot kind's segment style is made of: nested in it under their own names (a line's `{ stroke, dot }`), or the style itself (an area's `AreaStyle`). */
|
|
32
|
+
export type BasicStylesShape = {
|
|
33
|
+
nested: (keyof BasicStylesConfig)[];
|
|
34
|
+
} | {
|
|
35
|
+
flat: keyof BasicStylesConfig;
|
|
36
|
+
};
|
|
11
37
|
/**
|
|
12
38
|
* Top-level rendering style overrides shared by the non-geo plot kinds.
|
|
13
39
|
* Passed as the `styles` prop. Each plot reads only the fields it cares
|
|
14
40
|
* about.
|
|
15
41
|
*/
|
|
16
|
-
export type AxisBasedStylesConfig = BasePlotStylesConfig & {
|
|
42
|
+
export type AxisBasedStylesConfig = BasePlotStylesConfig & BasicStylesConfig & {
|
|
17
43
|
values?: ValuesStyle;
|
|
18
44
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Series } from '../../plots/data/common';
|
|
2
2
|
import type { DiscreteAxesCount } from './axes';
|
|
3
|
+
import type { BasicStylesShape } from '../../layout/styles';
|
|
3
4
|
/** How a plot kind's own mark splits into visual segments. */
|
|
4
5
|
export type SegmentsStageOptions = {
|
|
5
6
|
/** The color a segment's own style sets, if any, for the markers scoped to its series. */
|
|
6
7
|
color?: (style: any) => string | undefined;
|
|
7
8
|
/** Whether adjacent visual segments share their boundary row - a continuous mark (line, area) does. Defaults to `true`. */
|
|
8
9
|
connect?: boolean;
|
|
10
|
+
/** The basic styles (`styles.stroke`, `styles.dot`, `styles.area`) this kind's segment style is made of, which every segment of a series is drawn over. A segment's own style wins over them, field by field. Left out, the kind draws none of them. */
|
|
11
|
+
base?: BasicStylesShape;
|
|
9
12
|
};
|
|
10
13
|
/**
|
|
11
14
|
* What differs between plots in the shared "data" stage. Everything else -
|
|
@@ -61,6 +61,8 @@ export type AxisPlotState<TData extends Record<string, unknown>, TStyle extends
|
|
|
61
61
|
/** Every series name, before legend-omitted ones are dropped, so colors stay put. */
|
|
62
62
|
allSeriesNames?: string[];
|
|
63
63
|
groupedSeries?: VisualGroup<TSeries, TStyle>[];
|
|
64
|
+
/** The style every segment of a series is drawn over (see `SegmentsStageOptions.base`), which the "transforms" phase cuts its own groups with too. */
|
|
65
|
+
segmentBase?: (series: string) => TStyle | undefined;
|
|
64
66
|
flat?: TRow[];
|
|
65
67
|
scopedGroups?: ScopedMarkerGroup<TData>[];
|
|
66
68
|
colorForSeries?: (name: string) => string;
|
|
@@ -26,6 +26,8 @@ export type ProcessedData = {
|
|
|
26
26
|
/** What the shared "transforms" stage reads from and writes to a plot's state. */
|
|
27
27
|
export type TransformsStageState = {
|
|
28
28
|
resolvedSeries?: Series<any>[];
|
|
29
|
+
/** The style every segment of a series is drawn over, which the processed groups are cut with too. */
|
|
30
|
+
segmentBase?: (series: string) => any;
|
|
29
31
|
axes?: PlotAxes;
|
|
30
32
|
/** Each series' gap fills, by series id: the value each missing row is drawn at. */
|
|
31
33
|
gapFillMaps?: Map<string, Map<unknown, {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { StrokeStyle, DotStyle, AreaStyle } from '../styling';
|
|
2
2
|
import type { CurveName } from '../../layout/styles/curve';
|
|
3
|
-
/** Per-segment styling for a line: its curve, its stroke and/or its
|
|
3
|
+
/** Per-segment styling for a line: its curve, its stroke and/or its dots. */
|
|
4
4
|
export type LineSegmentStyle = {
|
|
5
5
|
/** How the segment joins its points; `'step'` and its kin draw stairs. Falls back to `styles.curve`, then `'linear'`. */
|
|
6
6
|
curve?: CurveName;
|
|
7
7
|
stroke?: StrokeStyle;
|
|
8
|
-
|
|
8
|
+
dot?: DotStyle;
|
|
9
9
|
};
|
|
10
10
|
/** Per-segment styling for a bar-shaped mark. Shared by `BarPlot` and `PyramidPlot`. */
|
|
11
11
|
export type BarSegmentStyle = AreaStyle;
|
|
@@ -35,5 +35,5 @@ Two dots per category, one for each of two series, joined by a connector: the ga
|
|
|
35
35
|
- `r` sizes the dots by a field (`r="population"`; area follows the value, and the legend gets a size section) or fixes their radius in pixels (`r={8}`).
|
|
36
36
|
- The connector is a neutral gray as thick as the dots are wide (`stemWidth: 'dots'`), a band; its default color and width, the dot radius and the padding come from `dumbbell` in the [configuration](../../theming.md). `styles.connector` restyles it per category (see [Connectors](cleveland.md#connectors)). `styles.connector.arrow` draws it as an arrow from one series to the other, pointing the way the value moved.
|
|
37
37
|
- `styles.values` writes each value past its dot, away from the other one. A label past the lowest dot can reach the axis labels when that dot sits at the edge of the axis; pin `scales.y.domain` (or `x`) wide enough to leave it room.
|
|
38
|
-
- `segments` restyle the dots of chosen categories (`styles: {
|
|
38
|
+
- `segments` restyle the dots of chosen categories (`styles: { dot: { dotRadius: 9 } }`, `areas: [{ x: { from: 'Salta', to: 'Salta' } }]`).
|
|
39
39
|
- The tooltip reads one category at a time, listing its two values.
|
|
@@ -35,5 +35,5 @@ One value per category, drawn as a dot on a thin stem that rises from the baseli
|
|
|
35
35
|
- The value axis reaches zero, where the stems start; pinning `scales.y.domain` moves the baseline to the nearest end of it, and a negative value hangs below zero.
|
|
36
36
|
- `scales.x.paddingInner` / `paddingOuter` set the room around each lollipop; the defaults, with the dot radius and the stem width (a thin 2 px line), come from `lollipop` in the [configuration](../../theming.md).
|
|
37
37
|
- `styles.values` writes each value just past its dot.
|
|
38
|
-
- `segments` restyle whole categories: `styles: { stroke: {...},
|
|
38
|
+
- `segments` restyle whole categories: `styles: { stroke: {...}, dot: {...} }` restyle their stems and dots (`areas: [{ x: { from: 'Salta', to: 'Salta' } }]`).
|
|
39
39
|
- The tooltip reads one lollipop at a time, along the category axis.
|
|
@@ -103,6 +103,36 @@ A row whose value is `null`, `undefined` or `NaN` breaks a line. `styles.gaps` b
|
|
|
103
103
|
- On bar plots only the presence of `x` matters.
|
|
104
104
|
- Unset styles fall back to `line.gap`, `bar.gap` and `pyramid.gap` in the [configuration](theming.md).
|
|
105
105
|
|
|
106
|
+
### Basic styles
|
|
107
|
+
|
|
108
|
+
`styles.stroke`, `styles.dot` and `styles.area` set the style every series is drawn with, so `segments` are left for what only part of a series looks like. Each takes a style as it is, or a function of the series' name returning one (`undefined` leaves that series as it is):
|
|
109
|
+
|
|
110
|
+
```svelte
|
|
111
|
+
<LinePlot
|
|
112
|
+
styles={{
|
|
113
|
+
stroke: (name) =>
|
|
114
|
+
name === 'male' ? { strokeWidth: 2, strokeOpacity: 0.5 } : { strokeWidth: 4 },
|
|
115
|
+
dot: { dotRadius: 3 }
|
|
116
|
+
}}
|
|
117
|
+
...
|
|
118
|
+
/>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- A plot uses the ones its own marks are made of and ignores the rest, so one `styles` can be shared between plots:
|
|
122
|
+
|
|
123
|
+
| Plot | Uses |
|
|
124
|
+
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
125
|
+
| `LinePlot`, `SlopePlot`, `SparklinePlot` | `stroke` (the line), `dot` (a dot on every point; unset, none) |
|
|
126
|
+
| `AreaPlot`, `BarPlot`, `BulletPlot`, `PyramidPlot`, `HistogramPlot`, `BoxPlot`, `DensityPlot`, `ViolinPlot`, `HeatmapPlot` | `area` |
|
|
127
|
+
| `ScatterPlot` | `dot` |
|
|
128
|
+
| `LollipopPlot`, `DumbbellPlot`, `ClevelandPlot`, `ForestPlot` | `stroke` (the stems and connectors), `dot` |
|
|
129
|
+
|
|
130
|
+
`GeoPlot` has styles of its own and uses none of them.
|
|
131
|
+
|
|
132
|
+
- On the plots with connectors, `styles.stroke` (or a segment's `stroke`) styles the stems and connectors: width, color, opacity, dashes and line caps and joins. What the author sets for a connector wins over it: a segment's `stroke` wins over `styles.connector`, which wins over `styles.stroke`. A function of the series styles only the connector of a lone dot, which has a series; the connector of several dots takes a style as it is.
|
|
133
|
+
- A series keeps the color of the `color` scale unless the style sets its own.
|
|
134
|
+
- A segment's own style wins over them, field by field, and what a legend turned off dims over both - so a `legend.off` fades a series without `segments` repeating its opacity.
|
|
135
|
+
|
|
106
136
|
## Segments
|
|
107
137
|
|
|
108
138
|
`segments` separates data ranges from their styling. It is an object: each key is a series name (a feature id for `GeoPlot`), or `'default'`, mapped to a list of segments.
|
|
@@ -125,7 +155,7 @@ The style shape depends on the plot:
|
|
|
125
155
|
|
|
126
156
|
| Plot | Style type | Fields |
|
|
127
157
|
| --------------------------- | ---------------------------------- | ---------------------------------------------- |
|
|
128
|
-
| `LinePlot` | `LineSegmentStyle` | `stroke` (line style), `
|
|
158
|
+
| `LinePlot` | `LineSegmentStyle` | `stroke` (line style), `dot` (dot style) |
|
|
129
159
|
| `AreaPlot`, `HistogramPlot` | `AreaStyle` | `fill`, `fillOpacity`, `stroke`, ... |
|
|
130
160
|
| `BarPlot`, `PyramidPlot` | `BarSegmentStyle` (`AreaStyle`) | `fill`, `fillOpacity`, `stroke`, ... |
|
|
131
161
|
| `HeatmapPlot` | `CellSegmentStyle` (`AreaStyle`) | `fill`, `fillOpacity`, `stroke`, `strokeWidth` |
|
|
@@ -192,7 +222,7 @@ Per-series colors, plus dots over a range shared by every series:
|
|
|
192
222
|
default: [
|
|
193
223
|
{
|
|
194
224
|
areas: [{ x: { from: 1965, to: 1980 } }],
|
|
195
|
-
styles: {
|
|
225
|
+
styles: { dot: { dotRadius: 5, dotSymbol: 'circle' } }
|
|
196
226
|
}
|
|
197
227
|
]
|
|
198
228
|
}}
|