@fundar/data-chart-telling 1.9.0 → 1.11.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 +21 -0
- package/dist/configuration/config.svelte.js +1 -1
- package/dist/layout/base/BasePlotLayout.svelte +2 -1
- package/dist/layout/interaction/PlotInteraction.svelte.js +1 -1
- package/dist/layout/legend/DisabledStyle.d.ts +1 -1
- package/dist/layout/legend/DisabledStyle.js +9 -9
- package/dist/layout/plot/PlotContext.d.ts +3 -0
- package/dist/layout/plot/PlotContext.js +3 -0
- package/dist/layout/plot/PublicPlotContext.d.ts +2 -0
- package/dist/layout/plot/PublicPlotContext.js +4 -0
- 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/delta/Delta.d.ts +1 -1
- package/dist/markers/delta/Delta.js +2 -2
- package/dist/markers/delta/DeltaMarker.svelte +1 -1
- package/dist/markers/delta/DeltaPoint.svelte +31 -15
- 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 +1 -1
- package/dist/types/layout/legend.d.ts +1 -1
- package/dist/types/layout/styles/base.d.ts +27 -1
- package/dist/types/markers/axis/delta.d.ts +4 -4
- 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/markers.md +1 -1
- 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/examples/SegmentsBySeries.svelte +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [1.11.0] - 2026-11-04
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- The dots of a `delta` take the color of the series their end measures, as the plot draws it. Before, they took the line's `stroke` and, with none set, a neutral color. The line's `stroke` still wins when it is set, and the delta's own `dot` wins over both; an end on an axis keeps the neutral color. `usePlot()` now also gives `colorForSeries`.
|
|
10
|
+
- **Breaking:** a delta's line is styled with `stroke`, not `line`, like the rest of the styles (`DeltaMarkerConfig.line`, also as a function of the position). Rename it where it is set.
|
|
11
|
+
|
|
12
|
+
## [1.10.0] - 2026-11-03
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `styles.stroke`, `styles.dot` and `styles.area` on every axis-based plot: the style every series is drawn with, each as it is or as a function of the series' name. A plot uses the ones its own marks are made of and ignores the rest: `stroke` and `dot` on `LinePlot`, `SlopePlot`, `SparklinePlot` and the plots with connectors (`LollipopPlot`, `DumbbellPlot`, `ClevelandPlot`, `ForestPlot`); `area` on `AreaPlot`, `BarPlot`, `BulletPlot`, `PyramidPlot`, `HistogramPlot`, `BoxPlot`, `DensityPlot`, `ViolinPlot` and `HeatmapPlot`; `dot` on `ScatterPlot`. A segment's own style wins over them field by field, a connector styled with `styles.connector` wins over `styles.stroke`, and the connectors of those plots - and their arrows - take its dashes and line caps and joins (`strokeDasharray`, `strokeLinecap`, `strokeLinejoin`) from it or from a segment, and a series a legend turned off dims over all of them. Before, restyling every series - or one of them - took a `segments` entry per series.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- The outline an area style sets - `stroke`, `strokeWidth`, `strokeOpacity`, `strokeDasharray` and `borderRadius` - reaches the bars of `BarPlot`, `BulletPlot` and `PyramidPlot` and the bins of `HistogramPlot`, whether it comes from a segment or from `styles.area`. These plots read only the fill of a segment's style, so the rest was ignored; a series a legend dims fades its outline with its fill.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **Breaking:** the dots of a line's style are `dot`, not `dots`, so they read like `styles.dot`: `LineSegmentStyle.dots` (in `segments` and in the `styles.gaps` zones), the legend's `LegendDisabledStyle.dots` and `line.gap.dots` in the configuration. Rename them where they are set.
|
|
25
|
+
|
|
5
26
|
## [1.9.0] - 2026-11-03
|
|
6
27
|
|
|
7
28
|
### Changed
|
|
@@ -238,7 +238,8 @@
|
|
|
238
238
|
scales: () => scales,
|
|
239
239
|
axes: () => state.axes,
|
|
240
240
|
data: () => data,
|
|
241
|
-
size: () => ({ width, height })
|
|
241
|
+
size: () => ({ width, height }),
|
|
242
|
+
colorForSeries: () => state.colorForSeries
|
|
242
243
|
});
|
|
243
244
|
|
|
244
245
|
/** The domains `ScalesLayout` widens an axis to, for an edge tick's `'extend'` (see `EdgeTicks`). */
|
|
@@ -47,7 +47,7 @@ export class PlotInteraction {
|
|
|
47
47
|
}
|
|
48
48
|
/** `series`' dots while dimmed. */
|
|
49
49
|
dotsFor(series) {
|
|
50
|
-
return DisabledStyle.
|
|
50
|
+
return DisabledStyle.dot(this.dimmed(series));
|
|
51
51
|
}
|
|
52
52
|
/** Isolates `series` - every other one dims - or ends it with `null`. */
|
|
53
53
|
isolate(series) {
|
|
@@ -11,7 +11,7 @@ export declare class DisabledStyle {
|
|
|
11
11
|
/** The style a legend item draws with while off - it stays visible, so it can be turned back on. */
|
|
12
12
|
static ofItem(action: LegendDisabledAction | undefined, fallbackOpacity: number): LegendDisabledStyle | undefined;
|
|
13
13
|
static stroke(disabled: LegendDisabledStyle | undefined): StrokeStyle | undefined;
|
|
14
|
-
static
|
|
14
|
+
static dot(disabled: LegendDisabledStyle | undefined): DotStyle | undefined;
|
|
15
15
|
static fill(disabled: LegendDisabledStyle | undefined): {
|
|
16
16
|
fill?: string;
|
|
17
17
|
fillOpacity?: number;
|
|
@@ -32,18 +32,18 @@ export class DisabledStyle {
|
|
|
32
32
|
strokeLinejoin: disabled.stroke?.strokeLinejoin
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
static
|
|
35
|
+
static dot(disabled) {
|
|
36
36
|
if (!disabled)
|
|
37
37
|
return undefined;
|
|
38
38
|
return {
|
|
39
|
-
dotRadius: disabled.
|
|
40
|
-
dotFill: disabled.
|
|
41
|
-
dotFillOpacity: disabled.
|
|
42
|
-
dotSymbol: disabled.
|
|
43
|
-
dotStroke: disabled.
|
|
44
|
-
dotStrokeWidth: disabled.
|
|
45
|
-
dotStrokeOpacity: disabled.
|
|
46
|
-
dotStrokeDasharray: disabled.
|
|
39
|
+
dotRadius: disabled.dot?.dotRadius,
|
|
40
|
+
dotFill: disabled.dot?.dotFill,
|
|
41
|
+
dotFillOpacity: disabled.dot?.dotFillOpacity ?? disabled.opacity,
|
|
42
|
+
dotSymbol: disabled.dot?.dotSymbol,
|
|
43
|
+
dotStroke: disabled.dot?.dotStroke,
|
|
44
|
+
dotStrokeWidth: disabled.dot?.dotStrokeWidth,
|
|
45
|
+
dotStrokeOpacity: disabled.dot?.dotStrokeOpacity ?? disabled.opacity,
|
|
46
|
+
dotStrokeDasharray: disabled.dot?.dotStrokeDasharray
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
static fill(disabled) {
|
|
@@ -16,6 +16,8 @@ export type PlotContextParts<T extends Record<string, unknown>> = {
|
|
|
16
16
|
width: number;
|
|
17
17
|
height: number;
|
|
18
18
|
};
|
|
19
|
+
/** Each series' color, by name - axis kinds only. */
|
|
20
|
+
colorForSeries?: () => ((name: string) => string) | undefined;
|
|
19
21
|
};
|
|
20
22
|
/**
|
|
21
23
|
* Everything one plot is, gathered where what draws inside it can reach it:
|
|
@@ -36,6 +38,7 @@ export declare class PlotContext<T extends Record<string, unknown> = Record<stri
|
|
|
36
38
|
get scales(): object | undefined;
|
|
37
39
|
get axes(): PlotAxes | undefined;
|
|
38
40
|
get data(): T[];
|
|
41
|
+
get colorForSeries(): ((name: string) => string) | undefined;
|
|
39
42
|
get width(): number;
|
|
40
43
|
get height(): number;
|
|
41
44
|
}
|
|
@@ -126,6 +126,8 @@ export declare class PublicPlotContext<T extends Record<string, unknown> = Recor
|
|
|
126
126
|
get axes(): PlotAxes | undefined;
|
|
127
127
|
/** The rows the plot draws and hovers over. */
|
|
128
128
|
get data(): T[];
|
|
129
|
+
/** The color a series is drawn in, by its name - `undefined` for a plot with no series (a map). */
|
|
130
|
+
get colorForSeries(): ((name: string) => string) | undefined;
|
|
129
131
|
get width(): number;
|
|
130
132
|
get height(): number;
|
|
131
133
|
}
|
|
@@ -231,6 +231,10 @@ export class PublicPlotContext {
|
|
|
231
231
|
get data() {
|
|
232
232
|
return this.#context.data;
|
|
233
233
|
}
|
|
234
|
+
/** The color a series is drawn in, by its name - `undefined` for a plot with no series (a map). */
|
|
235
|
+
get colorForSeries() {
|
|
236
|
+
return this.#context.colorForSeries;
|
|
237
|
+
}
|
|
234
238
|
get width() {
|
|
235
239
|
return this.#context.width;
|
|
236
240
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BasicStylesConfig, BasicStylesShape, PerSeries } from '../../types/layout/styles';
|
|
2
|
+
/**
|
|
3
|
+
* The style a plot draws every series with, out of its `styles.stroke`,
|
|
4
|
+
* `styles.dot` and `styles.area`: the ones its kind's segment style is made of
|
|
5
|
+
* (`shape`), each as it is or as a function of the series' name. The segments
|
|
6
|
+
* of a series restyle a stretch of it over this.
|
|
7
|
+
*/
|
|
8
|
+
export declare class BasicStyles {
|
|
9
|
+
/** The style of the series named `name`, or `undefined` when there is none to give. */
|
|
10
|
+
static of<TStyle>(styles: BasicStylesConfig | undefined, shape: BasicStylesShape | undefined): ((seriesName: string) => TStyle | undefined) | undefined;
|
|
11
|
+
/** A style as it is, or what its function says of the series named `name`; a function has nothing to say without a name. */
|
|
12
|
+
static read<TStyle extends object>(style: PerSeries<TStyle> | undefined, name: string | undefined): TStyle | undefined;
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The style a plot draws every series with, out of its `styles.stroke`,
|
|
3
|
+
* `styles.dot` and `styles.area`: the ones its kind's segment style is made of
|
|
4
|
+
* (`shape`), each as it is or as a function of the series' name. The segments
|
|
5
|
+
* of a series restyle a stretch of it over this.
|
|
6
|
+
*/
|
|
7
|
+
export class BasicStyles {
|
|
8
|
+
/** The style of the series named `name`, or `undefined` when there is none to give. */
|
|
9
|
+
static of(styles, shape) {
|
|
10
|
+
if (!styles || !shape)
|
|
11
|
+
return undefined;
|
|
12
|
+
const keys = 'flat' in shape ? [shape.flat] : shape.nested;
|
|
13
|
+
if (keys.every((key) => styles[key] === undefined))
|
|
14
|
+
return undefined;
|
|
15
|
+
return (name) => {
|
|
16
|
+
const picked = Object.fromEntries(keys.flatMap((key) => {
|
|
17
|
+
const style = BasicStyles.read(styles[key], name);
|
|
18
|
+
return style ? [[key, style]] : [];
|
|
19
|
+
}));
|
|
20
|
+
if (Object.keys(picked).length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
return ('flat' in shape ? picked[shape.flat] : picked);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** A style as it is, or what its function says of the series named `name`; a function has nothing to say without a name. */
|
|
26
|
+
static read(style, name) {
|
|
27
|
+
if (typeof style !== 'function')
|
|
28
|
+
return style;
|
|
29
|
+
return name === undefined ? undefined : style(name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -18,7 +18,7 @@ export declare class Delta {
|
|
|
18
18
|
#private;
|
|
19
19
|
/**
|
|
20
20
|
* The deltas a marker draws: one for its `at`, or one for each position of a list, each with the
|
|
21
|
-
* `
|
|
21
|
+
* `stroke`, `text` and `dot` its style functions give for that position.
|
|
22
22
|
*/
|
|
23
23
|
static points(marker: DeltaMarkerConfig): DeltaPointConfig[];
|
|
24
24
|
/** The delta `config` asks for over `allSeries`, or `undefined` while an end has no value there. */
|
|
@@ -7,7 +7,7 @@ import { resolveAccessor } from '../../utils/accessors';
|
|
|
7
7
|
export class Delta {
|
|
8
8
|
/**
|
|
9
9
|
* The deltas a marker draws: one for its `at`, or one for each position of a list, each with the
|
|
10
|
-
* `
|
|
10
|
+
* `stroke`, `text` and `dot` its style functions give for that position.
|
|
11
11
|
*/
|
|
12
12
|
static points(marker) {
|
|
13
13
|
const positions = Array.isArray(marker.at) ? marker.at : [marker.at];
|
|
@@ -15,7 +15,7 @@ export class Delta {
|
|
|
15
15
|
return positions.map((at) => ({
|
|
16
16
|
...marker,
|
|
17
17
|
at,
|
|
18
|
-
|
|
18
|
+
stroke: own(marker.stroke, at),
|
|
19
19
|
text: own(marker.text, at),
|
|
20
20
|
dot: own(marker.dot, at)
|
|
21
21
|
}));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* A delta annotation: one at `marker.at`, or, when it is a list, one at each position - all with the
|
|
9
|
-
* same ends. `
|
|
9
|
+
* same ends. `stroke`, `text` and `dot` can be functions of the position.
|
|
10
10
|
*/
|
|
11
11
|
let { marker, data = [] }: { marker: DeltaMarkerConfig; data?: Series<TData>[] } = $props();
|
|
12
12
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import DotMarker from '../dot/DotMarker.svelte';
|
|
4
4
|
import TextMarker from '../text/TextMarker.svelte';
|
|
5
5
|
import { Delta } from './Delta';
|
|
6
|
+
import { usePlot } from '../../layout/plot/usePlot';
|
|
6
7
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
7
8
|
import type { Series } from '../../types/plots/data/common';
|
|
8
9
|
import type { DeltaPointConfig } from '../../types/markers/axis/delta';
|
|
@@ -10,6 +11,8 @@
|
|
|
10
11
|
/** One delta, at the one position `marker.at` names. See {@link DeltaMarker} for a list of them. */
|
|
11
12
|
let { marker, data = [] }: { marker: DeltaPointConfig; data?: Series<TData>[] } = $props();
|
|
12
13
|
|
|
14
|
+
const plot = usePlot();
|
|
15
|
+
|
|
13
16
|
const resolved = $derived(Delta.build(marker.axis, data, marker));
|
|
14
17
|
const label = $derived(getConfiguration().delta.text[marker.axis]);
|
|
15
18
|
const lineAnchor = $derived(marker.text?.lineAnchor ?? label.lineAnchor);
|
|
@@ -54,13 +57,22 @@
|
|
|
54
57
|
};
|
|
55
58
|
});
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The dot of one end: in the delta's own `stroke` when it sets one, else in the color of the series the end
|
|
62
|
+
* measures - an axis end has none, and takes the dot's default. `dot` wins over both.
|
|
63
|
+
*/
|
|
64
|
+
function dotStyles(target: DeltaPointConfig['from'] | undefined) {
|
|
65
|
+
const color =
|
|
66
|
+
marker.stroke?.stroke ??
|
|
67
|
+
(target?.type === 'series' ? plot.colorForSeries?.(target.series) : undefined);
|
|
68
|
+
return {
|
|
69
|
+
dotRadius: 4,
|
|
70
|
+
dotFill: color,
|
|
71
|
+
dotStroke: color,
|
|
72
|
+
dotStrokeWidth: 2,
|
|
73
|
+
...marker.dot
|
|
74
|
+
};
|
|
75
|
+
}
|
|
64
76
|
</script>
|
|
65
77
|
|
|
66
78
|
{#if coords}
|
|
@@ -68,14 +80,18 @@
|
|
|
68
80
|
data={coords.lineData}
|
|
69
81
|
x={(p) => p.x}
|
|
70
82
|
y={(p) => p.y}
|
|
71
|
-
stroke={marker.
|
|
72
|
-
strokeWidth={marker.
|
|
73
|
-
strokeOpacity={marker.
|
|
74
|
-
strokeDasharray={marker.
|
|
75
|
-
strokeLinecap={marker.
|
|
83
|
+
stroke={marker.stroke?.stroke ?? 'currentColor'}
|
|
84
|
+
strokeWidth={marker.stroke?.strokeWidth ?? 2}
|
|
85
|
+
strokeOpacity={marker.stroke?.strokeOpacity ?? 0.8}
|
|
86
|
+
strokeDasharray={marker.stroke?.strokeDasharray ?? '6 6'}
|
|
87
|
+
strokeLinecap={marker.stroke?.strokeLinecap ?? 'round'}
|
|
88
|
+
/>
|
|
89
|
+
<DotMarker
|
|
90
|
+
marker={{ type: 'dot', x: coords.fromPt.x, y: coords.fromPt.y, styles: dotStyles(marker.from) }}
|
|
91
|
+
/>
|
|
92
|
+
<DotMarker
|
|
93
|
+
marker={{ type: 'dot', x: coords.toPt.x, y: coords.toPt.y, styles: dotStyles(marker.to) }}
|
|
76
94
|
/>
|
|
77
|
-
<DotMarker marker={{ type: 'dot', x: coords.fromPt.x, y: coords.fromPt.y, styles: dotStyles }} />
|
|
78
|
-
<DotMarker marker={{ type: 'dot', x: coords.toPt.x, y: coords.toPt.y, styles: dotStyles }} />
|
|
79
95
|
<TextMarker
|
|
80
96
|
marker={{
|
|
81
97
|
type: 'text',
|
|
@@ -86,7 +102,7 @@
|
|
|
86
102
|
fontWeight: 'bold',
|
|
87
103
|
fontSize: 14,
|
|
88
104
|
textAnchor: label.textAnchor,
|
|
89
|
-
fill: marker.
|
|
105
|
+
fill: marker.stroke?.stroke ?? 'currentColor',
|
|
90
106
|
...marker.text,
|
|
91
107
|
dx: marker.text?.dx ?? label.dx,
|
|
92
108
|
dy: marker.text?.dy ?? label.dy,
|
|
@@ -3,6 +3,7 @@ import { ColorRamp } from '../../utils/color';
|
|
|
3
3
|
import { resolveAccessor } from '../../utils/accessors';
|
|
4
4
|
import { buildGroupedSeries, buildScopedMarkerGroups } from '../../plots/utils/segments';
|
|
5
5
|
import { buildGapFillMaps, isGapValue } from '../../plots/utils/gaps';
|
|
6
|
+
import { BasicStyles } from '../../layout/styles/BasicStyles';
|
|
6
7
|
import { FacetPanels } from '../facet/FacetPanels';
|
|
7
8
|
import { PlotAxes } from './PlotAxes';
|
|
8
9
|
/**
|
|
@@ -39,10 +40,12 @@ export class DataPhase {
|
|
|
39
40
|
? FacetPanels.splitSeries(fullSeries, state.panels, state.panelOf)
|
|
40
41
|
: fullSeries;
|
|
41
42
|
const resolvedSeries = ctx.interaction?.legend?.withoutOmitted(panelSeries) ?? panelSeries;
|
|
42
|
-
const
|
|
43
|
+
const baseFor = BasicStyles.of(state.styles, options.segments?.base);
|
|
44
|
+
const groupedSeries = buildGroupedSeries(resolvedSeries, segments, connect, baseFor);
|
|
43
45
|
const scopedGroups = buildScopedMarkerGroups({
|
|
44
46
|
groupedSeries,
|
|
45
47
|
segments,
|
|
48
|
+
baseFor,
|
|
46
49
|
colorFor: (s) => colorForSeries(s.name),
|
|
47
50
|
segmentColorFor: (style, defaultColor) => segmentColor?.(style) ?? defaultColor
|
|
48
51
|
});
|
|
@@ -57,6 +60,7 @@ export class DataPhase {
|
|
|
57
60
|
seriesGrouping,
|
|
58
61
|
allSeriesNames: fullSeries.map((s) => s.name),
|
|
59
62
|
groupedSeries,
|
|
63
|
+
segmentBase: baseFor,
|
|
60
64
|
flat: resolvedSeries.flatMap((s) => s.data),
|
|
61
65
|
scopedGroups,
|
|
62
66
|
colorForSeries,
|
|
@@ -159,7 +159,7 @@ export class TransformsPhase {
|
|
|
159
159
|
static #buildGroups(state, connect) {
|
|
160
160
|
const { data } = state.processedData;
|
|
161
161
|
const series = (state.resolvedSeries ?? []).map((s) => TransformsPhase.#processedSeries(state, s, data.filter((row) => row[SERIES_ID_KEY] === seriesIdOf(s))));
|
|
162
|
-
return buildGroupedSeries(series, state.segments, connect);
|
|
162
|
+
return buildGroupedSeries(series, state.segments, connect, state.segmentBase);
|
|
163
163
|
}
|
|
164
164
|
/**
|
|
165
165
|
* The processed rows in their own order, split into runs of consecutive rows
|
|
@@ -181,7 +181,7 @@ export class TransformsPhase {
|
|
|
181
181
|
else
|
|
182
182
|
runs.push(TransformsPhase.#processedSeries(state, source, [row]));
|
|
183
183
|
}
|
|
184
|
-
return buildGroupedSeries(runs, state.segments, false);
|
|
184
|
+
return buildGroupedSeries(runs, state.segments, false, state.segmentBase);
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
187
|
* One series accessor shared by every series as-is, so a field key keeps
|
|
@@ -57,7 +57,7 @@ function extendWithColorScales(state, ctx) {
|
|
|
57
57
|
*/
|
|
58
58
|
export function buildHeatmapDefaults() {
|
|
59
59
|
return {
|
|
60
|
-
data: { discreteAxes: 2 },
|
|
60
|
+
data: { discreteAxes: 2, segments: { base: { flat: 'area' } } },
|
|
61
61
|
mapping: {
|
|
62
62
|
// A heatmap with no `color` still reads one per cell, as nothing.
|
|
63
63
|
extend: (state) => ({
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LegendState } from '../../../layout/legend/LegendState.svelte';
|
|
2
2
|
import { DisabledStyle } from '../../../layout/legend/DisabledStyle';
|
|
3
3
|
import { resolveAccessor } from '../../../utils/accessors';
|
|
4
|
+
import { merge } from '../../../utils/merge';
|
|
5
|
+
import { SERIES_KEY } from '../../../phases/transforms/TransformsPhase';
|
|
4
6
|
/** A processed channel as a per-row function. */
|
|
5
7
|
function readChannel(channel) {
|
|
6
8
|
return resolveAccessor(channel);
|
|
@@ -103,8 +105,10 @@ export function buildMarks(state, ctx) {
|
|
|
103
105
|
return base;
|
|
104
106
|
return { ...base, ...DisabledStyle.fill(range.disabledStyle) };
|
|
105
107
|
}
|
|
108
|
+
// `styles.area`, as the cell's series sets it: what every cell is drawn with, under its segments.
|
|
106
109
|
function getCellStyle(d) {
|
|
107
|
-
|
|
110
|
+
const base = state.segmentBase?.(String(d[SERIES_KEY]));
|
|
111
|
+
return rangeFilteredStyle(merge(base ?? {}, segStyleMap.get(`${String(getX(d))}::${String(getY(d))}`)), Number(getColor(d)));
|
|
108
112
|
}
|
|
109
113
|
const styleGroups = (() => {
|
|
110
114
|
const groups = new Map();
|
|
@@ -14,7 +14,11 @@ export function buildBoxDefaults() {
|
|
|
14
14
|
return {
|
|
15
15
|
data: {
|
|
16
16
|
discreteAxes: 1,
|
|
17
|
-
segments: {
|
|
17
|
+
segments: {
|
|
18
|
+
color: (style) => style?.fill,
|
|
19
|
+
connect: false,
|
|
20
|
+
base: { flat: 'area' }
|
|
21
|
+
}
|
|
18
22
|
},
|
|
19
23
|
scales: {
|
|
20
24
|
bandOrder: 'data',
|
|
@@ -15,7 +15,14 @@ export function buildMarks(state, ctx) {
|
|
|
15
15
|
const disabled = ctx.interaction?.fillFor(name);
|
|
16
16
|
return group.visualSegments.map((seg) => {
|
|
17
17
|
const { stroke, strokeWidth, strokeOpacity, borderRadius } = seg.styles;
|
|
18
|
-
const
|
|
18
|
+
const fill = disabled?.fill ?? seg.styles.fill ?? state.colorForSeries(name);
|
|
19
|
+
// An outline asked for without a color is the fill's.
|
|
20
|
+
const outline = {
|
|
21
|
+
stroke: stroke ?? (strokeWidth !== undefined ? fill : undefined),
|
|
22
|
+
strokeWidth,
|
|
23
|
+
strokeOpacity,
|
|
24
|
+
borderRadius
|
|
25
|
+
};
|
|
19
26
|
return {
|
|
20
27
|
type: 'box',
|
|
21
28
|
series: name,
|
|
@@ -25,7 +32,7 @@ export function buildMarks(state, ctx) {
|
|
|
25
32
|
bar: {
|
|
26
33
|
// The box's own outline, from its segment; unset fields stay svelteplot's.
|
|
27
34
|
...Object.fromEntries(Object.entries(outline).filter(([, v]) => v !== undefined)),
|
|
28
|
-
fill
|
|
35
|
+
fill,
|
|
29
36
|
fillOpacity: disabled?.fillOpacity ?? seg.styles.fillOpacity ?? cfg.box.fillOpacity
|
|
30
37
|
}
|
|
31
38
|
};
|
|
@@ -19,7 +19,13 @@ function mergeDensity(own, horizontal) {
|
|
|
19
19
|
*/
|
|
20
20
|
export function buildDensityDefaults() {
|
|
21
21
|
return {
|
|
22
|
-
data: {
|
|
22
|
+
data: {
|
|
23
|
+
segments: {
|
|
24
|
+
color: (style) => style?.fill,
|
|
25
|
+
connect: false,
|
|
26
|
+
base: { flat: 'area' }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
23
29
|
transforms: {
|
|
24
30
|
transforms: (state) => mergeDensity(state.transforms ?? [], state.axes?.isHorizontal ?? false)
|
|
25
31
|
},
|
|
@@ -24,6 +24,10 @@ export function buildMarks(state, ctx) {
|
|
|
24
24
|
strokeDasharray: disabledStroke?.strokeDasharray ?? seg.styles.strokeDasharray
|
|
25
25
|
}
|
|
26
26
|
: {
|
|
27
|
+
// An outline asked for without a color is the fill's.
|
|
28
|
+
...(seg.styles.strokeWidth !== undefined
|
|
29
|
+
? { stroke: disabledFill?.fill ?? color }
|
|
30
|
+
: {}),
|
|
27
31
|
...seg.styles,
|
|
28
32
|
fill: disabledFill?.fill ?? color,
|
|
29
33
|
fillOpacity: disabledFill?.fillOpacity ?? seg.styles.fillOpacity ?? cfg.density.fillOpacity
|
|
@@ -32,7 +32,13 @@ function mergeBin(own, cfg, horizontal) {
|
|
|
32
32
|
*/
|
|
33
33
|
export function buildHistogramDefaults() {
|
|
34
34
|
return {
|
|
35
|
-
data: {
|
|
35
|
+
data: {
|
|
36
|
+
segments: {
|
|
37
|
+
color: (style) => style?.fill,
|
|
38
|
+
connect: false,
|
|
39
|
+
base: { flat: 'area' }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
36
42
|
transforms: {
|
|
37
43
|
transforms: (state, ctx) => mergeBin(state.transforms ?? [], ctx.cfg, state.axes?.isHorizontal ?? false)
|
|
38
44
|
},
|
|
@@ -72,6 +72,8 @@ export function buildMarks(state, ctx) {
|
|
|
72
72
|
const fillOpacity = ctx.cfg.histogram.fillOpacity;
|
|
73
73
|
const rects = (state.processedGroups ?? []).flatMap((group) => {
|
|
74
74
|
const disabled = ctx.interaction?.fillFor(group.series.name);
|
|
75
|
+
// A stroke the segment sets is not a reason to lose the series' fill, as the marker would take it for the color.
|
|
76
|
+
const color = state.colorForSeries(group.series.name);
|
|
75
77
|
return group.visualSegments.map((seg) => Transforms.finish({
|
|
76
78
|
type: 'rect',
|
|
77
79
|
series: group.series.name,
|
|
@@ -79,9 +81,15 @@ export function buildMarks(state, ctx) {
|
|
|
79
81
|
...channels,
|
|
80
82
|
...options,
|
|
81
83
|
styles: {
|
|
84
|
+
// An outline asked for without a color is the fill's.
|
|
85
|
+
...(seg.styles.strokeWidth !== undefined
|
|
86
|
+
? { stroke: disabled?.fill ?? seg.styles.fill ?? color }
|
|
87
|
+
: {}),
|
|
88
|
+
...seg.styles,
|
|
89
|
+
fill: disabled?.fill ?? seg.styles.fill ?? color,
|
|
82
90
|
fillOpacity: disabled?.fillOpacity ?? seg.styles.fillOpacity ?? fillOpacity,
|
|
83
|
-
|
|
84
|
-
...(disabled?.
|
|
91
|
+
// A dimmed series fades its outline with its fill.
|
|
92
|
+
...(disabled?.fillOpacity !== undefined ? { strokeOpacity: disabled.fillOpacity } : {})
|
|
85
93
|
}
|
|
86
94
|
}, transforms));
|
|
87
95
|
});
|
|
@@ -20,7 +20,11 @@ export function buildPyramidDefaults() {
|
|
|
20
20
|
return {
|
|
21
21
|
data: {
|
|
22
22
|
discreteAxes: 1,
|
|
23
|
-
segments: {
|
|
23
|
+
segments: {
|
|
24
|
+
color: (style) => style?.fill,
|
|
25
|
+
connect: false,
|
|
26
|
+
base: { flat: 'area' }
|
|
27
|
+
},
|
|
24
28
|
orderSeries: (series, state) => {
|
|
25
29
|
const sorted = [...series].sort(byName).map((s) => s.name);
|
|
26
30
|
const reversed = state.scales.grouping?.reverse ?? false;
|
|
@@ -119,8 +119,17 @@ export function buildMarks(state, ctx) {
|
|
|
119
119
|
data: realData,
|
|
120
120
|
...span(processedCategory, signed),
|
|
121
121
|
styles: {
|
|
122
|
+
// An outline asked for without a color is the fill's.
|
|
123
|
+
...(seg.styles.strokeWidth !== undefined
|
|
124
|
+
? { stroke: disabledFill?.fill ?? seg.styles.fill ?? defaultColor }
|
|
125
|
+
: {}),
|
|
126
|
+
...seg.styles,
|
|
122
127
|
fill: disabledFill?.fill ?? seg.styles.fill ?? defaultColor,
|
|
123
|
-
fillOpacity: disabledFill?.fillOpacity ?? seg.styles.fillOpacity ?? 1
|
|
128
|
+
fillOpacity: disabledFill?.fillOpacity ?? seg.styles.fillOpacity ?? 1,
|
|
129
|
+
// A dimmed series fades its outline with its fill.
|
|
130
|
+
...(disabledFill?.fillOpacity !== undefined
|
|
131
|
+
? { strokeOpacity: disabledFill.fillOpacity }
|
|
132
|
+
: {})
|
|
124
133
|
}
|
|
125
134
|
});
|
|
126
135
|
}
|
|
@@ -34,7 +34,11 @@ export function buildViolinDefaults() {
|
|
|
34
34
|
return {
|
|
35
35
|
data: {
|
|
36
36
|
discreteAxes: 1,
|
|
37
|
-
segments: {
|
|
37
|
+
segments: {
|
|
38
|
+
color: (style) => style?.fill,
|
|
39
|
+
connect: false,
|
|
40
|
+
base: { flat: 'area' }
|
|
41
|
+
}
|
|
38
42
|
},
|
|
39
43
|
scales: {
|
|
40
44
|
bandOrder: 'data',
|
|
@@ -45,6 +45,10 @@ export function buildMarks(state, ctx) {
|
|
|
45
45
|
peak,
|
|
46
46
|
slot: { index, count: groups.length },
|
|
47
47
|
styles: {
|
|
48
|
+
// An outline asked for without a color is the fill's.
|
|
49
|
+
...(seg.styles.strokeWidth !== undefined
|
|
50
|
+
? { stroke: disabled?.fill ?? seg.styles.fill ?? state.colorForSeries(name) }
|
|
51
|
+
: {}),
|
|
48
52
|
...seg.styles,
|
|
49
53
|
fill: disabled?.fill ?? seg.styles.fill ?? state.colorForSeries(name),
|
|
50
54
|
fillOpacity: disabled?.fillOpacity ?? seg.styles.fillOpacity ?? cfg.violin.fillOpacity
|
|
@@ -3,7 +3,9 @@ import { ValueLabels } from '../../../layout/styles/ValueLabels';
|
|
|
3
3
|
/** AreaPlot on the shared pipeline: areas from a zero baseline, author markers taking the plot's `x`/`y`, and its own markers. Its plot defaults to: hover along x, cumulative timeline tweening `y` per series. */
|
|
4
4
|
export function buildAreaDefaults() {
|
|
5
5
|
return {
|
|
6
|
-
data: {
|
|
6
|
+
data: {
|
|
7
|
+
segments: { color: (style) => style?.fill, base: { flat: 'area' } }
|
|
8
|
+
},
|
|
7
9
|
statistics: { inheritPositions: true },
|
|
8
10
|
scales: { zero: ['y'] },
|
|
9
11
|
transforms: { labels: { select: 'last', skip: ValueLabels.skipGaps } },
|