@fundar/data-chart-telling 1.0.0 → 1.1.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/README.md +57 -18
- package/dist/configuration/config.svelte.js +13 -1
- package/dist/configuration/themes/index.d.ts +124 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/layout/base/BasePlotLayout.svelte +18 -5
- package/dist/layout/hover/HoverSettings.d.ts +20 -6
- package/dist/layout/hover/HoverSettings.js +29 -4
- package/dist/layout/legend/LegendState.svelte.d.ts +5 -0
- package/dist/layout/legend/LegendState.svelte.js +4 -0
- package/dist/layout/plot/Plot.svelte +6 -3
- package/dist/layout/plot/PublicPlotContext.d.ts +5 -4
- package/dist/layout/plot/PublicPlotContext.js +7 -3
- package/dist/layout/plot/Slice.js +4 -2
- package/dist/layout/scales/EdgeTicks.d.ts +39 -0
- package/dist/layout/scales/EdgeTicks.js +82 -0
- package/dist/layout/scales/NiceTicks.d.ts +40 -0
- package/dist/layout/scales/NiceTicks.js +72 -0
- package/dist/layout/scales/ScalesLayout.svelte +64 -30
- package/dist/layout/scales/ScalesLayout.svelte.d.ts +5 -0
- package/dist/layout/scales/SveltePlotScale.d.ts +15 -0
- package/dist/layout/scales/SveltePlotScale.js +79 -0
- package/dist/layout/styles/ValueLabels.js +3 -2
- package/dist/layout/timeline/Timeline.svelte +5 -2
- package/dist/layout/timeline/Timeline.svelte.d.ts +2 -0
- package/dist/layout/timeline/TimelineLayout.svelte +38 -12
- package/dist/layout/timeline/TimelineLayout.svelte.d.ts +7 -5
- package/dist/layout/timeline/TimelineRule.svelte +1 -0
- package/dist/layout/timeline/TimelineState.svelte.d.ts +13 -5
- package/dist/layout/timeline/TimelineState.svelte.js +42 -8
- package/dist/layout/timeline/TimelineTime.d.ts +18 -0
- package/dist/layout/timeline/TimelineTime.js +117 -0
- package/dist/layout/tooltip/Tooltip.svelte +6 -3
- package/dist/layout/tooltip/Tooltip.svelte.d.ts +2 -0
- package/dist/layout/tooltip/TooltipState.svelte.d.ts +8 -2
- package/dist/layout/tooltip/TooltipState.svelte.js +22 -3
- package/dist/markers/delta/DeltaMarker.svelte +9 -7
- package/dist/phases/Pipeline.js +3 -2
- package/dist/phases/data/DataPhase.d.ts +5 -11
- package/dist/phases/data/DataPhase.js +11 -8
- package/dist/phases/facet/FacetPanels.d.ts +54 -0
- package/dist/phases/facet/FacetPanels.js +95 -0
- package/dist/phases/facet/FacetPhase.d.ts +1 -42
- package/dist/phases/facet/FacetPhase.js +1 -87
- package/dist/phases/hover/HoverPhase.d.ts +6 -43
- package/dist/phases/hover/HoverPhase.js +19 -54
- package/dist/phases/mapping/MappingPhase.d.ts +1 -1
- package/dist/phases/mapping/MappingPhase.js +2 -4
- package/dist/phases/scales/ScalesPhase.d.ts +1 -3
- package/dist/phases/scales/ScalesPhase.js +17 -21
- package/dist/phases/sharedScales/SharedScalesPhase.d.ts +4 -1
- package/dist/phases/sharedScales/SharedScalesPhase.js +10 -7
- package/dist/phases/statistics/StatisticsPhase.js +3 -4
- package/dist/phases/tooltip/TooltipPhase.d.ts +4 -2
- package/dist/phases/tooltip/TooltipPhase.js +10 -3
- package/dist/phases/transforms/ProcessedPositions.d.ts +30 -0
- package/dist/phases/transforms/ProcessedPositions.js +74 -0
- package/dist/phases/transforms/TransformsPhase.d.ts +5 -42
- package/dist/phases/transforms/TransformsPhase.js +20 -57
- package/dist/plots/area/markers.js +1 -2
- package/dist/plots/bar/defaults.js +6 -10
- package/dist/plots/bar/markers.js +5 -4
- package/dist/plots/box/BoxSummaries.d.ts +19 -0
- package/dist/plots/box/BoxSummaries.js +101 -0
- package/dist/plots/box/Plot.svelte +42 -0
- package/dist/plots/box/Plot.svelte.d.ts +26 -0
- package/dist/plots/box/defaults.d.ts +12 -0
- package/dist/plots/box/defaults.js +34 -0
- package/dist/plots/box/markers.d.ts +12 -0
- package/dist/plots/box/markers.js +34 -0
- package/dist/plots/geo/phases/hover.js +5 -5
- package/dist/plots/geo/phases/markers.js +3 -4
- package/dist/plots/geo/slices.js +6 -3
- package/dist/plots/heatmap/markers.js +2 -1
- package/dist/plots/histogram/defaults.js +1 -1
- package/dist/plots/histogram/markers.js +4 -4
- package/dist/plots/line/markers.js +1 -2
- package/dist/plots/pyramid/defaults.js +3 -2
- package/dist/plots/pyramid/markers.js +3 -6
- package/dist/plots/scatter/defaults.js +3 -2
- package/dist/plots/scatter/markers.js +4 -3
- package/dist/types/configuration/styling.d.ts +24 -2
- package/dist/types/layout/composer.d.ts +5 -2
- package/dist/types/layout/scales.d.ts +16 -0
- package/dist/types/layout/timeline.d.ts +14 -5
- package/dist/types/phases/hover/phase.d.ts +6 -0
- package/dist/types/phases/state.d.ts +5 -2
- package/dist/types/phases/statistics/phase.d.ts +3 -1
- package/dist/types/phases/transforms/phase.d.ts +10 -0
- package/dist/types/plots/box/props.d.ts +10 -0
- package/dist/types/plots/box/props.js +1 -0
- package/dist/types/plots/box/state.d.ts +23 -0
- package/dist/types/plots/box/state.js +1 -0
- package/dist/types/plots/props.d.ts +2 -0
- package/docs/chrome.md +39 -8
- package/docs/composer.md +19 -3
- package/docs/concepts.md +1 -1
- package/docs/development.md +14 -5
- package/docs/facets.md +62 -8
- package/docs/getting-started.md +8 -9
- package/docs/legend.md +62 -15
- package/docs/markers.md +17 -6
- package/docs/plots.md +168 -42
- package/docs/scales.md +36 -19
- package/docs/state.md +1 -1
- package/docs/styles-and-segments.md +85 -31
- package/docs/theming.md +30 -19
- package/docs/timeline.md +62 -28
- package/docs/tooltip-and-hover.md +72 -24
- package/docs/transforms-and-statistics.md +47 -17
- package/examples/BarValues.svelte +2 -2
- package/examples/Box.svelte +7 -0
- package/examples/Chrome.svelte +17 -0
- package/examples/ChromeOrder.svelte +18 -0
- package/examples/Composer.svelte +23 -0
- package/examples/CustomTooltip.svelte +21 -14
- package/examples/Facets.svelte +3 -3
- package/examples/FacetsCarousel.svelte +14 -0
- package/examples/FacetsRowsCols.svelte +23 -0
- package/examples/FirstPlot.svelte +7 -0
- package/examples/Gaps.svelte +21 -0
- package/examples/GroupedBars.svelte +15 -0
- package/examples/Heatmap.svelte +7 -0
- package/examples/HeatmapSegments.svelte +26 -0
- package/examples/Histogram.svelte +14 -0
- package/examples/HorizontalBars.svelte +15 -0
- package/examples/LegendSections.svelte +27 -0
- package/examples/LegendToggle.svelte +3 -3
- package/examples/LineByRegion.svelte +18 -0
- package/examples/LineDates.svelte +15 -0
- package/examples/Markers.svelte +27 -0
- package/examples/MovingAverage.svelte +15 -0
- package/examples/Pyramid.svelte +7 -0
- package/examples/Regression.svelte +14 -0
- package/examples/ResponsiveLine.svelte +7 -7
- package/examples/Scales.svelte +26 -0
- package/examples/Scatter.svelte +16 -0
- package/examples/SegmentHighlight.svelte +22 -0
- package/examples/Segments.svelte +11 -3
- package/examples/SegmentsBySeries.svelte +25 -0
- package/examples/StackedArea.svelte +16 -0
- package/examples/StackedBars.svelte +15 -0
- package/examples/TimelinePlayer.svelte +8 -7
- package/examples/TimelineRule.svelte +19 -0
- package/examples/Tooltip.svelte +3 -3
- package/examples/ValueLabels.svelte +21 -0
- package/examples/data.ts +320 -8
- package/llms-full.txt +33 -5
- package/llms.txt +30 -2
- package/package.json +3 -2
- package/dist/layout/scales/buildScale.d.ts +0 -8
- package/dist/layout/scales/buildScale.js +0 -67
- package/dist/layout/scales/niceDomain.d.ts +0 -23
- package/dist/layout/scales/niceDomain.js +0 -59
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProcessedPositions } from '../transforms/ProcessedPositions';
|
|
2
2
|
import { isGapValue } from '../../plots/utils/gaps';
|
|
3
3
|
import { MarkerAxisValues } from './MarkerAxisValues';
|
|
4
4
|
import { PlotAxes } from '../data/PlotAxes';
|
|
5
|
+
import { NiceTicks } from '../../layout/scales/NiceTicks';
|
|
5
6
|
import { DISCRETE_SCALE_TYPES } from '../../types/layout/scales';
|
|
6
7
|
const AXES = ['x', 'y'];
|
|
7
8
|
const POSITION_CHANNELS = {
|
|
@@ -86,15 +87,22 @@ export class ScalesPhase {
|
|
|
86
87
|
const aggregated = (processedData?.transforms.length ?? 0) > 0 && zero.has(axis);
|
|
87
88
|
if (!derived.length && !ranged && !aggregated)
|
|
88
89
|
continue;
|
|
89
|
-
const own = numeric(
|
|
90
|
+
const own = numeric(ProcessedPositions.values(processedData, axis));
|
|
90
91
|
if (!own)
|
|
91
92
|
continue;
|
|
92
93
|
const values = [...own, ...derived, ...(zero.has(axis) ? [0] : [])];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
const lo = Math.min(...values);
|
|
95
|
+
const hi = Math.max(...values);
|
|
96
|
+
const author = scales[axis];
|
|
97
|
+
// Niced here, with a tolerance, so parts that add up to a whole land on its tick.
|
|
98
|
+
const niced = author?.nice !== false && author?.ticks == null && hi > lo;
|
|
99
|
+
scales[axis] = niced
|
|
100
|
+
? {
|
|
101
|
+
...author,
|
|
102
|
+
domain: NiceTicks.fit(state, axis, lo, hi, author?.niceTolerance),
|
|
103
|
+
nice: false
|
|
104
|
+
}
|
|
105
|
+
: { ...author, domain: [lo, hi], nice: author?.nice ?? true };
|
|
98
106
|
}
|
|
99
107
|
const next = { ...state, scales };
|
|
100
108
|
return options.extend ? options.extend(next, ctx) : next;
|
|
@@ -116,22 +124,10 @@ export class ScalesPhase {
|
|
|
116
124
|
}
|
|
117
125
|
}
|
|
118
126
|
if (this.keepsDataOrder && !band.domain) {
|
|
119
|
-
band.domain = [...new Set(
|
|
127
|
+
band.domain = [...new Set(ProcessedPositions.values(processed, axis))];
|
|
120
128
|
}
|
|
121
129
|
return band;
|
|
122
130
|
}
|
|
123
|
-
/** Every value the plot's own processed rows sit at on one axis. */
|
|
124
|
-
static processedValues(processed, axis) {
|
|
125
|
-
if (!processed)
|
|
126
|
-
return [];
|
|
127
|
-
return POSITION_CHANNELS[axis].flatMap((name) => {
|
|
128
|
-
const channel = processed.channels[name];
|
|
129
|
-
if (channel === undefined)
|
|
130
|
-
return [];
|
|
131
|
-
const read = resolveAccessor(channel);
|
|
132
|
-
return processed.data.map((row) => read(row));
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
131
|
/** The axes `refs` name, by name or by the role they play in this state. */
|
|
136
132
|
static #axesOf(refs, state) {
|
|
137
133
|
return (refs ?? []).map((ref) => ref === 'value'
|
|
@@ -147,7 +143,7 @@ export class ScalesPhase {
|
|
|
147
143
|
}
|
|
148
144
|
/** How far an axis reaches from zero: the largest magnitude its rows, and the gaps filled in between them, sit at. */
|
|
149
145
|
static #reachOf(state, axis) {
|
|
150
|
-
const rows =
|
|
146
|
+
const rows = ProcessedPositions.values(state.processedData, axis)
|
|
151
147
|
.filter((v) => !isGapValue(v))
|
|
152
148
|
.map((v) => Math.abs(Number(v)));
|
|
153
149
|
const filled = [...(state.gapFillMaps?.values() ?? [])].flatMap((fills) => [...fills.values()].map((fill) => Math.abs(fill.value)));
|
|
@@ -13,6 +13,9 @@ type SharedScalesState = FacetStageState & {
|
|
|
13
13
|
axes?: PlotAxes;
|
|
14
14
|
processedData?: ProcessedData;
|
|
15
15
|
markers?: object[];
|
|
16
|
+
scales?: Partial<Record<AxisChannelName, {
|
|
17
|
+
niceTolerance?: number;
|
|
18
|
+
}>>;
|
|
16
19
|
sharedScales?: Record<string, AxisBasedScalesConfig<never>>;
|
|
17
20
|
};
|
|
18
21
|
/**
|
|
@@ -38,7 +41,7 @@ export declare class SharedScalesPhase<TState extends SharedScalesState, TCtx> i
|
|
|
38
41
|
* count derived from each panel's height, which over-rounds short panels and
|
|
39
42
|
* can disagree between panels of different heights.
|
|
40
43
|
*/
|
|
41
|
-
static buildSharedAxisScale(slices: SharedAxisValues[], axis: AxisChannelName): SharedScale;
|
|
44
|
+
static buildSharedAxisScale(slices: SharedAxisValues[], axis: AxisChannelName, niceTolerance?: number): SharedScale;
|
|
42
45
|
/**
|
|
43
46
|
* Where one panel's rows and markers sit on each axis, read off a pipeline
|
|
44
47
|
* run over every panel - or, without a `panel`, where all of them sit.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProcessedPositions } from '../transforms/ProcessedPositions';
|
|
2
|
+
import { NiceTicks } from '../../layout/scales/NiceTicks';
|
|
2
3
|
import { ScalesPhase } from '../scales/ScalesPhase';
|
|
3
4
|
import { Pipeline } from '../Pipeline';
|
|
4
|
-
import { PANEL_KEY,
|
|
5
|
+
import { PANEL_KEY, FacetPanels } from '../facet/FacetPanels';
|
|
5
6
|
import { isFacetWrap } from '../../layout/facet/config';
|
|
6
7
|
import { MarkerAxisValues } from '../scales/MarkerAxisValues';
|
|
7
8
|
/** Natural ascending order, as svelteplot sorts a band domain. */
|
|
@@ -49,7 +50,7 @@ export class SharedScalesPhase {
|
|
|
49
50
|
/** The channel's domain over `members` panels, or every row without them. */
|
|
50
51
|
const domainOver = (channel, members) => {
|
|
51
52
|
if (fromPhase.has(channel)) {
|
|
52
|
-
const scoped = members ?
|
|
53
|
+
const scoped = members ? FacetPanels.narrowToPanels(state, members) : state;
|
|
53
54
|
const scales = this.#kindScales.run(scoped, ctx).scales;
|
|
54
55
|
const domain = scales?.[channel]?.domain;
|
|
55
56
|
return domain === undefined ? undefined : { domain };
|
|
@@ -59,7 +60,7 @@ export class SharedScalesPhase {
|
|
|
59
60
|
const values = members
|
|
60
61
|
? members.map((panel) => SharedScalesPhase.buildPanelAxisValues(state, panel, layout))
|
|
61
62
|
: [SharedScalesPhase.buildPanelAxisValues(state, undefined, layout)];
|
|
62
|
-
return SharedScalesPhase.buildSharedAxisScale(values, channel);
|
|
63
|
+
return SharedScalesPhase.buildSharedAxisScale(values, channel, state.scales?.[channel]?.niceTolerance);
|
|
63
64
|
};
|
|
64
65
|
const cache = new Map();
|
|
65
66
|
const scalesOf = (place) => {
|
|
@@ -103,7 +104,7 @@ export class SharedScalesPhase {
|
|
|
103
104
|
* count derived from each panel's height, which over-rounds short panels and
|
|
104
105
|
* can disagree between panels of different heights.
|
|
105
106
|
*/
|
|
106
|
-
static buildSharedAxisScale(slices, axis) {
|
|
107
|
+
static buildSharedAxisScale(slices, axis, niceTolerance) {
|
|
107
108
|
const values = slices
|
|
108
109
|
.flatMap((slice) => slice[axis])
|
|
109
110
|
.filter((v) => v != null && v !== '' && !(typeof v === 'number' && !Number.isFinite(v)));
|
|
@@ -119,7 +120,9 @@ export class SharedScalesPhase {
|
|
|
119
120
|
const numbers = slices.some((slice) => slice.zero.includes(axis))
|
|
120
121
|
? [...values, 0]
|
|
121
122
|
: values;
|
|
122
|
-
|
|
123
|
+
const lo = Math.min(...numbers);
|
|
124
|
+
const hi = Math.max(...numbers);
|
|
125
|
+
return { domain: NiceTicks.domain(lo, hi, 10, niceTolerance), nice: false };
|
|
123
126
|
}
|
|
124
127
|
if (values.every((v) => v instanceof Date)) {
|
|
125
128
|
const times = values.map((d) => +d);
|
|
@@ -139,7 +142,7 @@ export class SharedScalesPhase {
|
|
|
139
142
|
};
|
|
140
143
|
const markers = (state.markers ?? []).filter((marker) => inPanel(marker[PANEL_KEY]));
|
|
141
144
|
const values = (axis) => [
|
|
142
|
-
...
|
|
145
|
+
...ProcessedPositions.values(processed, axis),
|
|
143
146
|
...MarkerAxisValues.of(markers, axis)
|
|
144
147
|
];
|
|
145
148
|
return {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AuthorMarkers } from './AuthorMarkers';
|
|
2
2
|
import { Statistics } from './Statistics';
|
|
3
|
-
import { PANEL_KEY,
|
|
4
|
-
import { TransformsPhase } from '../transforms/TransformsPhase';
|
|
3
|
+
import { PANEL_KEY, FacetPanels } from '../facet/FacetPanels';
|
|
5
4
|
import { PlotAxes } from '../data/PlotAxes';
|
|
6
5
|
/** Marker kinds that draw from a channel pair, so they take the plot's `x`/`y` when the author leaves one out. */
|
|
7
6
|
const POSITIONED_KINDS = new Set(['line', 'area', 'dot']);
|
|
@@ -25,7 +24,7 @@ export class StatisticsPhase {
|
|
|
25
24
|
run = (state) => {
|
|
26
25
|
if (!state.panels)
|
|
27
26
|
return { ...state, markers: this.#resolve(state) };
|
|
28
|
-
const markers = state.panels.flatMap((panel) => this.#resolve(
|
|
27
|
+
const markers = state.panels.flatMap((panel) => this.#resolve(FacetPanels.narrowToPanel(state, panel.key)).map((marker) => ({ ...marker, [PANEL_KEY]: panel.key })));
|
|
29
28
|
return { ...state, markers };
|
|
30
29
|
};
|
|
31
30
|
#resolve(state) {
|
|
@@ -51,7 +50,7 @@ export class StatisticsPhase {
|
|
|
51
50
|
// A series setting no value of its own (a histogram's) measures its processed rows: its bins.
|
|
52
51
|
const valueAxis = PlotAxes.valueOf(state);
|
|
53
52
|
const measured = state.processedData && resolvedSeries.some((s) => s[valueAxis] === undefined)
|
|
54
|
-
?
|
|
53
|
+
? (state.processedGroups ?? []).map((group) => group.series)
|
|
55
54
|
: resolvedSeries;
|
|
56
55
|
return [
|
|
57
56
|
...Statistics.buildForPlot({ series: measured, statistics, colorFor: colorForSeries }),
|
|
@@ -2,8 +2,10 @@ import type { AxisPlotState, PhaseStage } from '../../types/phases';
|
|
|
2
2
|
type AnyAxisState = AxisPlotState<any, any, any, any>;
|
|
3
3
|
/**
|
|
4
4
|
* The "tooltip" phase every axis-based plot shares: how a hovered row reads,
|
|
5
|
-
* as `tooltipFormat` - the same default on every plot, {@link TooltipState.default}
|
|
6
|
-
*
|
|
5
|
+
* as `tooltipFormat` - the same default on every plot, {@link TooltipState.default} -
|
|
6
|
+
* and the order its rows read in, as `tooltipSort`: highest value first, by
|
|
7
|
+
* each row's own value (a stacked row's share, not its running total). It
|
|
8
|
+
* runs after the scales, which say which axis holds categories.
|
|
7
9
|
*/
|
|
8
10
|
export declare class TooltipPhase<TState extends AnyAxisState> implements PhaseStage<TState> {
|
|
9
11
|
readonly phase: "tooltip";
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { TooltipState } from '../../layout/tooltip/TooltipState.svelte';
|
|
2
|
+
import { ValueLabels } from '../../layout/styles/ValueLabels';
|
|
2
3
|
/**
|
|
3
4
|
* The "tooltip" phase every axis-based plot shares: how a hovered row reads,
|
|
4
|
-
* as `tooltipFormat` - the same default on every plot, {@link TooltipState.default}
|
|
5
|
-
*
|
|
5
|
+
* as `tooltipFormat` - the same default on every plot, {@link TooltipState.default} -
|
|
6
|
+
* and the order its rows read in, as `tooltipSort`: highest value first, by
|
|
7
|
+
* each row's own value (a stacked row's share, not its running total). It
|
|
8
|
+
* runs after the scales, which say which axis holds categories.
|
|
6
9
|
*/
|
|
7
10
|
export class TooltipPhase {
|
|
8
11
|
phase = 'tooltip';
|
|
9
|
-
run = (state) => ({
|
|
12
|
+
run = (state) => ({
|
|
13
|
+
...state,
|
|
14
|
+
tooltipFormat: TooltipState.default(state),
|
|
15
|
+
tooltipSort: TooltipState.descending(ValueLabels.valueOf(state))
|
|
16
|
+
});
|
|
10
17
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AxisChannelName } from '../../types/layout/mapping';
|
|
2
|
+
import type { ProcessedData, ProcessedRow } from '../../types/phases';
|
|
3
|
+
/**
|
|
4
|
+
* Where the "transforms" phase's processed rows sit, read off its output
|
|
5
|
+
* (`state.processedData`) - by whichever channels the transforms left: a
|
|
6
|
+
* row's own value, a stack's `1`-`2` span, a bin's edges. Every later phase
|
|
7
|
+
* reads positions through it instead of telling those channels apart itself.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ProcessedPositions {
|
|
10
|
+
/**
|
|
11
|
+
* Where a processed row sits: the top of its value (`y2`, or `y` - `x2`/`x`
|
|
12
|
+
* when `valueAxis` is `x`) and, on the other axis, its own position (or the
|
|
13
|
+
* middle of its `1`-`2` span, a bin's centre).
|
|
14
|
+
*/
|
|
15
|
+
static of(processed: ProcessedData, valueAxis?: 'x' | 'y'): {
|
|
16
|
+
x: (row: ProcessedRow) => unknown;
|
|
17
|
+
y: (row: ProcessedRow) => unknown;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A processed row's span along `valueAxis`: from its `1` channel (a stack's
|
|
21
|
+
* bottom) or, with none, from `baseline`, to its top. `undefined` when the
|
|
22
|
+
* row has no value there. `stacked` tells a stack from plain values.
|
|
23
|
+
*/
|
|
24
|
+
static span(processed: ProcessedData, valueAxis: 'x' | 'y', baseline?: number): {
|
|
25
|
+
stacked: boolean;
|
|
26
|
+
of: (row: ProcessedRow) => [number, number] | undefined;
|
|
27
|
+
};
|
|
28
|
+
/** Every value the processed rows sit at on one axis, through each of its channels. */
|
|
29
|
+
static values(processed: ProcessedData | undefined, axis: AxisChannelName): unknown[];
|
|
30
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { resolveAccessor } from '../../plots/utils/accessors';
|
|
2
|
+
import { isGapValue } from '../../plots/utils/gaps';
|
|
3
|
+
const POSITION_CHANNELS = {
|
|
4
|
+
x: ['x', 'x1', 'x2'],
|
|
5
|
+
y: ['y', 'y1', 'y2']
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Where the "transforms" phase's processed rows sit, read off its output
|
|
9
|
+
* (`state.processedData`) - by whichever channels the transforms left: a
|
|
10
|
+
* row's own value, a stack's `1`-`2` span, a bin's edges. Every later phase
|
|
11
|
+
* reads positions through it instead of telling those channels apart itself.
|
|
12
|
+
*/
|
|
13
|
+
export class ProcessedPositions {
|
|
14
|
+
/**
|
|
15
|
+
* Where a processed row sits: the top of its value (`y2`, or `y` - `x2`/`x`
|
|
16
|
+
* when `valueAxis` is `x`) and, on the other axis, its own position (or the
|
|
17
|
+
* middle of its `1`-`2` span, a bin's centre).
|
|
18
|
+
*/
|
|
19
|
+
static of(processed, valueAxis = 'y') {
|
|
20
|
+
const { channels } = processed;
|
|
21
|
+
const read = (channel) => resolveAccessor(channel);
|
|
22
|
+
const along = (axis) => {
|
|
23
|
+
const own = channels[axis];
|
|
24
|
+
const from = channels[`${axis}1`];
|
|
25
|
+
const to = channels[`${axis}2`];
|
|
26
|
+
if (axis === valueAxis) {
|
|
27
|
+
const top = to ?? own;
|
|
28
|
+
return top !== undefined ? read(top) : () => undefined;
|
|
29
|
+
}
|
|
30
|
+
if (own !== undefined)
|
|
31
|
+
return read(own);
|
|
32
|
+
if (from === undefined || to === undefined)
|
|
33
|
+
return () => undefined;
|
|
34
|
+
return (row) => (Number(read(from)(row)) + Number(read(to)(row))) / 2;
|
|
35
|
+
};
|
|
36
|
+
return { x: along('x'), y: along('y') };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A processed row's span along `valueAxis`: from its `1` channel (a stack's
|
|
40
|
+
* bottom) or, with none, from `baseline`, to its top. `undefined` when the
|
|
41
|
+
* row has no value there. `stacked` tells a stack from plain values.
|
|
42
|
+
*/
|
|
43
|
+
static span(processed, valueAxis, baseline = 0) {
|
|
44
|
+
const from = processed.channels[`${valueAxis}1`];
|
|
45
|
+
const stacked = from !== undefined && processed.channels[`${valueAxis}2`] !== undefined;
|
|
46
|
+
const top = ProcessedPositions.of(processed, valueAxis)[valueAxis];
|
|
47
|
+
const bottom = stacked
|
|
48
|
+
? resolveAccessor(from)
|
|
49
|
+
: () => baseline;
|
|
50
|
+
return {
|
|
51
|
+
stacked,
|
|
52
|
+
of: (row) => {
|
|
53
|
+
const end = top(row);
|
|
54
|
+
const start = bottom(row);
|
|
55
|
+
if (isGapValue(end) || isGapValue(start))
|
|
56
|
+
return undefined;
|
|
57
|
+
const span = [Number(start), Number(end)];
|
|
58
|
+
return Number.isFinite(span[0]) && Number.isFinite(span[1]) ? span : undefined;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Every value the processed rows sit at on one axis, through each of its channels. */
|
|
63
|
+
static values(processed, axis) {
|
|
64
|
+
if (!processed)
|
|
65
|
+
return [];
|
|
66
|
+
return POSITION_CHANNELS[axis].flatMap((name) => {
|
|
67
|
+
const channel = processed.channels[name];
|
|
68
|
+
if (channel === undefined)
|
|
69
|
+
return [];
|
|
70
|
+
const read = resolveAccessor(channel);
|
|
71
|
+
return processed.data.map((row) => read(row));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { SegmentsConfig } from '../../types/plots/props';
|
|
3
|
-
import type { VisualGroup } from '../../types/plots/segments/config';
|
|
4
|
-
import type { PhaseStage, ProcessedData, ProcessedRow, TransformsStageOptions, TransformsStageState } from '../../types/phases';
|
|
1
|
+
import type { PhaseStage, TransformsStageOptions, TransformsStageState } from '../../types/phases';
|
|
5
2
|
/** The field each processed row carries its series name under, for its color, legend and labels. */
|
|
6
3
|
export declare const SERIES_KEY = "__series";
|
|
7
4
|
/** The field each processed row carries its series id under - the transforms' `z`, so a stack or bin keeps series apart. */
|
|
@@ -14,7 +11,8 @@ export declare const GAP_KIND = "__gapKind";
|
|
|
14
11
|
* The "transforms" phase every axis-based plot shares: runs the plot-level
|
|
15
12
|
* transforms once over every visible series' rows together, so a stack sees
|
|
16
13
|
* every series and a bin keeps each series apart (both group by series), and
|
|
17
|
-
* leaves the result in `state.processedData` for the plot's own marks
|
|
14
|
+
* leaves the result in `state.processedData` for the plot's own marks - and
|
|
15
|
+
* in `state.processedGroups`, split back into the visual groups they draw. With
|
|
18
16
|
* facet panels, the transforms run once per panel's rows, so a stack never
|
|
19
17
|
* combines two panels, and every row they return keeps its panel. A gap that
|
|
20
18
|
* `styles.gaps` fills is read at its interpolated value, so it takes its place
|
|
@@ -23,42 +21,7 @@ export declare const GAP_KIND = "__gapKind";
|
|
|
23
21
|
export declare class TransformsPhase<TState extends TransformsStageState, TCtx = unknown> implements PhaseStage<TState, TCtx> {
|
|
24
22
|
#private;
|
|
25
23
|
readonly phase: "transforms";
|
|
26
|
-
|
|
24
|
+
/** `connect`: whether a series' visual segments bridge into each other, as the kind's "data" phase splits them. */
|
|
25
|
+
constructor(options?: TransformsStageOptions<TState, TCtx>, connect?: boolean);
|
|
27
26
|
run: (state: TState, ctx: TCtx) => TState;
|
|
28
|
-
/**
|
|
29
|
-
* The processed rows split back into one group per visible series, each cut
|
|
30
|
-
* into its styled visual segments by `segments`, matched on the processed `x`.
|
|
31
|
-
* Each series reads its rows through its value axis: a stack's top, a bin's start.
|
|
32
|
-
*/
|
|
33
|
-
static buildProcessedGroups<TStyle>(state: TransformsStageState & {
|
|
34
|
-
segments?: SegmentsConfig<TStyle>;
|
|
35
|
-
}, connect?: boolean): VisualGroup<Series<ProcessedRow>, TStyle>[];
|
|
36
|
-
/**
|
|
37
|
-
* The processed rows in their own order, split into runs of consecutive rows
|
|
38
|
-
* from the same visible series, each cut into its styled visual segments like
|
|
39
|
-
* {@link TransformsPhase.buildProcessedGroups}. Drawn in turn, the runs keep the rows' order
|
|
40
|
-
* across series, so a transform that reorders rows (`sort`, `reverse`,
|
|
41
|
-
* `shuffle`) decides which rows paint on top. Unreordered, each series is one run.
|
|
42
|
-
*/
|
|
43
|
-
static buildProcessedRuns<TStyle>(state: TransformsStageState & {
|
|
44
|
-
segments?: SegmentsConfig<TStyle>;
|
|
45
|
-
}): VisualGroup<Series<ProcessedRow>, TStyle>[];
|
|
46
|
-
/**
|
|
47
|
-
* Where a processed row sits: the top of its value (`y2`, or `y` - `x2`/`x`
|
|
48
|
-
* when `valueAxis` is `x`) and, on the other axis, its own position (or the
|
|
49
|
-
* middle of its `1`-`2` span, a bin's centre).
|
|
50
|
-
*/
|
|
51
|
-
static processedPosition(processed: ProcessedData, valueAxis?: 'x' | 'y'): {
|
|
52
|
-
x: (row: ProcessedRow) => unknown;
|
|
53
|
-
y: (row: ProcessedRow) => unknown;
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* A processed row's span along `valueAxis`: from its `1` channel (a stack's
|
|
57
|
-
* bottom) or, with none, from `baseline`, to its top. `undefined` when the
|
|
58
|
-
* row has no value there. `stacked` tells a stack from plain values.
|
|
59
|
-
*/
|
|
60
|
-
static processedSpan(processed: ProcessedData, valueAxis: 'x' | 'y', baseline?: number): {
|
|
61
|
-
stacked: boolean;
|
|
62
|
-
of: (row: ProcessedRow) => [number, number] | undefined;
|
|
63
|
-
};
|
|
64
27
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Transforms } from './Transforms';
|
|
2
2
|
import { seriesIdOf } from '../../utils/grouping';
|
|
3
|
-
import { PANEL_KEY } from '../facet/
|
|
3
|
+
import { PANEL_KEY } from '../facet/FacetPanels';
|
|
4
4
|
import { resolveAccessor } from '../../plots/utils/accessors';
|
|
5
|
-
import { isGapValue } from '../../plots/utils/gaps';
|
|
6
5
|
import { buildGroupedSeries } from '../../plots/utils/segments';
|
|
7
6
|
import { PlotAxes } from '../data/PlotAxes';
|
|
8
7
|
/** The field each processed row carries its series name under, for its color, legend and labels. */
|
|
@@ -20,7 +19,8 @@ const NOT_OPTIONS = new Set(['data', 'z', 'grouping', 'transforms', 'sort', 'fil
|
|
|
20
19
|
* The "transforms" phase every axis-based plot shares: runs the plot-level
|
|
21
20
|
* transforms once over every visible series' rows together, so a stack sees
|
|
22
21
|
* every series and a bin keeps each series apart (both group by series), and
|
|
23
|
-
* leaves the result in `state.processedData` for the plot's own marks
|
|
22
|
+
* leaves the result in `state.processedData` for the plot's own marks - and
|
|
23
|
+
* in `state.processedGroups`, split back into the visual groups they draw. With
|
|
24
24
|
* facet panels, the transforms run once per panel's rows, so a stack never
|
|
25
25
|
* combines two panels, and every row they return keeps its panel. A gap that
|
|
26
26
|
* `styles.gaps` fills is read at its interpolated value, so it takes its place
|
|
@@ -29,8 +29,11 @@ const NOT_OPTIONS = new Set(['data', 'z', 'grouping', 'transforms', 'sort', 'fil
|
|
|
29
29
|
export class TransformsPhase {
|
|
30
30
|
phase = 'transforms';
|
|
31
31
|
#options;
|
|
32
|
-
|
|
32
|
+
#connect;
|
|
33
|
+
/** `connect`: whether a series' visual segments bridge into each other, as the kind's "data" phase splits them. */
|
|
34
|
+
constructor(options = {}, connect = true) {
|
|
33
35
|
this.#options = options;
|
|
36
|
+
this.#connect = connect;
|
|
34
37
|
}
|
|
35
38
|
run = (state, ctx) => {
|
|
36
39
|
const options = this.#options;
|
|
@@ -62,7 +65,7 @@ export class TransformsPhase {
|
|
|
62
65
|
if (!transforms.length) {
|
|
63
66
|
const channels = Object.fromEntries(Object.entries({ x, y }).filter(([, channel]) => channel !== undefined));
|
|
64
67
|
const processedData = { data, channels, options: {}, transforms };
|
|
65
|
-
return { ...state, processedData };
|
|
68
|
+
return this.#withGroups({ ...state, processedData });
|
|
66
69
|
}
|
|
67
70
|
const out = TransformsPhase.#runByPanel(data, (rows) => Transforms.resolve({ data: rows, x, y, grouping: SERIES_ID_KEY, transforms }, rows));
|
|
68
71
|
const channels = {};
|
|
@@ -89,8 +92,15 @@ export class TransformsPhase {
|
|
|
89
92
|
options: markOptions,
|
|
90
93
|
transforms
|
|
91
94
|
};
|
|
92
|
-
return { ...state, processedData };
|
|
95
|
+
return this.#withGroups({ ...state, processedData });
|
|
93
96
|
};
|
|
97
|
+
/** `state` with its processed rows split into the visual groups the kind's marks draw (`processedGroups`). */
|
|
98
|
+
#withGroups(state) {
|
|
99
|
+
const processedGroups = this.#options.groups === 'runs'
|
|
100
|
+
? TransformsPhase.#buildRuns(state)
|
|
101
|
+
: TransformsPhase.#buildGroups(state, this.#connect);
|
|
102
|
+
return { ...state, processedGroups };
|
|
103
|
+
}
|
|
94
104
|
/** What a gap-filled row carries: its interpolated value and the kind of zone that filled it. */
|
|
95
105
|
static #fillTags(fill) {
|
|
96
106
|
return fill ? { [GAP_VALUE]: fill.value, [GAP_KIND]: fill.kind } : {};
|
|
@@ -100,7 +110,7 @@ export class TransformsPhase {
|
|
|
100
110
|
* into its styled visual segments by `segments`, matched on the processed `x`.
|
|
101
111
|
* Each series reads its rows through its value axis: a stack's top, a bin's start.
|
|
102
112
|
*/
|
|
103
|
-
static
|
|
113
|
+
static #buildGroups(state, connect) {
|
|
104
114
|
const { data } = state.processedData;
|
|
105
115
|
const series = (state.resolvedSeries ?? []).map((s) => TransformsPhase.#processedSeries(state, s, data.filter((row) => row[SERIES_ID_KEY] === seriesIdOf(s))));
|
|
106
116
|
return buildGroupedSeries(series, state.segments, connect);
|
|
@@ -108,11 +118,11 @@ export class TransformsPhase {
|
|
|
108
118
|
/**
|
|
109
119
|
* The processed rows in their own order, split into runs of consecutive rows
|
|
110
120
|
* from the same visible series, each cut into its styled visual segments like
|
|
111
|
-
* {@link TransformsPhase
|
|
121
|
+
* {@link TransformsPhase.#buildGroups}. Drawn in turn, the runs keep the rows' order
|
|
112
122
|
* across series, so a transform that reorders rows (`sort`, `reverse`,
|
|
113
123
|
* `shuffle`) decides which rows paint on top. Unreordered, each series is one run.
|
|
114
124
|
*/
|
|
115
|
-
static
|
|
125
|
+
static #buildRuns(state) {
|
|
116
126
|
const byId = new Map((state.resolvedSeries ?? []).map((s) => [seriesIdOf(s), s]));
|
|
117
127
|
const runs = [];
|
|
118
128
|
for (const row of state.processedData.data) {
|
|
@@ -127,54 +137,6 @@ export class TransformsPhase {
|
|
|
127
137
|
}
|
|
128
138
|
return buildGroupedSeries(runs, state.segments, false);
|
|
129
139
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Where a processed row sits: the top of its value (`y2`, or `y` - `x2`/`x`
|
|
132
|
-
* when `valueAxis` is `x`) and, on the other axis, its own position (or the
|
|
133
|
-
* middle of its `1`-`2` span, a bin's centre).
|
|
134
|
-
*/
|
|
135
|
-
static processedPosition(processed, valueAxis = 'y') {
|
|
136
|
-
const { channels } = processed;
|
|
137
|
-
const read = (channel) => resolveAccessor(channel);
|
|
138
|
-
const along = (axis) => {
|
|
139
|
-
const own = channels[axis];
|
|
140
|
-
const from = channels[`${axis}1`];
|
|
141
|
-
const to = channels[`${axis}2`];
|
|
142
|
-
if (axis === valueAxis) {
|
|
143
|
-
const top = to ?? own;
|
|
144
|
-
return top !== undefined ? read(top) : () => undefined;
|
|
145
|
-
}
|
|
146
|
-
if (own !== undefined)
|
|
147
|
-
return read(own);
|
|
148
|
-
if (from === undefined || to === undefined)
|
|
149
|
-
return () => undefined;
|
|
150
|
-
return (row) => (Number(read(from)(row)) + Number(read(to)(row))) / 2;
|
|
151
|
-
};
|
|
152
|
-
return { x: along('x'), y: along('y') };
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* A processed row's span along `valueAxis`: from its `1` channel (a stack's
|
|
156
|
-
* bottom) or, with none, from `baseline`, to its top. `undefined` when the
|
|
157
|
-
* row has no value there. `stacked` tells a stack from plain values.
|
|
158
|
-
*/
|
|
159
|
-
static processedSpan(processed, valueAxis, baseline = 0) {
|
|
160
|
-
const from = processed.channels[`${valueAxis}1`];
|
|
161
|
-
const stacked = from !== undefined && processed.channels[`${valueAxis}2`] !== undefined;
|
|
162
|
-
const top = TransformsPhase.processedPosition(processed, valueAxis)[valueAxis];
|
|
163
|
-
const bottom = stacked
|
|
164
|
-
? resolveAccessor(from)
|
|
165
|
-
: () => baseline;
|
|
166
|
-
return {
|
|
167
|
-
stacked,
|
|
168
|
-
of: (row) => {
|
|
169
|
-
const end = top(row);
|
|
170
|
-
const start = bottom(row);
|
|
171
|
-
if (isGapValue(end) || isGapValue(start))
|
|
172
|
-
return undefined;
|
|
173
|
-
const span = [Number(start), Number(end)];
|
|
174
|
-
return Number.isFinite(span[0]) && Number.isFinite(span[1]) ? span : undefined;
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
140
|
/**
|
|
179
141
|
* One series accessor shared by every series as-is, so a field key keeps
|
|
180
142
|
* feeding svelteplot's axis-title inference; a per-series function otherwise.
|
|
@@ -234,6 +196,7 @@ export class TransformsPhase {
|
|
|
234
196
|
return {
|
|
235
197
|
name: source.name,
|
|
236
198
|
...(source.id !== undefined ? { id: source.id } : {}),
|
|
199
|
+
...(source.panel !== undefined ? { panel: source.panel } : {}),
|
|
237
200
|
x: read('x'),
|
|
238
201
|
y: read('y'),
|
|
239
202
|
data
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TransformsPhase } from '../../phases/transforms/TransformsPhase';
|
|
2
1
|
import { Transforms } from '../../phases/transforms/Transforms';
|
|
3
2
|
/**
|
|
4
3
|
* AreaPlot's "markers" phase: one `area` per visual segment of each series,
|
|
@@ -27,7 +26,7 @@ export function buildMarks(state, ctx) {
|
|
|
27
26
|
};
|
|
28
27
|
// The dim channel: each area reads its own series name, looked up in `opacityScale`.
|
|
29
28
|
const dimByName = (name) => dimmed ? { opacity: () => name, scales: { opacity: opacityScale } } : {};
|
|
30
|
-
const areas =
|
|
29
|
+
const areas = (state.processedGroups ?? []).flatMap((group) => group.visualSegments.map((seg) => Transforms.finish({
|
|
31
30
|
type: 'area',
|
|
32
31
|
series: group.series.name,
|
|
33
32
|
data: seg.data,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
1
2
|
import { buildMarks, buildValueLabels, buildHitAreas, buildBarGeometry } from './markers';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { SERIES_KEY, TransformsPhase } from '../../phases/transforms/TransformsPhase';
|
|
3
|
+
import { NiceTicks } from '../../layout/scales/NiceTicks';
|
|
4
|
+
import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
|
|
5
5
|
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
6
6
|
/**
|
|
7
7
|
* The domain a bar plot fits to its own bars on its value axis: from zero to
|
|
@@ -9,14 +9,13 @@ import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
|
9
9
|
* author pins the axis, and when there is no span to fit.
|
|
10
10
|
*/
|
|
11
11
|
function fitValueDomain(state) {
|
|
12
|
-
const isHorizontal = state.axes?.isHorizontal ?? false;
|
|
13
12
|
const axis = PlotAxes.valueOf(state);
|
|
14
13
|
const scale = state.scales[axis];
|
|
15
14
|
if (scale?.domain != null || scale?.ticks != null || scale?.nice != null)
|
|
16
15
|
return {};
|
|
17
16
|
// Every bar's span, as the "transforms" phase left it - its baseline is
|
|
18
17
|
// unknown until the domain is, so a plain bar reads as reaching from zero.
|
|
19
|
-
const { of: spanOf } =
|
|
18
|
+
const { of: spanOf } = ProcessedPositions.span(state.processedData, axis);
|
|
20
19
|
let lo = 0;
|
|
21
20
|
let hi = 0;
|
|
22
21
|
for (const row of state.processedData?.data ?? []) {
|
|
@@ -28,10 +27,7 @@ function fitValueDomain(state) {
|
|
|
28
27
|
}
|
|
29
28
|
if (hi <= lo)
|
|
30
29
|
return {};
|
|
31
|
-
|
|
32
|
-
const box = Margins.AXIS_ESTIMATE.contentBox(state.width, state.height);
|
|
33
|
-
const count = naturalTickCount(isHorizontal ? box.width : box.height, spacing, 2);
|
|
34
|
-
return { [axis]: { domain: niceDomain(lo, hi, count) } };
|
|
30
|
+
return { [axis]: { domain: NiceTicks.fit(state, axis, lo, hi, scale?.niceTolerance) } };
|
|
35
31
|
}
|
|
36
32
|
/**
|
|
37
33
|
* BarPlot on the shared pipeline: discrete bars with gap fills along one
|
|
@@ -59,7 +55,7 @@ export function buildBarDefaults() {
|
|
|
59
55
|
if (labels.anchor !== 'outside')
|
|
60
56
|
return [];
|
|
61
57
|
const axis = PlotAxes.valueOf(state);
|
|
62
|
-
if (
|
|
58
|
+
if (ProcessedPositions.span(state.processedData, axis).stacked)
|
|
63
59
|
return [];
|
|
64
60
|
if (!state.axes?.isHorizontal) {
|
|
65
61
|
const at = { y: -labels.offset('y') };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
1
2
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
2
|
-
import { GAP_KIND, GAP_VALUE, SERIES_ID_KEY
|
|
3
|
+
import { GAP_KIND, GAP_VALUE, SERIES_ID_KEY } from '../../phases/transforms/TransformsPhase';
|
|
3
4
|
import { seriesIdOf } from '../../utils/grouping';
|
|
4
5
|
import { resolveGapAreaStyle } from '../utils/gaps';
|
|
5
6
|
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
@@ -24,8 +25,8 @@ export function buildBarGeometry(state, seriesPadding) {
|
|
|
24
25
|
const [min, max] = [Math.min(lo, hi), Math.max(lo, hi)];
|
|
25
26
|
return min > 0 ? min : max < 0 ? max : 0;
|
|
26
27
|
})();
|
|
27
|
-
const { stacked, of: spanOf } =
|
|
28
|
-
const position =
|
|
28
|
+
const { stacked, of: spanOf } = ProcessedPositions.span(state.processedData, valueAxis, baseline);
|
|
29
|
+
const position = ProcessedPositions.of(state.processedData, valueAxis);
|
|
29
30
|
const categoryOf = (row) => position[categoryAxis](row);
|
|
30
31
|
const names = [...new Set((state.groupedSeries ?? []).map((g) => g.series.name))];
|
|
31
32
|
// A lone series, or a stack, fills its whole band: there's nothing to set side by side.
|
|
@@ -197,7 +198,7 @@ export function buildMarks(state, ctx) {
|
|
|
197
198
|
}
|
|
198
199
|
const isReal = (row) => row[GAP_VALUE] === undefined && geometry.spanOf(row) !== undefined;
|
|
199
200
|
const segmentMarkers = [];
|
|
200
|
-
|
|
201
|
+
(state.processedGroups ?? []).forEach((group) => {
|
|
201
202
|
const name = group.series.name;
|
|
202
203
|
const slice = geometry.sliceOf(name);
|
|
203
204
|
const defaultColor = colorForSeries(name);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HoverPosition } from '../../types/phases';
|
|
2
|
+
import type { BoxPlotState, BoxSummaryRow } from '../../types/plots/box/state';
|
|
3
|
+
/**
|
|
4
|
+
* The boxes a box plot draws, one summary row per series, category and facet
|
|
5
|
+
* panel - the same quartiles and whiskers svelteplot's `BoxX`/`BoxY` compute,
|
|
6
|
+
* so its hover and tooltip read a box rather than every value inside it.
|
|
7
|
+
*/
|
|
8
|
+
export declare class BoxSummaries {
|
|
9
|
+
/** One summary per box, in the order the processed rows first reach it. */
|
|
10
|
+
static of<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): BoxSummaryRow[];
|
|
11
|
+
/** Where a box's summary sits for hover: at its category, level with its median. */
|
|
12
|
+
static position<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): HoverPosition;
|
|
13
|
+
/**
|
|
14
|
+
* How a hovered box reads by default: its series, when the plot groups its
|
|
15
|
+
* rows; its category; then its five numbers, highest first, and how many
|
|
16
|
+
* values fall past the whiskers.
|
|
17
|
+
*/
|
|
18
|
+
static format<TData extends Record<string, unknown>>(state: BoxPlotState<TData>): (row: BoxSummaryRow) => string;
|
|
19
|
+
}
|