@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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
2
|
+
import { quantile } from 'd3-array';
|
|
3
|
+
import { SERIES_ID_KEY, SERIES_KEY } from '../../phases/transforms/TransformsPhase';
|
|
4
|
+
import { PANEL_KEY } from '../../phases/facet/FacetPanels';
|
|
5
|
+
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
6
|
+
import { TooltipState } from '../../layout/tooltip/TooltipState.svelte';
|
|
7
|
+
import { isGapValue } from '../utils/gaps';
|
|
8
|
+
/** The whiskers reach this many interquartile ranges past the box, as svelteplot draws them. */
|
|
9
|
+
const WHISKER_IQR = 1.5;
|
|
10
|
+
/** A summary value as a tooltip shows it: at most two decimals. */
|
|
11
|
+
const round = (value) => String(Math.round(value * 100) / 100);
|
|
12
|
+
/**
|
|
13
|
+
* The boxes a box plot draws, one summary row per series, category and facet
|
|
14
|
+
* panel - the same quartiles and whiskers svelteplot's `BoxX`/`BoxY` compute,
|
|
15
|
+
* so its hover and tooltip read a box rather than every value inside it.
|
|
16
|
+
*/
|
|
17
|
+
export class BoxSummaries {
|
|
18
|
+
/** One summary per box, in the order the processed rows first reach it. */
|
|
19
|
+
static of(state) {
|
|
20
|
+
const processed = state.processedData;
|
|
21
|
+
if (!processed?.data.length)
|
|
22
|
+
return [];
|
|
23
|
+
const valueAxis = PlotAxes.valueOf(state);
|
|
24
|
+
const categoryAxis = PlotAxes.categoryOf(state);
|
|
25
|
+
const position = ProcessedPositions.of(processed, valueAxis);
|
|
26
|
+
const valueOf = position[valueAxis];
|
|
27
|
+
const categoryOf = position[categoryAxis];
|
|
28
|
+
const categoryField = processed.channels[categoryAxis];
|
|
29
|
+
const boxes = new Map();
|
|
30
|
+
for (const row of processed.data) {
|
|
31
|
+
const value = valueOf(row);
|
|
32
|
+
if (isGapValue(value))
|
|
33
|
+
continue;
|
|
34
|
+
const n = Number(value);
|
|
35
|
+
if (!Number.isFinite(n))
|
|
36
|
+
continue;
|
|
37
|
+
const key = JSON.stringify([
|
|
38
|
+
row[SERIES_ID_KEY],
|
|
39
|
+
row[PANEL_KEY] ?? null,
|
|
40
|
+
String(categoryOf(row))
|
|
41
|
+
]);
|
|
42
|
+
const box = boxes.get(key);
|
|
43
|
+
if (box)
|
|
44
|
+
box.values.push(n);
|
|
45
|
+
else
|
|
46
|
+
boxes.set(key, { first: row, values: [n] });
|
|
47
|
+
}
|
|
48
|
+
return [...boxes.values()].map(({ first, values }) => {
|
|
49
|
+
const sorted = values.sort((a, b) => a - b);
|
|
50
|
+
const p25 = quantile(sorted, 0.25);
|
|
51
|
+
const median = quantile(sorted, 0.5);
|
|
52
|
+
const p75 = quantile(sorted, 0.75);
|
|
53
|
+
const reach = (p75 - p25) * WHISKER_IQR;
|
|
54
|
+
const inside = sorted.filter((v) => v >= p25 - reach && v <= p75 + reach);
|
|
55
|
+
const category = categoryOf(first);
|
|
56
|
+
return {
|
|
57
|
+
...(typeof categoryField === 'string' ? { [categoryField]: category } : {}),
|
|
58
|
+
[SERIES_KEY]: first[SERIES_KEY],
|
|
59
|
+
[SERIES_ID_KEY]: first[SERIES_ID_KEY],
|
|
60
|
+
...(first[PANEL_KEY] !== undefined ? { [PANEL_KEY]: first[PANEL_KEY] } : {}),
|
|
61
|
+
category,
|
|
62
|
+
min: inside[0] ?? p25,
|
|
63
|
+
p25,
|
|
64
|
+
median,
|
|
65
|
+
p75,
|
|
66
|
+
max: inside.at(-1) ?? p75,
|
|
67
|
+
count: sorted.length,
|
|
68
|
+
outliers: sorted.filter((v) => v < p25 - reach || v > p75 + reach)
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Where a box's summary sits for hover: at its category, level with its median. */
|
|
73
|
+
static position(state) {
|
|
74
|
+
const valueAxis = PlotAxes.valueOf(state);
|
|
75
|
+
const at = {
|
|
76
|
+
[valueAxis]: (row) => row.median,
|
|
77
|
+
[PlotAxes.other(valueAxis)]: (row) => row.category
|
|
78
|
+
};
|
|
79
|
+
return at;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* How a hovered box reads by default: its series, when the plot groups its
|
|
83
|
+
* rows; its category; then its five numbers, highest first, and how many
|
|
84
|
+
* values fall past the whiskers.
|
|
85
|
+
*/
|
|
86
|
+
static format(state) {
|
|
87
|
+
const grouped = state.channels?.grouping !== undefined;
|
|
88
|
+
return (row) => {
|
|
89
|
+
const lines = grouped ? [String(row[SERIES_KEY])] : [];
|
|
90
|
+
lines.push(TooltipState.value(row.category));
|
|
91
|
+
lines.push(`max: ${round(row.max)}`);
|
|
92
|
+
lines.push(`p75: ${round(row.p75)}`);
|
|
93
|
+
lines.push(`median: ${round(row.median)}`);
|
|
94
|
+
lines.push(`p25: ${round(row.p25)}`);
|
|
95
|
+
lines.push(`min: ${round(row.min)}`);
|
|
96
|
+
if (row.outliers.length)
|
|
97
|
+
lines.push(`outliers: ${row.outliers.length}`);
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends Record<string, unknown>">
|
|
2
|
+
import Plot from '../../layout/plot/Plot.svelte';
|
|
3
|
+
import { buildBoxDefaults } from './defaults';
|
|
4
|
+
import type { BoxPlotProps, BoxPlotCoreProps } from '../../types/plots/box/props';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A box plot: the distribution of each series' values per category - quartiles, whiskers and outliers - one box per category, upright, or sideways when the categories run along `y`. Draws from pre-built `series` or flat `data` rows.
|
|
8
|
+
* Optional chrome (`title`, `subtitle`, `caption`, `header`, `footer`,
|
|
9
|
+
* `legend`) frames it; `timeline` and `facet` slice its `data` rows.
|
|
10
|
+
* `margins` is bindable: it reports the margin the plot settled on.
|
|
11
|
+
*/
|
|
12
|
+
let {
|
|
13
|
+
title,
|
|
14
|
+
subtitle,
|
|
15
|
+
caption,
|
|
16
|
+
header,
|
|
17
|
+
footer,
|
|
18
|
+
legend,
|
|
19
|
+
order,
|
|
20
|
+
timeline,
|
|
21
|
+
facet,
|
|
22
|
+
tooltip,
|
|
23
|
+
margins = $bindable(),
|
|
24
|
+
...core
|
|
25
|
+
}: BoxPlotProps<TData> = $props();
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<Plot
|
|
29
|
+
core={core as BoxPlotCoreProps<TData>}
|
|
30
|
+
defaults={buildBoxDefaults}
|
|
31
|
+
bind:margins
|
|
32
|
+
{title}
|
|
33
|
+
{subtitle}
|
|
34
|
+
{caption}
|
|
35
|
+
{header}
|
|
36
|
+
{footer}
|
|
37
|
+
{legend}
|
|
38
|
+
{order}
|
|
39
|
+
{timeline}
|
|
40
|
+
{facet}
|
|
41
|
+
{tooltip}
|
|
42
|
+
/>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Plot from '../../layout/plot/Plot.svelte';
|
|
2
|
+
import type { BoxPlotProps } from '../../types/plots/box/props';
|
|
3
|
+
declare function $$render<TData extends Record<string, unknown>>(): {
|
|
4
|
+
props: BoxPlotProps<TData>;
|
|
5
|
+
exports: {};
|
|
6
|
+
bindings: "margins";
|
|
7
|
+
slots: {};
|
|
8
|
+
events: {};
|
|
9
|
+
};
|
|
10
|
+
declare class __sveltets_Render<TData extends Record<string, unknown>> {
|
|
11
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
12
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
13
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
14
|
+
bindings(): "margins";
|
|
15
|
+
exports(): {};
|
|
16
|
+
}
|
|
17
|
+
interface $$IsomorphicComponent {
|
|
18
|
+
new <TData extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
19
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
20
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
21
|
+
<TData extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
22
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
23
|
+
}
|
|
24
|
+
declare const Plot: $$IsomorphicComponent;
|
|
25
|
+
type Plot<TData extends Record<string, unknown>> = InstanceType<typeof Plot<TData>>;
|
|
26
|
+
export default Plot;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AxisPlotDefaults } from '../../types/phases';
|
|
2
|
+
import type { BoxPlotState, BoxPlotCtx } from '../../types/plots/box/state';
|
|
3
|
+
/**
|
|
4
|
+
* BoxPlot on the shared pipeline: one box per category of each series along
|
|
5
|
+
* one category axis - `x`, or `y` for horizontal boxes, as the "data" phase
|
|
6
|
+
* reads it off the values, in the order the data gives them - with its own
|
|
7
|
+
* band padding and markers. The value
|
|
8
|
+
* axis fits the values, without reaching for zero. Its plot defaults to:
|
|
9
|
+
* hover along the category axis, over one summary per box rather than every
|
|
10
|
+
* value inside it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildBoxDefaults<TData extends Record<string, unknown>>(): AxisPlotDefaults<BoxPlotState<TData>, BoxPlotCtx>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { buildMarks } from './markers';
|
|
2
|
+
import { BoxSummaries } from './BoxSummaries';
|
|
3
|
+
import { TooltipState } from '../../layout/tooltip/TooltipState.svelte';
|
|
4
|
+
/**
|
|
5
|
+
* BoxPlot on the shared pipeline: one box per category of each series along
|
|
6
|
+
* one category axis - `x`, or `y` for horizontal boxes, as the "data" phase
|
|
7
|
+
* reads it off the values, in the order the data gives them - with its own
|
|
8
|
+
* band padding and markers. The value
|
|
9
|
+
* axis fits the values, without reaching for zero. Its plot defaults to:
|
|
10
|
+
* hover along the category axis, over one summary per box rather than every
|
|
11
|
+
* value inside it.
|
|
12
|
+
*/
|
|
13
|
+
export function buildBoxDefaults() {
|
|
14
|
+
return {
|
|
15
|
+
data: {
|
|
16
|
+
discreteAxes: 1,
|
|
17
|
+
segments: { color: (style) => style?.fill, connect: false }
|
|
18
|
+
},
|
|
19
|
+
scales: {
|
|
20
|
+
bandOrder: 'data',
|
|
21
|
+
defaults: (_state, ctx) => ({
|
|
22
|
+
category: { paddingInner: ctx.cfg.box.paddingInner, paddingOuter: ctx.cfg.box.paddingOuter }
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
markers: { marks: buildMarks },
|
|
26
|
+
hover: {
|
|
27
|
+
rows: BoxSummaries.of,
|
|
28
|
+
position: BoxSummaries.position,
|
|
29
|
+
label: () => ((row) => String(row.median)),
|
|
30
|
+
format: (state) => BoxSummaries.format(state),
|
|
31
|
+
sort: () => TooltipState.descending((row) => row.median)
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BoxPlotState, BoxPlotCtx } from '../../types/plots/box/state';
|
|
2
|
+
import type { AxisBasedMarkersConfig } from '../../types/markers/common';
|
|
3
|
+
/**
|
|
4
|
+
* BoxPlot's "markers" phase: one `box` per visual segment of each series,
|
|
5
|
+
* one box per category of its rows, filled with its series' color (or its
|
|
6
|
+
* segment's own `fill`). The markers the "statistics" phase already put in
|
|
7
|
+
* `state.markers` come after, so they render on top.
|
|
8
|
+
*
|
|
9
|
+
* A series toggled off in the legend (style mode), or dimmed by hovering
|
|
10
|
+
* another one, takes the legend's disabled fill.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildMarks<TData extends Record<string, unknown>>(state: BoxPlotState<TData>, ctx: BoxPlotCtx): AxisBasedMarkersConfig[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoxPlot's "markers" phase: one `box` per visual segment of each series,
|
|
3
|
+
* one box per category of its rows, filled with its series' color (or its
|
|
4
|
+
* segment's own `fill`). The markers the "statistics" phase already put in
|
|
5
|
+
* `state.markers` come after, so they render on top.
|
|
6
|
+
*
|
|
7
|
+
* A series toggled off in the legend (style mode), or dimmed by hovering
|
|
8
|
+
* another one, takes the legend's disabled fill.
|
|
9
|
+
*/
|
|
10
|
+
export function buildMarks(state, ctx) {
|
|
11
|
+
const { channels } = state.processedData;
|
|
12
|
+
const { cfg } = ctx;
|
|
13
|
+
return (state.processedGroups ?? []).flatMap((group) => {
|
|
14
|
+
const name = group.series.name;
|
|
15
|
+
const disabled = ctx.interaction?.fillFor(name);
|
|
16
|
+
return group.visualSegments.map((seg) => {
|
|
17
|
+
const { stroke, strokeWidth, strokeOpacity, borderRadius } = seg.styles;
|
|
18
|
+
const outline = { stroke, strokeWidth, strokeOpacity, borderRadius };
|
|
19
|
+
return {
|
|
20
|
+
type: 'box',
|
|
21
|
+
series: name,
|
|
22
|
+
data: seg.data,
|
|
23
|
+
x: channels.x,
|
|
24
|
+
y: channels.y,
|
|
25
|
+
bar: {
|
|
26
|
+
// The box's own outline, from its segment; unset fields stay svelteplot's.
|
|
27
|
+
...Object.fromEntries(Object.entries(outline).filter(([, v]) => v !== undefined)),
|
|
28
|
+
fill: disabled?.fill ?? seg.styles.fill ?? state.colorForSeries(name),
|
|
29
|
+
fillOpacity: disabled?.fillOpacity ?? seg.styles.fillOpacity ?? cfg.box.fillOpacity
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LegendState } from '../../../layout/legend/LegendState.svelte';
|
|
2
|
+
import { HoverSettings } from '../../../layout/hover/HoverSettings';
|
|
2
3
|
/** Whether a pointer event is over an `inset` marker's panel, which has its own hover. */
|
|
3
4
|
export function isOverInset(e) {
|
|
4
5
|
return e.target?.closest?.('.dct-geo-inset') != null;
|
|
@@ -14,10 +15,10 @@ export function buildHover(state, ctx) {
|
|
|
14
15
|
const features = state.features;
|
|
15
16
|
const label = (f) => f.__id;
|
|
16
17
|
const points = features
|
|
17
|
-
.filter((f) => !range || !color ||
|
|
18
|
+
.filter((f) => !range || !color || LegendState.inRange(color(f), range))
|
|
18
19
|
.map((f) => ({ x: f.__cx, y: f.__cy, row: f, label: label(f) }));
|
|
19
20
|
const hover = {
|
|
20
|
-
...
|
|
21
|
+
...HoverSettings.buildConfig({
|
|
21
22
|
tooltip: tooltip,
|
|
22
23
|
format: state.tooltipFormat,
|
|
23
24
|
markers: authoredMarkers,
|
|
@@ -27,8 +28,7 @@ export function buildHover(state, ctx) {
|
|
|
27
28
|
// Match the feature under the cursor, not the nearest centroid.
|
|
28
29
|
pointMatching: 'contains'
|
|
29
30
|
}),
|
|
30
|
-
// A map has no
|
|
31
|
-
// only means "exact XY hover match", unlike heatmap's own use of it.
|
|
31
|
+
// A map has no axes for a crosshair: 'punctual' only means an exact match.
|
|
32
32
|
strategy: 'punctual',
|
|
33
33
|
crosshair: false,
|
|
34
34
|
exclude: isOverInset
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LegendState } from '../../../layout/legend/LegendState.svelte';
|
|
2
2
|
import { resolveFeatureStyle } from '../../utils/geoSegments';
|
|
3
3
|
import { DisabledStyle } from '../../../layout/legend/DisabledStyle';
|
|
4
4
|
/**
|
|
@@ -24,8 +24,7 @@ export function buildMarkers(state, ctx) {
|
|
|
24
24
|
function rangeFilteredStyle(base, f) {
|
|
25
25
|
if (range == null || !color)
|
|
26
26
|
return base;
|
|
27
|
-
|
|
28
|
-
if (v == null || (v >= range.min && v <= range.max))
|
|
27
|
+
if (LegendState.inRange(color(f), range))
|
|
29
28
|
return base;
|
|
30
29
|
return { ...base, ...DisabledStyle.fill(range.disabledStyle) };
|
|
31
30
|
}
|
|
@@ -51,7 +50,7 @@ export function buildMarkers(state, ctx) {
|
|
|
51
50
|
}
|
|
52
51
|
// What the pointer can match: the features inside the legend's range, by id.
|
|
53
52
|
const hoverable = new Map(features
|
|
54
|
-
.filter((f) => !range || !color ||
|
|
53
|
+
.filter((f) => !range || !color || LegendState.inRange(color(f), range))
|
|
55
54
|
.map((f) => [f.__id, f]));
|
|
56
55
|
const featureMarkers = styleGroups.map((group) => ({
|
|
57
56
|
type: 'geo',
|
package/dist/plots/geo/slices.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TimelineTime } from '../../layout/timeline/TimelineTime';
|
|
1
2
|
import { isFacetWrap } from '../../layout/facet/config';
|
|
2
3
|
const SLICE = Symbol('dct-geo-slice');
|
|
3
4
|
/** A slicing field's value on a feature (from its `properties`) or on a marker's own row. */
|
|
@@ -32,9 +33,11 @@ export function buildGeoSlicing(args) {
|
|
|
32
33
|
: readField(fields[dimension], row);
|
|
33
34
|
let combos = [{}];
|
|
34
35
|
for (const dimension of Object.keys(fields)) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const read = rows.map((row) => readField(fields[dimension], row)).filter((v) => v != null);
|
|
37
|
+
// Dates on the same step are one value.
|
|
38
|
+
const time = new TimelineTime(timeline?.dateFormat);
|
|
39
|
+
const keyOf = (value) => dimension === 'time' ? String(time.toTime(value) ?? value) : value;
|
|
40
|
+
const values = [...new Map(read.map((value) => [keyOf(value), value])).values()];
|
|
38
41
|
combos = combos.flatMap((combo) => values.map((value) => ({ ...combo, [dimension]: value })));
|
|
39
42
|
}
|
|
40
43
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LegendState } from '../../layout/legend/LegendState.svelte';
|
|
1
2
|
import { DisabledStyle } from '../../layout/legend/DisabledStyle';
|
|
2
3
|
import { resolveAccessor } from '../utils/accessors';
|
|
3
4
|
/** A processed channel as a per-row function. */
|
|
@@ -98,7 +99,7 @@ export function buildMarks(state, ctx) {
|
|
|
98
99
|
return map;
|
|
99
100
|
})();
|
|
100
101
|
function rangeFilteredStyle(base, value) {
|
|
101
|
-
if (range == null || (value
|
|
102
|
+
if (range == null || LegendState.inRange(value, range))
|
|
102
103
|
return base;
|
|
103
104
|
return { ...base, ...DisabledStyle.fill(range.disabledStyle) };
|
|
104
105
|
}
|
|
@@ -32,7 +32,7 @@ function mergeBin(own, cfg, horizontal) {
|
|
|
32
32
|
*/
|
|
33
33
|
export function buildHistogramDefaults() {
|
|
34
34
|
return {
|
|
35
|
-
data: { segments: { color: (style) => style?.fill } },
|
|
35
|
+
data: { segments: { color: (style) => style?.fill, connect: false } },
|
|
36
36
|
transforms: {
|
|
37
37
|
transforms: (state, ctx) => mergeBin(state.transforms ?? [], ctx.cfg, state.axes?.isHorizontal ?? false)
|
|
38
38
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
2
|
+
import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
|
|
2
3
|
import { Transforms } from '../../phases/transforms/Transforms';
|
|
3
4
|
import { resolveAccessor } from '../utils/accessors';
|
|
4
5
|
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
@@ -18,7 +19,7 @@ export function buildValueLabels(labels, state, ctx) {
|
|
|
18
19
|
const value = PlotAxes.valueOf(state);
|
|
19
20
|
const binned = PlotAxes.other(value);
|
|
20
21
|
const horizontal = value === 'x';
|
|
21
|
-
const position =
|
|
22
|
+
const position = ProcessedPositions.of(processed, value);
|
|
22
23
|
const read = (channel) => channel === undefined
|
|
23
24
|
? undefined
|
|
24
25
|
: resolveAccessor(channel);
|
|
@@ -69,8 +70,7 @@ export function buildValueLabels(labels, state, ctx) {
|
|
|
69
70
|
export function buildMarks(state, ctx) {
|
|
70
71
|
const { channels, options, transforms } = state.processedData;
|
|
71
72
|
const fillOpacity = ctx.cfg.histogram.fillOpacity;
|
|
72
|
-
|
|
73
|
-
const rects = TransformsPhase.buildProcessedGroups(state, false).flatMap((group) => {
|
|
73
|
+
const rects = (state.processedGroups ?? []).flatMap((group) => {
|
|
74
74
|
const disabled = ctx.interaction?.fillFor(group.series.name);
|
|
75
75
|
return group.visualSegments.map((seg) => Transforms.finish({
|
|
76
76
|
type: 'rect',
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
2
|
-
import { TransformsPhase } from '../../phases/transforms/TransformsPhase';
|
|
3
2
|
import { resolveGapRuns, resolveGapsForSeries } from '../utils/gaps';
|
|
4
3
|
/**
|
|
5
4
|
* Every `'line'` (+ `'dot'`, where a segment's style asks for them) marker
|
|
@@ -182,7 +181,7 @@ export function buildMarks(state, ctx) {
|
|
|
182
181
|
const { colorForSeries, gaps } = state;
|
|
183
182
|
const disabledStrokeFor = (name) => ctx.interaction?.strokeFor(name);
|
|
184
183
|
const disabledDotsFor = (name) => ctx.interaction?.dotsFor(name);
|
|
185
|
-
const groups =
|
|
184
|
+
const groups = state.processedGroups ?? [];
|
|
186
185
|
const lineMarkers = buildLineMarkers({
|
|
187
186
|
groupedSeries: groups,
|
|
188
187
|
colorFor: colorForSeries,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
1
2
|
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
2
3
|
import { buildMarks, buildValueLabels } from './markers';
|
|
3
4
|
import { ValueLabels } from '../../layout/styles/ValueLabels';
|
|
4
5
|
import { seriesIdOf } from '../../utils/grouping';
|
|
5
6
|
import { isGapValue } from '../utils/gaps';
|
|
6
|
-
import { SERIES_ID_KEY
|
|
7
|
+
import { SERIES_ID_KEY } from '../../phases/transforms/TransformsPhase';
|
|
7
8
|
const byName = (a, b) => a.name.localeCompare(b.name);
|
|
8
9
|
/**
|
|
9
10
|
* PyramidPlot on the shared pipeline. Its `x`/`y` are the visual axes: the one
|
|
@@ -64,7 +65,7 @@ export function buildPyramidDefaults() {
|
|
|
64
65
|
// side - and its category, and reads as its magnitude.
|
|
65
66
|
position: (state) => {
|
|
66
67
|
const value = PlotAxes.valueOf(state);
|
|
67
|
-
const place =
|
|
68
|
+
const place = ProcessedPositions.of(state.processedData, value);
|
|
68
69
|
const valueOf = ValueLabels.valueOf(state);
|
|
69
70
|
const sideOf = new Map((state.resolvedSeries ?? []).map((s) => [seriesIdOf(s), s.side]));
|
|
70
71
|
const signed = (row) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveAccessor } from '../utils/accessors';
|
|
2
|
-
import { SERIES_ID_KEY
|
|
2
|
+
import { SERIES_ID_KEY } from '../../phases/transforms/TransformsPhase';
|
|
3
3
|
import { seriesIdOf } from '../../utils/grouping';
|
|
4
4
|
import { PlotAxes } from '../../phases/data/PlotAxes';
|
|
5
5
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
@@ -23,7 +23,7 @@ export function buildValueLabels(labels, state, ctx) {
|
|
|
23
23
|
const categoryAxis = PlotAxes.categoryOf(state);
|
|
24
24
|
const valueAxis = PlotAxes.valueOf(state);
|
|
25
25
|
const isHorizontal = state.axes?.isHorizontal ?? true;
|
|
26
|
-
return
|
|
26
|
+
return (state.processedGroups ?? []).flatMap(({ series }) => {
|
|
27
27
|
const id = seriesIdOf(series);
|
|
28
28
|
const side = state.resolvedSeries.find((s) => seriesIdOf(s) === id).side;
|
|
29
29
|
const isLeft = side === 'left';
|
|
@@ -93,10 +93,7 @@ export function buildMarks(state, ctx) {
|
|
|
93
93
|
[`${valueAxis}2`]: to
|
|
94
94
|
});
|
|
95
95
|
// The real bars draw the rows the "transforms" phase left, read through its channels.
|
|
96
|
-
const processedGroups = new Map(
|
|
97
|
-
seriesIdOf(g.series),
|
|
98
|
-
g
|
|
99
|
-
]));
|
|
96
|
+
const processedGroups = new Map((state.processedGroups ?? []).map((g) => [seriesIdOf(g.series), g]));
|
|
100
97
|
const markers = [];
|
|
101
98
|
for (const group of groupedSeries) {
|
|
102
99
|
// Forwarded raw (a field key or function, whatever the series' category is)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
1
2
|
import { buildMarks, buildValueLabels } from './markers';
|
|
2
|
-
import { TransformsPhase } from '../../phases/transforms/TransformsPhase';
|
|
3
3
|
import { Margins } from '../../layout/margins/Margins';
|
|
4
4
|
import { MarkerAxisValues } from '../../phases/scales/MarkerAxisValues';
|
|
5
5
|
/** Stand-in max render radius for a channel-bound `r` (svelteplot's own auto sqrt scale). */
|
|
@@ -28,7 +28,7 @@ function paddedDomain(values, radiusPx, sizePx) {
|
|
|
28
28
|
function fitDomains(state, ctx) {
|
|
29
29
|
const { scales, channels, groupedSeries } = state;
|
|
30
30
|
const processed = state.processedData;
|
|
31
|
-
const position =
|
|
31
|
+
const position = ProcessedPositions.of(processed);
|
|
32
32
|
const extra = {
|
|
33
33
|
x: MarkerAxisValues.of(state.markers ?? [], 'x'),
|
|
34
34
|
y: MarkerAxisValues.of(state.markers ?? [], 'y')
|
|
@@ -66,6 +66,7 @@ export function buildScatterDefaults() {
|
|
|
66
66
|
connect: false
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
|
+
transforms: { groups: 'runs' },
|
|
69
70
|
scales: { fit: fitDomains },
|
|
70
71
|
markers: { marks: buildMarks, valueLabels: buildValueLabels },
|
|
71
72
|
hover: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ProcessedPositions } from '../../phases/transforms/ProcessedPositions';
|
|
2
|
+
import { SERIES_KEY } from '../../phases/transforms/TransformsPhase';
|
|
2
3
|
import { ValueLabels } from '../../layout/styles/ValueLabels';
|
|
3
4
|
/**
|
|
4
5
|
* ScatterPlot's `styles.values` as one `'text'` marker over the same
|
|
@@ -11,7 +12,7 @@ export function buildValueLabels(labels, state, ctx) {
|
|
|
11
12
|
if (!processedData?.data.length)
|
|
12
13
|
return [];
|
|
13
14
|
const { font, format } = labels;
|
|
14
|
-
const position =
|
|
15
|
+
const position = ProcessedPositions.of(processedData);
|
|
15
16
|
const nameOf = (d) => String(d[SERIES_KEY]);
|
|
16
17
|
const valueOf = ValueLabels.valueOf(state);
|
|
17
18
|
return [
|
|
@@ -89,7 +90,7 @@ export function buildMarks(state, ctx) {
|
|
|
89
90
|
}
|
|
90
91
|
: undefined;
|
|
91
92
|
const markers = [];
|
|
92
|
-
|
|
93
|
+
(state.processedGroups ?? []).forEach((group) => {
|
|
93
94
|
const defaultColor = colorForSeries(group.series.name);
|
|
94
95
|
const disabledDots = interaction?.dotsFor(group.series.name);
|
|
95
96
|
for (const seg of group.visualSegments) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StrokeStyle, DotStyle, FontStyle, AreaStyle } from '../plots/styling';
|
|
2
|
-
import type { SymbolType } from '../plots/constants';
|
|
2
|
+
import type { LineAnchor, SymbolType, TextAnchor } from '../plots/constants';
|
|
3
3
|
import type { PlotChromeEntry } from '../plots/props';
|
|
4
|
+
import type { EdgeTicksOptions } from '../layout/scales';
|
|
4
5
|
export type TextStyle = {
|
|
5
6
|
/** font-family */
|
|
6
7
|
font: string;
|
|
@@ -139,8 +140,11 @@ export type PlotStylesConfig = {
|
|
|
139
140
|
yTickPadding: number;
|
|
140
141
|
grid: boolean;
|
|
141
142
|
locale: string;
|
|
142
|
-
/** Compact units abbreviated (`'short'`, the default: `1 M`) or spelled out (`'long'`: `1 millón`). Axes and size legends. */
|
|
143
143
|
compactDisplay: 'short' | 'long';
|
|
144
|
+
edgeTicks: {
|
|
145
|
+
x: EdgeTicksOptions;
|
|
146
|
+
y: EdgeTicksOptions;
|
|
147
|
+
};
|
|
144
148
|
};
|
|
145
149
|
tooltip: {
|
|
146
150
|
background: string;
|
|
@@ -239,6 +243,15 @@ export type PlotStylesConfig = {
|
|
|
239
243
|
/** Fill opacity of every bar, so a grouped histogram's bars behind another group's still show. */
|
|
240
244
|
fillOpacity: number;
|
|
241
245
|
};
|
|
246
|
+
/** Default box-plot styling. */
|
|
247
|
+
box: {
|
|
248
|
+
/** Fill opacity of every box that sets none of its own. */
|
|
249
|
+
fillOpacity: number;
|
|
250
|
+
/** Fallback gap (0-1) between the boxes of neighbouring categories, when a plot doesn't set its own `scales.x/y.paddingInner`. */
|
|
251
|
+
paddingInner: number;
|
|
252
|
+
/** Fallback gap (0-1) before the first and after the last box, when a plot doesn't set its own `scales.x/y.paddingOuter`. */
|
|
253
|
+
paddingOuter: number;
|
|
254
|
+
};
|
|
242
255
|
/** Default scatter point geometry. */
|
|
243
256
|
scatter: {
|
|
244
257
|
dotRadius: number;
|
|
@@ -324,6 +337,15 @@ export type PlotStylesConfig = {
|
|
|
324
337
|
font: FontStyle;
|
|
325
338
|
area: AreaStyle;
|
|
326
339
|
};
|
|
340
|
+
/** The `delta` marker's label offset and anchor, per axis the delta is taken on. A marker's own `text` wins. */
|
|
341
|
+
delta: {
|
|
342
|
+
text: Record<'x' | 'y', {
|
|
343
|
+
dx: number;
|
|
344
|
+
dy: number;
|
|
345
|
+
lineAnchor: LineAnchor;
|
|
346
|
+
textAnchor: TextAnchor;
|
|
347
|
+
}>;
|
|
348
|
+
};
|
|
327
349
|
facet: {
|
|
328
350
|
/** Position badge shown in `carousel`/`paginated` facet modes. */
|
|
329
351
|
indicator: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HoverStrategy } from './hover';
|
|
2
|
-
import type { Orientation, PlayerConfig,
|
|
2
|
+
import type { Orientation, PlayerConfig, TimeInput, TimelineCallbacks, TimelineType } from './timeline';
|
|
3
3
|
/**
|
|
4
4
|
* How a `PlotComposer` arranges its panels: `areas` names the grid cells
|
|
5
5
|
* (one string per row, `.` for empty), else panels flow into `columns`;
|
|
@@ -20,7 +20,10 @@ export type ComposerTags = 'A' | 'a' | '1' | 'I' | 'i' | string[];
|
|
|
20
20
|
/** One timeline for every panel: plots with a `timeline` follow its `progress` over `values`. */
|
|
21
21
|
export type ComposerTimelineConfig = TimelineCallbacks & {
|
|
22
22
|
progress?: number;
|
|
23
|
-
|
|
23
|
+
/** The steps: numbers, `Date`s or date strings. */
|
|
24
|
+
values: TimeInput[];
|
|
25
|
+
/** How a non-ISO date string in `values` reads. */
|
|
26
|
+
dateFormat?: string;
|
|
24
27
|
/** How it draws. Defaults to `'player'`. */
|
|
25
28
|
type?: TimelineType;
|
|
26
29
|
/** The player bar's own options, when `type` is `'player'`. */
|
|
@@ -76,6 +76,18 @@ export type DivergingScaleType = (typeof DIVERGING_SCALE_TYPES)[number];
|
|
|
76
76
|
/** Scale types that bucket a numeric domain into a fixed set of output values instead of interpolating. */
|
|
77
77
|
export declare const BINNED_SCALE_TYPES: readonly ["threshold", "quantile", "quantize"];
|
|
78
78
|
export type BinnedScaleType = (typeof BINNED_SCALE_TYPES)[number];
|
|
79
|
+
/**
|
|
80
|
+
* What an axis does when the tick at the data's first or last value would
|
|
81
|
+
* crowd the nice tick next to it: `'drop-nice'` keeps the data's tick and
|
|
82
|
+
* drops the nice one, `'drop-edge'` keeps the nice tick and drops the data's,
|
|
83
|
+
* `'extend'` widens the axis to the next nice tick past the data.
|
|
84
|
+
*/
|
|
85
|
+
export type EdgeTickCollision = 'drop-nice' | 'drop-edge' | 'extend';
|
|
86
|
+
/** Whether an axis adds ticks at the data's first and last values, and what it does when one crowds a nice tick. */
|
|
87
|
+
export type EdgeTicksOptions = {
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
collision: EdgeTickCollision;
|
|
90
|
+
};
|
|
79
91
|
/**
|
|
80
92
|
* Scale overrides for one plot axis - svelteplot's `ScaleOptions` and
|
|
81
93
|
* `XScaleOptions`, plus this package's own fields. `TData` is the row type
|
|
@@ -94,6 +106,10 @@ export type AxisScale<TData = Record<string, unknown>> = {
|
|
|
94
106
|
interval?: string | number;
|
|
95
107
|
clamp?: boolean;
|
|
96
108
|
nice?: boolean;
|
|
109
|
+
niceTolerance?: number;
|
|
110
|
+
edgeTicks?: boolean | {
|
|
111
|
+
collision?: EdgeTickCollision;
|
|
112
|
+
};
|
|
97
113
|
zero?: boolean;
|
|
98
114
|
round?: boolean;
|
|
99
115
|
percent?: boolean;
|