@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
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import { resolveAccessor } from '../../plots/utils/accessors';
|
|
4
4
|
import TimelineFrame from './TimelineFrame.svelte';
|
|
5
5
|
import { TimelineSteps } from './TimelineSteps';
|
|
6
|
+
import { TimelineTime } from './TimelineTime';
|
|
6
7
|
import { Interpolation } from '../../utils/Interpolation';
|
|
7
8
|
import type {
|
|
8
9
|
Orientation,
|
|
10
|
+
TimeInput,
|
|
9
11
|
TimeValue,
|
|
10
12
|
TimelineChildSnippet,
|
|
11
13
|
TimelineInterpolation
|
|
@@ -22,6 +24,7 @@
|
|
|
22
24
|
let {
|
|
23
25
|
data,
|
|
24
26
|
accessor,
|
|
27
|
+
dateFormat,
|
|
25
28
|
progress,
|
|
26
29
|
values,
|
|
27
30
|
orientation = 'vertical',
|
|
@@ -35,28 +38,44 @@
|
|
|
35
38
|
child
|
|
36
39
|
}: {
|
|
37
40
|
data: TRow[];
|
|
38
|
-
accessor: Accessor<TRow,
|
|
41
|
+
accessor: Accessor<TRow, TimeInput>;
|
|
42
|
+
/** How a non-ISO date string reads. */
|
|
43
|
+
dateFormat?: string;
|
|
39
44
|
progress: number;
|
|
40
|
-
values?:
|
|
45
|
+
values?: TimeInput[];
|
|
41
46
|
orientation?: Orientation;
|
|
42
47
|
show?: boolean;
|
|
43
48
|
thickness?: number;
|
|
44
49
|
filterMode?: 'exact' | 'upTo';
|
|
45
50
|
interpolate?: TimelineInterpolation<TRow>;
|
|
46
|
-
/** Told the steps,
|
|
47
|
-
onsteps?: (values: TimeValue[]) => void;
|
|
51
|
+
/** Told the steps, and whether they are dates, when they change. */
|
|
52
|
+
onsteps?: (values: TimeValue[], temporal: boolean) => void;
|
|
48
53
|
width: number;
|
|
49
54
|
height: number;
|
|
50
55
|
child: TimelineChildSnippet<TRow>;
|
|
51
56
|
} = $props();
|
|
52
57
|
|
|
53
|
-
const
|
|
58
|
+
const time = $derived(new TimelineTime(dateFormat));
|
|
59
|
+
const getRaw = $derived(resolveAccessor<TRow>(accessor) as (row: TRow) => unknown);
|
|
60
|
+
/** A row's step, or `undefined` when its value reads as none. */
|
|
61
|
+
const getTime = $derived((row: TRow) => time.toTime(getRaw(row)));
|
|
62
|
+
/** The rows with a step; one without shows at every step. */
|
|
63
|
+
const timed = $derived(data.filter((row) => getTime(row) !== undefined));
|
|
64
|
+
const untimed = $derived(data.filter((row) => getTime(row) === undefined));
|
|
54
65
|
|
|
55
|
-
const steps = $derived(
|
|
66
|
+
const steps = $derived(
|
|
67
|
+
TimelineSteps.of(
|
|
68
|
+
values?.map((value) => time.toTime(value)).filter((v): v is TimeValue => v !== undefined),
|
|
69
|
+
timed,
|
|
70
|
+
getTime as (row: TRow) => TimeValue
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
const temporal = $derived(time.isTemporal(values?.length ? values : timed.map(getRaw)));
|
|
56
74
|
|
|
57
75
|
$effect(() => {
|
|
58
76
|
const found = steps.values;
|
|
59
|
-
|
|
77
|
+
const dates = temporal;
|
|
78
|
+
untrack(() => onsteps?.(found, dates));
|
|
60
79
|
});
|
|
61
80
|
|
|
62
81
|
const progressEasingFn = $derived(Interpolation.buildEasing(interpolate?.progressEasing));
|
|
@@ -77,12 +96,19 @@
|
|
|
77
96
|
);
|
|
78
97
|
return upper;
|
|
79
98
|
}
|
|
80
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
...upper,
|
|
101
|
+
[accessor]: time.toValueLike(x, upper[accessor]),
|
|
102
|
+
[valueField]: value
|
|
103
|
+
} as TRow;
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
const filtered = $derived.by(() => {
|
|
84
107
|
const discrete = () =>
|
|
85
|
-
data.filter((d) =>
|
|
108
|
+
data.filter((d) => {
|
|
109
|
+
const at = getTime(d);
|
|
110
|
+
return at === undefined || TimelineSteps.matches(at, selectedValue, filterMode);
|
|
111
|
+
});
|
|
86
112
|
|
|
87
113
|
if (!interpolate) return discrete();
|
|
88
114
|
if (!interpolate.value) {
|
|
@@ -96,14 +122,14 @@
|
|
|
96
122
|
const getValue = resolveAccessor<TRow>(interpolate.value) as (d: TRow) => number;
|
|
97
123
|
const create = interpolate.create ?? defaultCreate;
|
|
98
124
|
|
|
99
|
-
const groups = Map.groupBy(
|
|
125
|
+
const groups = Map.groupBy(timed, (row) => String(getKey(row)));
|
|
100
126
|
|
|
101
|
-
const result: TRow[] = [];
|
|
127
|
+
const result: TRow[] = [...untimed];
|
|
102
128
|
for (const rows of groups.values()) {
|
|
103
129
|
const tweened = Interpolation.tweenSeries({
|
|
104
130
|
rows,
|
|
105
131
|
x: currentTime,
|
|
106
|
-
getTime,
|
|
132
|
+
getTime: getTime as (row: TRow) => TimeValue,
|
|
107
133
|
getValue,
|
|
108
134
|
create,
|
|
109
135
|
segmentEasing: segmentEasingFn
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import type { Orientation, TimeValue, TimelineChildSnippet, TimelineInterpolation } from '../../types/layout/timeline';
|
|
1
|
+
import type { Orientation, TimeInput, TimeValue, TimelineChildSnippet, TimelineInterpolation } from '../../types/layout/timeline';
|
|
2
2
|
import type { Accessor } from '../../types/plots/data/common';
|
|
3
3
|
declare function $$render<TRow extends Record<string, unknown>>(): {
|
|
4
4
|
props: {
|
|
5
5
|
data: TRow[];
|
|
6
|
-
accessor: Accessor<TRow,
|
|
6
|
+
accessor: Accessor<TRow, TimeInput>;
|
|
7
|
+
/** How a non-ISO date string reads. */
|
|
8
|
+
dateFormat?: string;
|
|
7
9
|
progress: number;
|
|
8
|
-
values?:
|
|
10
|
+
values?: TimeInput[];
|
|
9
11
|
orientation?: Orientation;
|
|
10
12
|
show?: boolean;
|
|
11
13
|
thickness?: number;
|
|
12
14
|
filterMode?: "exact" | "upTo";
|
|
13
15
|
interpolate?: TimelineInterpolation<TRow>;
|
|
14
|
-
/** Told the steps,
|
|
15
|
-
onsteps?: (values: TimeValue[]) => void;
|
|
16
|
+
/** Told the steps, and whether they are dates, when they change. */
|
|
17
|
+
onsteps?: (values: TimeValue[], temporal: boolean) => void;
|
|
16
18
|
width: number;
|
|
17
19
|
height: number;
|
|
18
20
|
child: TimelineChildSnippet<TRow>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { PlayerConfig, PlayerControl, Orientation, TimeValue, TimelineCallbacks, TimelineType } from '../../types/layout/timeline';
|
|
1
|
+
import type { PlayerConfig, PlayerControl, Orientation, TimeInput, TimeValue, TimelineStep, TimelineCallbacks, TimelineType } from '../../types/layout/timeline';
|
|
2
2
|
/** What a {@link TimelineState} reads from the timeline's owner, each time it needs it. */
|
|
3
3
|
export type TimelineSettings = TimelineCallbacks & {
|
|
4
4
|
type?: TimelineType;
|
|
5
5
|
player?: PlayerConfig;
|
|
6
6
|
progress?: number;
|
|
7
|
-
values?:
|
|
7
|
+
values?: TimeInput[];
|
|
8
|
+
/** How a non-ISO date string in `values` reads. */
|
|
9
|
+
dateFormat?: string;
|
|
8
10
|
show?: boolean;
|
|
9
11
|
orientation?: Orientation;
|
|
10
12
|
thickness?: number;
|
|
@@ -37,8 +39,10 @@ export declare class TimelineState {
|
|
|
37
39
|
get shown(): boolean;
|
|
38
40
|
/** The steps it moves across, ascending: the owner's `values`, else those its plots report. */
|
|
39
41
|
get steps(): TimeValue[];
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
+
/** Whether the steps are dates. */
|
|
43
|
+
get temporal(): boolean;
|
|
44
|
+
/** Reports the steps the plots found in their rows, and whether they are dates. */
|
|
45
|
+
setSteps(values: TimeValue[], temporal?: boolean): void;
|
|
42
46
|
/** The position across the steps, `0`-`1`. */
|
|
43
47
|
get progress(): number;
|
|
44
48
|
/** The step the position lands on, as an index; `-1` without steps. */
|
|
@@ -55,8 +59,12 @@ export declare class TimelineState {
|
|
|
55
59
|
get speeds(): number[];
|
|
56
60
|
/** Milliseconds each step lasts at speed `1`. */
|
|
57
61
|
get stepDuration(): number;
|
|
58
|
-
/** How the
|
|
62
|
+
/** How a step reads: the player's `format`, else a date label or the number. */
|
|
59
63
|
get format(): (value: TimeValue) => string;
|
|
64
|
+
/** A step as authors see it. */
|
|
65
|
+
step(value: TimeValue): TimelineStep;
|
|
66
|
+
/** An author's step as the timeline's own, or `undefined` when it reads as none. */
|
|
67
|
+
timeOf(value: TimeInput): TimeValue | undefined;
|
|
60
68
|
/** Moves to `progress` (`0`-`1`), and tells the owner. */
|
|
61
69
|
seek(progress: number): void;
|
|
62
70
|
/** Moves to the step at `index`. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getContext, setContext, untrack } from 'svelte';
|
|
2
2
|
import { getConfiguration } from '../../configuration/config.svelte';
|
|
3
3
|
import { TimelineSteps } from './TimelineSteps';
|
|
4
|
+
import { TimelineTime } from './TimelineTime';
|
|
4
5
|
const TIMELINE_KEY = Symbol('dct-timeline');
|
|
5
6
|
/** The longest a frame counts for while playing, so a hidden tab doesn't jump ahead when it comes back. */
|
|
6
7
|
const MAX_FRAME_MS = 250;
|
|
@@ -17,6 +18,7 @@ const MAX_FRAME_MS = 250;
|
|
|
17
18
|
export class TimelineState {
|
|
18
19
|
#settings;
|
|
19
20
|
#steps = $state([]);
|
|
21
|
+
#temporal = $state(false);
|
|
20
22
|
#own = $state(null);
|
|
21
23
|
#playing = $state(false);
|
|
22
24
|
#loop = $state(false);
|
|
@@ -58,14 +60,33 @@ export class TimelineState {
|
|
|
58
60
|
}
|
|
59
61
|
/** The steps it moves across, ascending: the owner's `values`, else those its plots report. */
|
|
60
62
|
get steps() {
|
|
61
|
-
const values = this.#
|
|
62
|
-
return values
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
|
|
63
|
+
const values = this.#ownerValues;
|
|
64
|
+
return values ? new TimelineSteps(values).values : this.#steps;
|
|
65
|
+
}
|
|
66
|
+
/** Whether the steps are dates. */
|
|
67
|
+
get temporal() {
|
|
68
|
+
const settings = this.#settings();
|
|
69
|
+
if (!settings?.values?.length)
|
|
70
|
+
return this.#temporal;
|
|
71
|
+
return new TimelineTime(settings.dateFormat).isTemporal(settings.values);
|
|
72
|
+
}
|
|
73
|
+
/** Reports the steps the plots found in their rows, and whether they are dates. */
|
|
74
|
+
setSteps(values, temporal = false) {
|
|
66
75
|
const same = values.length === this.#steps.length && values.every((v, i) => v === this.#steps[i]);
|
|
67
76
|
if (!same)
|
|
68
77
|
this.#steps = values;
|
|
78
|
+
if (temporal !== this.#temporal)
|
|
79
|
+
this.#temporal = temporal;
|
|
80
|
+
}
|
|
81
|
+
/** The owner's `values` as steps, or `undefined` without any. */
|
|
82
|
+
get #ownerValues() {
|
|
83
|
+
const settings = this.#settings();
|
|
84
|
+
if (!settings?.values?.length)
|
|
85
|
+
return undefined;
|
|
86
|
+
const time = new TimelineTime(settings.dateFormat);
|
|
87
|
+
return settings.values
|
|
88
|
+
.map((value) => time.toTime(value))
|
|
89
|
+
.filter((value) => value !== undefined);
|
|
69
90
|
}
|
|
70
91
|
/** The position across the steps, `0`-`1`. */
|
|
71
92
|
get progress() {
|
|
@@ -103,9 +124,22 @@ export class TimelineState {
|
|
|
103
124
|
get stepDuration() {
|
|
104
125
|
return (this.#settings()?.player?.stepDuration ?? getConfiguration().timeline.player.stepDuration);
|
|
105
126
|
}
|
|
106
|
-
/** How the
|
|
127
|
+
/** How a step reads: the player's `format`, else a date label or the number. */
|
|
107
128
|
get format() {
|
|
108
|
-
|
|
129
|
+
const own = this.#settings()?.player?.format;
|
|
130
|
+
if (own)
|
|
131
|
+
return (value) => own(this.step(value));
|
|
132
|
+
if (this.temporal)
|
|
133
|
+
return TimelineTime.dateLabel(this.steps, getConfiguration().axis.locale);
|
|
134
|
+
return (value) => String(value);
|
|
135
|
+
}
|
|
136
|
+
/** A step as authors see it. */
|
|
137
|
+
step(value) {
|
|
138
|
+
return TimelineTime.toStep(value, this.temporal);
|
|
139
|
+
}
|
|
140
|
+
/** An author's step as the timeline's own, or `undefined` when it reads as none. */
|
|
141
|
+
timeOf(value) {
|
|
142
|
+
return new TimelineTime(this.#settings()?.dateFormat).toTime(value);
|
|
109
143
|
}
|
|
110
144
|
/** Moves to `progress` (`0`-`1`), and tells the owner. */
|
|
111
145
|
seek(progress) {
|
|
@@ -196,7 +230,7 @@ export class TimelineState {
|
|
|
196
230
|
const value = this.value;
|
|
197
231
|
untrack(() => {
|
|
198
232
|
if (last !== undefined && value !== undefined && value !== last) {
|
|
199
|
-
this.#settings()?.onvalue?.(value);
|
|
233
|
+
this.#settings()?.onvalue?.(this.step(value));
|
|
200
234
|
}
|
|
201
235
|
last = value;
|
|
202
236
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TimeValue, TimelineStep } from '../../types/layout/timeline';
|
|
2
|
+
/** Reads a timeline's values (numbers, `Date`s, ISO or `dateFormat` strings) as numeric steps, and back. */
|
|
3
|
+
export declare class TimelineTime {
|
|
4
|
+
#private;
|
|
5
|
+
readonly dateFormat?: string | undefined;
|
|
6
|
+
/** @param dateFormat How a non-ISO date string reads, e.g. `'DD/MM/YYYY'`. */
|
|
7
|
+
constructor(dateFormat?: string | undefined);
|
|
8
|
+
/** A value as its step, or `undefined` when it reads as none. */
|
|
9
|
+
toTime(value: unknown): TimeValue | undefined;
|
|
10
|
+
/** A step as authors see it: a `Date` when `temporal`, else the number. */
|
|
11
|
+
static toStep(value: TimeValue, temporal: boolean): TimelineStep;
|
|
12
|
+
/** Whether `values` hold dates rather than plain numbers. */
|
|
13
|
+
isTemporal(values: unknown[]): boolean;
|
|
14
|
+
/** A step written back in the same form as `like`, for interpolated rows. */
|
|
15
|
+
toValueLike(time: TimeValue, like: unknown): unknown;
|
|
16
|
+
/** A date label as precise as the steps need: year, month, day or time. */
|
|
17
|
+
static dateLabel(steps: TimeValue[], locale?: string): (time: TimeValue) => string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** The `dateFormat` tokens and the UTC date part each reads or writes. */
|
|
2
|
+
const TOKENS = {
|
|
3
|
+
YYYY: { group: '(\\d{4})', get: (date) => date.getUTCFullYear(), width: 4 },
|
|
4
|
+
MM: { group: '(\\d{1,2})', get: (date) => date.getUTCMonth() + 1, width: 2 },
|
|
5
|
+
DD: { group: '(\\d{1,2})', get: (date) => date.getUTCDate(), width: 2 },
|
|
6
|
+
HH: { group: '(\\d{1,2})', get: (date) => date.getUTCHours(), width: 2 },
|
|
7
|
+
mm: { group: '(\\d{1,2})', get: (date) => date.getUTCMinutes(), width: 2 },
|
|
8
|
+
ss: { group: '(\\d{1,2})', get: (date) => date.getUTCSeconds(), width: 2 }
|
|
9
|
+
};
|
|
10
|
+
const TOKEN_PATTERN = /YYYY|MM|DD|HH|mm|ss/g;
|
|
11
|
+
/** An ISO 8601 date, from a bare year to a full date-time. */
|
|
12
|
+
const ISO_DATE = /^\d{4}(-\d{2}(-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?)?)?$/;
|
|
13
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
14
|
+
/** Reads a timeline's values (numbers, `Date`s, ISO or `dateFormat` strings) as numeric steps, and back. */
|
|
15
|
+
export class TimelineTime {
|
|
16
|
+
dateFormat;
|
|
17
|
+
/** @param dateFormat How a non-ISO date string reads, e.g. `'DD/MM/YYYY'`. */
|
|
18
|
+
constructor(dateFormat) {
|
|
19
|
+
this.dateFormat = dateFormat;
|
|
20
|
+
}
|
|
21
|
+
/** A value as its step, or `undefined` when it reads as none. */
|
|
22
|
+
toTime(value) {
|
|
23
|
+
if (typeof value === 'number')
|
|
24
|
+
return Number.isFinite(value) ? value : undefined;
|
|
25
|
+
if (value instanceof Date) {
|
|
26
|
+
const time = value.getTime();
|
|
27
|
+
return Number.isFinite(time) ? time : undefined;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value !== 'string')
|
|
30
|
+
return undefined;
|
|
31
|
+
const text = value.trim();
|
|
32
|
+
if (this.dateFormat) {
|
|
33
|
+
const parsed = TimelineTime.#parse(text, this.dateFormat);
|
|
34
|
+
if (parsed !== undefined)
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
if (text !== '' && Number.isFinite(Number(text)) && !ISO_DATE.test(text))
|
|
38
|
+
return Number(text);
|
|
39
|
+
if (ISO_DATE.test(text)) {
|
|
40
|
+
// A bare year stays a year, like a number.
|
|
41
|
+
if (/^\d{4}$/.test(text))
|
|
42
|
+
return Number(text);
|
|
43
|
+
const time = Date.parse(/^\d{4}-\d{2}$/.test(text) ? `${text}-01` : text);
|
|
44
|
+
return Number.isFinite(time) ? time : undefined;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/** A step as authors see it: a `Date` when `temporal`, else the number. */
|
|
49
|
+
static toStep(value, temporal) {
|
|
50
|
+
return temporal ? new Date(value) : value;
|
|
51
|
+
}
|
|
52
|
+
/** Whether `values` hold dates rather than plain numbers. */
|
|
53
|
+
isTemporal(values) {
|
|
54
|
+
return values.some((value) => {
|
|
55
|
+
if (value instanceof Date)
|
|
56
|
+
return true;
|
|
57
|
+
if (typeof value !== 'string')
|
|
58
|
+
return false;
|
|
59
|
+
const text = value.trim();
|
|
60
|
+
return !/^-?\d+(\.\d+)?$/.test(text) && this.toTime(text) !== undefined;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** A step written back in the same form as `like`, for interpolated rows. */
|
|
64
|
+
toValueLike(time, like) {
|
|
65
|
+
if (like instanceof Date)
|
|
66
|
+
return new Date(time);
|
|
67
|
+
if (typeof like !== 'string')
|
|
68
|
+
return time;
|
|
69
|
+
const text = like.trim();
|
|
70
|
+
if (this.dateFormat && TimelineTime.#parse(text, this.dateFormat) !== undefined) {
|
|
71
|
+
return TimelineTime.#write(new Date(time), this.dateFormat);
|
|
72
|
+
}
|
|
73
|
+
if (ISO_DATE.test(text) && !/^\d{4}$/.test(text)) {
|
|
74
|
+
return new Date(time).toISOString().slice(0, text.length);
|
|
75
|
+
}
|
|
76
|
+
return String(time);
|
|
77
|
+
}
|
|
78
|
+
/** A date label as precise as the steps need: year, month, day or time. */
|
|
79
|
+
static dateLabel(steps, locale) {
|
|
80
|
+
const dates = steps.map((time) => new Date(time));
|
|
81
|
+
const options = dates.every((d) => d.getUTCMonth() === 0 && d.getUTCDate() === 1 && d.getTime() % DAY === 0)
|
|
82
|
+
? { year: 'numeric' }
|
|
83
|
+
: dates.every((d) => d.getUTCDate() === 1 && d.getTime() % DAY === 0)
|
|
84
|
+
? { month: 'short', year: 'numeric' }
|
|
85
|
+
: dates.every((d) => d.getTime() % DAY === 0)
|
|
86
|
+
? { day: 'numeric', month: 'short', year: 'numeric' }
|
|
87
|
+
: { dateStyle: 'medium', timeStyle: 'short' };
|
|
88
|
+
const format = new Intl.DateTimeFormat(locale, { ...options, timeZone: 'UTC' });
|
|
89
|
+
return (time) => format.format(new Date(time));
|
|
90
|
+
}
|
|
91
|
+
/** `text` read through `format`, or `undefined` when it does not match. */
|
|
92
|
+
static #parse(text, format) {
|
|
93
|
+
const order = [];
|
|
94
|
+
const pattern = format
|
|
95
|
+
.split(TOKEN_PATTERN)
|
|
96
|
+
.map((literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
97
|
+
const tokens = format.match(TOKEN_PATTERN) ?? [];
|
|
98
|
+
let source = pattern[0];
|
|
99
|
+
tokens.forEach((token, index) => {
|
|
100
|
+
order.push(token);
|
|
101
|
+
source += TOKENS[token].group + pattern[index + 1];
|
|
102
|
+
});
|
|
103
|
+
const match = new RegExp(`^${source}$`).exec(text);
|
|
104
|
+
if (!match || !order.length)
|
|
105
|
+
return undefined;
|
|
106
|
+
const parts = { YYYY: 1970, MM: 1, DD: 1, HH: 0, mm: 0, ss: 0 };
|
|
107
|
+
order.forEach((token, index) => (parts[token] = Number(match[index + 1])));
|
|
108
|
+
return Date.UTC(parts.YYYY, parts.MM - 1, parts.DD, parts.HH, parts.mm, parts.ss);
|
|
109
|
+
}
|
|
110
|
+
/** `date` written through `format`. */
|
|
111
|
+
static #write(date, format) {
|
|
112
|
+
return format.replace(TOKEN_PATTERN, (token) => {
|
|
113
|
+
const { get, width } = TOKENS[token];
|
|
114
|
+
return String(get(date)).padStart(width, '0');
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* A plot's floating tooltip at `(x, y)`, inside a `position: relative`
|
|
8
8
|
* parent and themed by the `--dct-tooltip-*` variables. It reads the rows
|
|
9
|
-
* the hover matched: its own `content` snippet, given every row
|
|
10
|
-
* row's `format` text, one per line. It sits at `anchorX`/`anchorY` of the
|
|
9
|
+
* the hover matched: its own `content` snippet, given every row as matched,
|
|
10
|
+
* or each row's `format` text, one per line, in `sort`'s order. It sits at `anchorX`/`anchorY` of the
|
|
11
11
|
* point (default right and below) and moves over where it would leave its
|
|
12
12
|
* plot's box - or `bounds`, when given. See {@link TooltipState}.
|
|
13
13
|
*/
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
y,
|
|
18
18
|
rows,
|
|
19
19
|
format,
|
|
20
|
+
sort,
|
|
20
21
|
content,
|
|
21
22
|
bounds,
|
|
22
23
|
anchorX = 'right',
|
|
@@ -30,6 +31,8 @@
|
|
|
30
31
|
/** Every row the hover matched. */
|
|
31
32
|
rows: TRow[];
|
|
32
33
|
format: (row: TRow) => string;
|
|
34
|
+
/** The order the `format` text reads the rows in. Unset, as matched. */
|
|
35
|
+
sort?: (a: TRow, b: TRow) => number;
|
|
33
36
|
/** Custom tooltip body, given every row; replaces the `format` text and the default box. */
|
|
34
37
|
content?: Snippet<[{ rows: TRow[] }]>;
|
|
35
38
|
/** Box the tooltip must stay within. Defaults to its plot's box. */
|
|
@@ -63,7 +66,7 @@
|
|
|
63
66
|
{#if content}
|
|
64
67
|
{@render content({ rows })}
|
|
65
68
|
{:else}
|
|
66
|
-
{TooltipState.text(rows, format)}
|
|
69
|
+
{TooltipState.text(rows, format, sort)}
|
|
67
70
|
{/if}
|
|
68
71
|
</div>
|
|
69
72
|
{/if}
|
|
@@ -8,6 +8,8 @@ declare function $$render<TRow extends Record<string, unknown>>(): {
|
|
|
8
8
|
/** Every row the hover matched. */
|
|
9
9
|
rows: TRow[];
|
|
10
10
|
format: (row: TRow) => string;
|
|
11
|
+
/** The order the `format` text reads the rows in. Unset, as matched. */
|
|
12
|
+
sort?: (a: TRow, b: TRow) => number;
|
|
11
13
|
/** Custom tooltip body, given every row; replaces the `format` text and the default box. */
|
|
12
14
|
content?: Snippet<[{
|
|
13
15
|
rows: TRow[];
|
|
@@ -35,8 +35,14 @@ export declare class TooltipState {
|
|
|
35
35
|
constructor(inputs: TooltipInputs);
|
|
36
36
|
/** Where the tooltip sits. */
|
|
37
37
|
readonly box: Box;
|
|
38
|
-
/** The text `rows` read as through `format`, one line each. */
|
|
39
|
-
static text<TRow>(rows: TRow[], format: (row: TRow) => string): string;
|
|
38
|
+
/** The text `rows` read as through `format`, one line each, in `sort`'s order when given. */
|
|
39
|
+
static text<TRow>(rows: TRow[], format: (row: TRow) => string, sort?: (a: TRow, b: TRow) => number): string;
|
|
40
|
+
/**
|
|
41
|
+
* Orders rows from the highest value `valueOf` reads to the lowest. A row
|
|
42
|
+
* without a numeric value goes after every one with, and rows that tie keep
|
|
43
|
+
* the order the hover matched them in.
|
|
44
|
+
*/
|
|
45
|
+
static descending<TRow>(valueOf: (row: TRow) => unknown): (a: TRow, b: TRow) => number;
|
|
40
46
|
/** A value as plain text: a date as a locale date, anything else as itself. */
|
|
41
47
|
static value(value: unknown): string;
|
|
42
48
|
/**
|
|
@@ -39,9 +39,28 @@ export class TooltipState {
|
|
|
39
39
|
return (this.#plotMargins?.place(size, point, anchor) ??
|
|
40
40
|
size.placeNear(point, anchor, Box.size(Infinity, Infinity)));
|
|
41
41
|
});
|
|
42
|
-
/** The text `rows` read as through `format`, one line each. */
|
|
43
|
-
static text(rows, format) {
|
|
44
|
-
return rows.map(format).join('\n');
|
|
42
|
+
/** The text `rows` read as through `format`, one line each, in `sort`'s order when given. */
|
|
43
|
+
static text(rows, format, sort) {
|
|
44
|
+
return (sort ? [...rows].sort(sort) : rows).map(format).join('\n');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Orders rows from the highest value `valueOf` reads to the lowest. A row
|
|
48
|
+
* without a numeric value goes after every one with, and rows that tie keep
|
|
49
|
+
* the order the hover matched them in.
|
|
50
|
+
*/
|
|
51
|
+
static descending(valueOf) {
|
|
52
|
+
const numberOf = (row) => {
|
|
53
|
+
const value = valueOf(row);
|
|
54
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
55
|
+
};
|
|
56
|
+
return (a, b) => {
|
|
57
|
+
const left = numberOf(a);
|
|
58
|
+
const right = numberOf(b);
|
|
59
|
+
if (left === undefined || right === undefined) {
|
|
60
|
+
return (left === undefined ? 1 : 0) - (right === undefined ? 1 : 0);
|
|
61
|
+
}
|
|
62
|
+
return right - left;
|
|
63
|
+
};
|
|
45
64
|
}
|
|
46
65
|
/** A value as plain text: a date as a locale date, anything else as itself. */
|
|
47
66
|
static value(value) {
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
import DotMarker from '../dot/DotMarker.svelte';
|
|
4
4
|
import TextMarker from '../text/TextMarker.svelte';
|
|
5
5
|
import { Delta } from './Delta';
|
|
6
|
+
import { getConfiguration } from '../../configuration/config.svelte';
|
|
6
7
|
import type { Series } from '../../types/plots/data/common';
|
|
7
8
|
import type { DeltaMarkerConfig } from '../../types/markers/axis/delta';
|
|
8
9
|
|
|
9
10
|
let { marker, data = [] }: { marker: DeltaMarkerConfig; data?: Series<TData>[] } = $props();
|
|
10
11
|
|
|
11
12
|
const resolved = $derived(Delta.build(marker.axis, data, marker));
|
|
13
|
+
const label = $derived(getConfiguration().delta.text[marker.axis]);
|
|
14
|
+
const lineAnchor = $derived(marker.text?.lineAnchor ?? label.lineAnchor);
|
|
12
15
|
|
|
13
16
|
const clampRange = $derived.by((): [number, number] | undefined => {
|
|
14
17
|
const domain = marker.axis === 'x' ? marker.yDomain : marker.xDomain;
|
|
@@ -27,10 +30,9 @@
|
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
function textVaryCoord(fromVis: number, toVis: number) {
|
|
30
|
-
|
|
31
|
-
return anchor === 'top'
|
|
33
|
+
return lineAnchor === 'top'
|
|
32
34
|
? Math.max(fromVis, toVis)
|
|
33
|
-
:
|
|
35
|
+
: lineAnchor === 'bottom'
|
|
34
36
|
? Math.min(fromVis, toVis)
|
|
35
37
|
: (fromVis + toVis) / 2;
|
|
36
38
|
}
|
|
@@ -82,12 +84,12 @@
|
|
|
82
84
|
styles: {
|
|
83
85
|
fontWeight: 'bold',
|
|
84
86
|
fontSize: 14,
|
|
85
|
-
textAnchor:
|
|
87
|
+
textAnchor: label.textAnchor,
|
|
86
88
|
fill: marker.line?.stroke ?? 'currentColor',
|
|
87
89
|
...marker.text,
|
|
88
|
-
dx: marker.text?.dx ??
|
|
89
|
-
dy: marker.text?.dy ??
|
|
90
|
-
lineAnchor
|
|
90
|
+
dx: marker.text?.dx ?? label.dx,
|
|
91
|
+
dy: marker.text?.dy ?? label.dy,
|
|
92
|
+
lineAnchor
|
|
91
93
|
}
|
|
92
94
|
}}
|
|
93
95
|
/>
|
package/dist/phases/Pipeline.js
CHANGED
|
@@ -9,6 +9,7 @@ import { StylesPhase } from './styles/StylesPhase';
|
|
|
9
9
|
import { MarkersPhase } from './markers/MarkersPhase';
|
|
10
10
|
import { TooltipPhase } from './tooltip/TooltipPhase';
|
|
11
11
|
import { HoverPhase } from './hover/HoverPhase';
|
|
12
|
+
import { HoverSettings } from '../layout/hover/HoverSettings';
|
|
12
13
|
import { PlotInteraction } from '../layout/interaction/PlotInteraction.svelte';
|
|
13
14
|
import { DEFAULT_CONFIG } from '../configuration/config.svelte';
|
|
14
15
|
/**
|
|
@@ -77,7 +78,7 @@ export class Pipeline {
|
|
|
77
78
|
new FacetPhase(),
|
|
78
79
|
new DataPhase(kind.data),
|
|
79
80
|
new MappingPhase(kind.mapping),
|
|
80
|
-
new TransformsPhase(kind.transforms),
|
|
81
|
+
new TransformsPhase(kind.transforms, kind.data?.segments?.connect),
|
|
81
82
|
new StatisticsPhase(kind.statistics),
|
|
82
83
|
new SharedScalesPhase(kind.sharedScales, scales),
|
|
83
84
|
scales,
|
|
@@ -143,7 +144,7 @@ export class Pipeline {
|
|
|
143
144
|
return fixed;
|
|
144
145
|
const upToData = this.stages.filter((s) => s.phase === 'data');
|
|
145
146
|
const state = Pipeline.#through(upToData, this.#seed(props), Pipeline.#ctxFor(cfg, null));
|
|
146
|
-
return
|
|
147
|
+
return HoverSettings.strategyOf(state.axes);
|
|
147
148
|
}
|
|
148
149
|
/** The plot's default `timeline.filterMode`. */
|
|
149
150
|
get filterMode() {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { Accessor } from '../../types/plots/data/common';
|
|
2
1
|
import type { AxisPlotState, AxisPlotCtx, DataStageOptions, PhaseStage } from '../../types/phases';
|
|
3
2
|
type AnyAxisState = AxisPlotState<any, any, any, any>;
|
|
4
3
|
/**
|
|
5
4
|
* The "data" phase every axis-based plot shares: its series (from `series`,
|
|
6
|
-
* or flat rows split by
|
|
7
|
-
* channel is and which way the plot runs
|
|
8
|
-
* color, the series a legend omits dropped,
|
|
9
|
-
* visual segments, and - for a discrete
|
|
5
|
+
* or flat rows split by `seriesGrouping`: its `grouping`, or a categorical
|
|
6
|
+
* `color`), what each position channel is and which way the plot runs
|
|
7
|
+
* ({@link PlotAxes}), each series' color, the series a legend omits dropped,
|
|
8
|
+
* each series cut into its styled visual segments, and - for a discrete
|
|
9
|
+
* mark - the values its gaps fill in.
|
|
10
10
|
* Colors and axes come from every series, before any is omitted or split by
|
|
11
11
|
* facet panel, so they stay put and every panel agrees.
|
|
12
12
|
*/
|
|
@@ -15,11 +15,5 @@ export declare class DataPhase<TState extends AnyAxisState, TCtx extends AxisPlo
|
|
|
15
15
|
readonly phase: "data";
|
|
16
16
|
constructor(options?: DataStageOptions<TState, TCtx>);
|
|
17
17
|
run: (state: TState, ctx: TCtx) => TState;
|
|
18
|
-
/**
|
|
19
|
-
* The key a plot's rows split into series by: its `grouping`, or its `color`
|
|
20
|
-
* when that names categories. A numeric `color` is a continuous value (a
|
|
21
|
-
* heatmap's cells), so it colors rows without grouping them.
|
|
22
|
-
*/
|
|
23
|
-
static buildGrouping(state: Pick<AnyAxisState, 'grouping' | 'color' | 'data'>): Accessor<any, unknown> | undefined;
|
|
24
18
|
}
|
|
25
19
|
export {};
|