inkplot 0.0.0-stage → 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +46 -2
- package/dist/adapt.d.ts +10 -0
- package/dist/adapt.js +34 -0
- package/dist/annotation.d.ts +120 -0
- package/dist/annotation.js +267 -0
- package/dist/aria.d.ts +24 -0
- package/dist/aria.js +31 -0
- package/dist/attrs.d.ts +81 -0
- package/dist/attrs.js +84 -0
- package/dist/axis.d.ts +137 -0
- package/dist/axis.js +185 -0
- package/dist/bars.d.ts +80 -0
- package/dist/bars.js +255 -0
- package/dist/boxplot.d.ts +57 -0
- package/dist/boxplot.js +177 -0
- package/dist/boxzoom.d.ts +39 -0
- package/dist/boxzoom.js +138 -0
- package/dist/brush.d.ts +60 -0
- package/dist/brush.js +185 -0
- package/dist/bucket.d.ts +22 -0
- package/dist/bucket.js +62 -0
- package/dist/callout.d.ts +30 -0
- package/dist/callout.js +98 -0
- package/dist/chart.d.ts +126 -0
- package/dist/chart.js +257 -0
- package/dist/crosshair.d.ts +28 -0
- package/dist/crosshair.js +78 -0
- package/dist/cursor.d.ts +76 -0
- package/dist/cursor.js +344 -0
- package/dist/curve.d.ts +50 -0
- package/dist/curve.js +87 -0
- package/dist/curves.d.ts +104 -0
- package/dist/curves.js +375 -0
- package/dist/data.d.ts +209 -0
- package/dist/data.js +276 -0
- package/dist/decimation.d.ts +86 -0
- package/dist/decimation.js +410 -0
- package/dist/density.d.ts +55 -0
- package/dist/density.js +252 -0
- package/dist/dimension.d.ts +103 -0
- package/dist/dimension.js +205 -0
- package/dist/dom-platform.d.ts +53 -0
- package/dist/dom-platform.js +190 -0
- package/dist/element.d.ts +232 -0
- package/dist/element.js +414 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.js +237 -0
- package/dist/events.d.ts +144 -0
- package/dist/events.js +19 -0
- package/dist/focus.d.ts +22 -0
- package/dist/focus.js +51 -0
- package/dist/furniture.d.ts +70 -0
- package/dist/furniture.js +166 -0
- package/dist/geometry.d.ts +40 -0
- package/dist/geometry.js +7 -0
- package/dist/heatmap.d.ts +57 -0
- package/dist/heatmap.js +207 -0
- package/dist/hit.d.ts +103 -0
- package/dist/hit.js +428 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +73 -0
- package/dist/input.d.ts +42 -0
- package/dist/input.js +166 -0
- package/dist/jsx-dev-runtime.d.ts +9 -0
- package/dist/jsx-dev-runtime.js +13 -0
- package/dist/jsx-runtime.d.ts +59 -0
- package/dist/jsx-runtime.js +81 -0
- package/dist/key.d.ts +118 -0
- package/dist/key.js +199 -0
- package/dist/legend.d.ts +36 -0
- package/dist/legend.js +220 -0
- package/dist/mapped.d.ts +80 -0
- package/dist/mapped.js +106 -0
- package/dist/marker.d.ts +143 -0
- package/dist/marker.js +213 -0
- package/dist/panzoom.d.ts +119 -0
- package/dist/panzoom.js +467 -0
- package/dist/plane.d.ts +173 -0
- package/dist/plane.js +370 -0
- package/dist/platform.d.ts +113 -0
- package/dist/platform.js +80 -0
- package/dist/png.d.ts +44 -0
- package/dist/png.js +71 -0
- package/dist/pointer.d.ts +54 -0
- package/dist/pointer.js +132 -0
- package/dist/quantile.d.ts +3 -0
- package/dist/quantile.js +11 -0
- package/dist/radial.d.ts +116 -0
- package/dist/radial.js +336 -0
- package/dist/react.d.ts +441 -0
- package/dist/react.js +252 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +51 -0
- package/dist/renderer.d.ts +191 -0
- package/dist/renderer.js +164 -0
- package/dist/scale.d.ts +37 -0
- package/dist/scale.js +19 -0
- package/dist/scales.d.ts +69 -0
- package/dist/scales.js +134 -0
- package/dist/serialize.d.ts +77 -0
- package/dist/serialize.js +321 -0
- package/dist/series.d.ts +304 -0
- package/dist/series.js +513 -0
- package/dist/simple.d.ts +35 -0
- package/dist/simple.js +20 -0
- package/dist/sources.d.ts +116 -0
- package/dist/sources.js +237 -0
- package/dist/stack.d.ts +61 -0
- package/dist/stack.js +172 -0
- package/dist/stats.d.ts +38 -0
- package/dist/stats.js +159 -0
- package/dist/steps.d.ts +50 -0
- package/dist/steps.js +154 -0
- package/dist/stub.d.ts +3 -0
- package/dist/stub.js +5 -0
- package/dist/style-names.d.ts +1 -0
- package/dist/style-names.js +6 -0
- package/dist/summary.d.ts +44 -0
- package/dist/summary.js +58 -0
- package/dist/surface.d.ts +94 -0
- package/dist/surface.js +145 -0
- package/dist/table.d.ts +39 -0
- package/dist/table.js +37 -0
- package/dist/text.d.ts +23 -0
- package/dist/text.js +44 -0
- package/dist/theme.d.ts +174 -0
- package/dist/theme.js +73 -0
- package/dist/themes.d.ts +9 -0
- package/dist/themes.js +56 -0
- package/dist/time-intl.d.ts +194 -0
- package/dist/time-intl.js +144 -0
- package/dist/time-temporal.d.ts +236 -0
- package/dist/time-temporal.js +58 -0
- package/dist/time.d.ts +216 -0
- package/dist/time.js +154 -0
- package/dist/trend.d.ts +151 -0
- package/dist/trend.js +500 -0
- package/dist/web-components.d.ts +62 -0
- package/dist/web-components.js +264 -0
- package/package.json +53 -3
package/dist/time.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { Extent } from './geometry.ts';
|
|
2
|
+
import { type ScaleAttributes } from './scale.ts';
|
|
3
|
+
import { LinearScale } from './scales.ts';
|
|
4
|
+
/** The units a calendar walks in. */
|
|
5
|
+
export type TimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
|
|
6
|
+
/** What a label is for: the largest unit a tick begins, else the precision of the ticks around it. `full` is the reading of a single instant (a tooltip). */
|
|
7
|
+
export type TimeRank = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'millisecond' | 'full';
|
|
8
|
+
/** `formats` for the time scales (Intl options per rank; a format of another kind lists its own parts). */
|
|
9
|
+
export declare const TIME_FORMATS_TYPES: {
|
|
10
|
+
year: {
|
|
11
|
+
localeMatcher: 1;
|
|
12
|
+
timeZone: 1;
|
|
13
|
+
hourCycle: 1;
|
|
14
|
+
formatMatcher: 1;
|
|
15
|
+
dateStyle: 1;
|
|
16
|
+
timeStyle: 1;
|
|
17
|
+
dayPeriod: 1;
|
|
18
|
+
fractionalSecondDigits: 0;
|
|
19
|
+
calendar: 1;
|
|
20
|
+
numberingSystem: 1;
|
|
21
|
+
weekday: 1;
|
|
22
|
+
era: 1;
|
|
23
|
+
year: 1;
|
|
24
|
+
month: 1;
|
|
25
|
+
day: 1;
|
|
26
|
+
hour: 1;
|
|
27
|
+
minute: 1;
|
|
28
|
+
second: 1;
|
|
29
|
+
timeZoneName: 1;
|
|
30
|
+
};
|
|
31
|
+
month: {
|
|
32
|
+
localeMatcher: 1;
|
|
33
|
+
timeZone: 1;
|
|
34
|
+
hourCycle: 1;
|
|
35
|
+
formatMatcher: 1;
|
|
36
|
+
dateStyle: 1;
|
|
37
|
+
timeStyle: 1;
|
|
38
|
+
dayPeriod: 1;
|
|
39
|
+
fractionalSecondDigits: 0;
|
|
40
|
+
calendar: 1;
|
|
41
|
+
numberingSystem: 1;
|
|
42
|
+
weekday: 1;
|
|
43
|
+
era: 1;
|
|
44
|
+
year: 1;
|
|
45
|
+
month: 1;
|
|
46
|
+
day: 1;
|
|
47
|
+
hour: 1;
|
|
48
|
+
minute: 1;
|
|
49
|
+
second: 1;
|
|
50
|
+
timeZoneName: 1;
|
|
51
|
+
};
|
|
52
|
+
day: {
|
|
53
|
+
localeMatcher: 1;
|
|
54
|
+
timeZone: 1;
|
|
55
|
+
hourCycle: 1;
|
|
56
|
+
formatMatcher: 1;
|
|
57
|
+
dateStyle: 1;
|
|
58
|
+
timeStyle: 1;
|
|
59
|
+
dayPeriod: 1;
|
|
60
|
+
fractionalSecondDigits: 0;
|
|
61
|
+
calendar: 1;
|
|
62
|
+
numberingSystem: 1;
|
|
63
|
+
weekday: 1;
|
|
64
|
+
era: 1;
|
|
65
|
+
year: 1;
|
|
66
|
+
month: 1;
|
|
67
|
+
day: 1;
|
|
68
|
+
hour: 1;
|
|
69
|
+
minute: 1;
|
|
70
|
+
second: 1;
|
|
71
|
+
timeZoneName: 1;
|
|
72
|
+
};
|
|
73
|
+
hour: {
|
|
74
|
+
localeMatcher: 1;
|
|
75
|
+
timeZone: 1;
|
|
76
|
+
hourCycle: 1;
|
|
77
|
+
formatMatcher: 1;
|
|
78
|
+
dateStyle: 1;
|
|
79
|
+
timeStyle: 1;
|
|
80
|
+
dayPeriod: 1;
|
|
81
|
+
fractionalSecondDigits: 0;
|
|
82
|
+
calendar: 1;
|
|
83
|
+
numberingSystem: 1;
|
|
84
|
+
weekday: 1;
|
|
85
|
+
era: 1;
|
|
86
|
+
year: 1;
|
|
87
|
+
month: 1;
|
|
88
|
+
day: 1;
|
|
89
|
+
hour: 1;
|
|
90
|
+
minute: 1;
|
|
91
|
+
second: 1;
|
|
92
|
+
timeZoneName: 1;
|
|
93
|
+
};
|
|
94
|
+
minute: {
|
|
95
|
+
localeMatcher: 1;
|
|
96
|
+
timeZone: 1;
|
|
97
|
+
hourCycle: 1;
|
|
98
|
+
formatMatcher: 1;
|
|
99
|
+
dateStyle: 1;
|
|
100
|
+
timeStyle: 1;
|
|
101
|
+
dayPeriod: 1;
|
|
102
|
+
fractionalSecondDigits: 0;
|
|
103
|
+
calendar: 1;
|
|
104
|
+
numberingSystem: 1;
|
|
105
|
+
weekday: 1;
|
|
106
|
+
era: 1;
|
|
107
|
+
year: 1;
|
|
108
|
+
month: 1;
|
|
109
|
+
day: 1;
|
|
110
|
+
hour: 1;
|
|
111
|
+
minute: 1;
|
|
112
|
+
second: 1;
|
|
113
|
+
timeZoneName: 1;
|
|
114
|
+
};
|
|
115
|
+
second: {
|
|
116
|
+
localeMatcher: 1;
|
|
117
|
+
timeZone: 1;
|
|
118
|
+
hourCycle: 1;
|
|
119
|
+
formatMatcher: 1;
|
|
120
|
+
dateStyle: 1;
|
|
121
|
+
timeStyle: 1;
|
|
122
|
+
dayPeriod: 1;
|
|
123
|
+
fractionalSecondDigits: 0;
|
|
124
|
+
calendar: 1;
|
|
125
|
+
numberingSystem: 1;
|
|
126
|
+
weekday: 1;
|
|
127
|
+
era: 1;
|
|
128
|
+
year: 1;
|
|
129
|
+
month: 1;
|
|
130
|
+
day: 1;
|
|
131
|
+
hour: 1;
|
|
132
|
+
minute: 1;
|
|
133
|
+
second: 1;
|
|
134
|
+
timeZoneName: 1;
|
|
135
|
+
};
|
|
136
|
+
millisecond: {
|
|
137
|
+
localeMatcher: 1;
|
|
138
|
+
timeZone: 1;
|
|
139
|
+
hourCycle: 1;
|
|
140
|
+
formatMatcher: 1;
|
|
141
|
+
dateStyle: 1;
|
|
142
|
+
timeStyle: 1;
|
|
143
|
+
dayPeriod: 1;
|
|
144
|
+
fractionalSecondDigits: 0;
|
|
145
|
+
calendar: 1;
|
|
146
|
+
numberingSystem: 1;
|
|
147
|
+
weekday: 1;
|
|
148
|
+
era: 1;
|
|
149
|
+
year: 1;
|
|
150
|
+
month: 1;
|
|
151
|
+
day: 1;
|
|
152
|
+
hour: 1;
|
|
153
|
+
minute: 1;
|
|
154
|
+
second: 1;
|
|
155
|
+
timeZoneName: 1;
|
|
156
|
+
};
|
|
157
|
+
full: {
|
|
158
|
+
localeMatcher: 1;
|
|
159
|
+
timeZone: 1;
|
|
160
|
+
hourCycle: 1;
|
|
161
|
+
formatMatcher: 1;
|
|
162
|
+
dateStyle: 1;
|
|
163
|
+
timeStyle: 1;
|
|
164
|
+
dayPeriod: 1;
|
|
165
|
+
fractionalSecondDigits: 0;
|
|
166
|
+
calendar: 1;
|
|
167
|
+
numberingSystem: 1;
|
|
168
|
+
weekday: 1;
|
|
169
|
+
era: 1;
|
|
170
|
+
year: 1;
|
|
171
|
+
month: 1;
|
|
172
|
+
day: 1;
|
|
173
|
+
hour: 1;
|
|
174
|
+
minute: 1;
|
|
175
|
+
second: 1;
|
|
176
|
+
timeZoneName: 1;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
export interface TimeScaleAttributes<Format = unknown> extends ScaleAttributes {
|
|
180
|
+
/** An IANA time zone name, 'UTC' or 'local' (the runtime's). Default 'UTC'. */
|
|
181
|
+
timeZone?: string;
|
|
182
|
+
/** The first day of a week, 0 for Sunday to 6. Default 1, Monday. */
|
|
183
|
+
weekStart?: number;
|
|
184
|
+
/** Replaces the default format of a rank. */
|
|
185
|
+
formats?: Partial<Record<TimeRank, Format>>;
|
|
186
|
+
}
|
|
187
|
+
export declare abstract class TimeScale<Format = unknown, A extends TimeScaleAttributes<Format> = TimeScaleAttributes<Format>> extends LinearScale<A> {
|
|
188
|
+
#private;
|
|
189
|
+
static attrTypes: {
|
|
190
|
+
id: 1;
|
|
191
|
+
meta: {
|
|
192
|
+
title: 1;
|
|
193
|
+
description: 1;
|
|
194
|
+
role: 1;
|
|
195
|
+
};
|
|
196
|
+
timeZone: 1;
|
|
197
|
+
weekStart: 2;
|
|
198
|
+
};
|
|
199
|
+
/** The start of the `unit` that holds `t`, in the time zone. */
|
|
200
|
+
protected abstract startOf(t: number, unit: TimeUnit): number;
|
|
201
|
+
/** `n` (whole, maybe negative) units after `t`, by the calendar. */
|
|
202
|
+
protected abstract add(t: number, unit: TimeUnit, n: number): number;
|
|
203
|
+
/** Which one `t` is of its unit within the next unit up: the year; the month 0 to 11; the day of the month from 1; the hour, minute and second from 0. */
|
|
204
|
+
protected abstract field(t: number, unit: Exclude<TimeUnit, 'week'>): number;
|
|
205
|
+
/** The text for `t` as a tick or reading of this rank. */
|
|
206
|
+
protected abstract format(t: number, rank: TimeRank, spec: Format): string;
|
|
207
|
+
/** The format for each rank when `formats` does not say. */
|
|
208
|
+
protected abstract get defaultFormats(): Record<TimeRank, Format>;
|
|
209
|
+
/** The day weeks start on, 0 to 6 from Sunday. */
|
|
210
|
+
protected get weekStart(): number;
|
|
211
|
+
ticks(r: Extent, count: number): readonly number[];
|
|
212
|
+
nice(r: Extent): Extent;
|
|
213
|
+
/** The reading of one instant: the `full` format. */
|
|
214
|
+
label(value: number): string | undefined;
|
|
215
|
+
labels(values: readonly number[]): readonly string[];
|
|
216
|
+
}
|
package/dist/time.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time: an axis scale whose ticks fall on the calendar (midnights, the hour, the first of the month) instead of on powers of ten.
|
|
3
|
+
*
|
|
4
|
+
* The data is JavaScript timestamps: milliseconds since the epoch, as `Date.now()`, `Date.parse(iso)` and
|
|
5
|
+
* `Temporal.Instant#epochMilliseconds` give. (A Unix timestamp in seconds needs `* 1000`.) Nothing else about time
|
|
6
|
+
* reaches the library: it is numbers all the way down, so panning, zooming, stacking, decimating and hit-testing work as they do for any
|
|
7
|
+
* linear axis, and what is added here is where the ticks go and what they say.
|
|
8
|
+
*
|
|
9
|
+
* `TimeScale` is the part that does not depend on a date library: the choice of interval, the walk along the calendar, `nice`, and the
|
|
10
|
+
* rule for labels. What does depend on one is four small methods, which a subclass gives:
|
|
11
|
+
* - `startOf(t, unit)`: the start of the unit that holds `t`, in the scale's time zone.
|
|
12
|
+
* - `add(t, unit, n)`: `n` units later, by the calendar (a day is 23 or 25 hours when the clocks change).
|
|
13
|
+
* - `field(t, unit)`: which one the unit is within the next one up (the hour of the day, the month of the year).
|
|
14
|
+
* - `format(t, rank, spec)`: the text for a tick of this rank.
|
|
15
|
+
* `IntlTimeScale` (time-intl.ts) does it with `Date` and `Intl`, and `TemporalTimeScale` (time-temporal.ts) with
|
|
16
|
+
* `Temporal`. A scale over date-fns, Luxon or moment is one more subclass.
|
|
17
|
+
*
|
|
18
|
+
* The time zone is the `timeZone` attribute: an IANA name, 'UTC' (the default, so that a chart renders the same wherever it
|
|
19
|
+
* is rendered) or 'local' for the one the runtime is in.
|
|
20
|
+
*
|
|
21
|
+
* Labels follow the tick, not the axis: a tick that begins a day says the date ("Jun 11"), however finely the axis is divided, and
|
|
22
|
+
* one that does not says the time at the precision of the whole set ("22:10"). `formats` replaces any of the default
|
|
23
|
+
* formats, whose type is the adapter's own (`Intl.DateTimeFormatOptions` for the two built in, a format string for moment).
|
|
24
|
+
*/
|
|
25
|
+
import { AUTO, STRING, NUMBER } from './attrs.js';
|
|
26
|
+
import { niceStep } from './scale.js';
|
|
27
|
+
import { LinearScale } from './scales.js';
|
|
28
|
+
/** The text-only and capitalised parts of `Intl.DateTimeFormatOptions`, for the `formats` attribute of both Intl-based time scales. */
|
|
29
|
+
const INTL_FORMAT_TYPES = {
|
|
30
|
+
localeMatcher: STRING, timeZone: STRING, hourCycle: STRING, formatMatcher: STRING, dateStyle: STRING, timeStyle: STRING,
|
|
31
|
+
dayPeriod: STRING, fractionalSecondDigits: AUTO, calendar: STRING, numberingSystem: STRING, weekday: STRING, era: STRING,
|
|
32
|
+
year: STRING, month: STRING, day: STRING, hour: STRING, minute: STRING, second: STRING, timeZoneName: STRING,
|
|
33
|
+
};
|
|
34
|
+
/** `formats` for the time scales (Intl options per rank; a format of another kind lists its own parts). */
|
|
35
|
+
export const TIME_FORMATS_TYPES = {
|
|
36
|
+
year: INTL_FORMAT_TYPES, month: INTL_FORMAT_TYPES, day: INTL_FORMAT_TYPES, hour: INTL_FORMAT_TYPES,
|
|
37
|
+
minute: INTL_FORMAT_TYPES, second: INTL_FORMAT_TYPES, millisecond: INTL_FORMAT_TYPES, full: INTL_FORMAT_TYPES,
|
|
38
|
+
};
|
|
39
|
+
const SECOND = 1000, MINUTE = 60 * SECOND, HOUR = 60 * MINUTE, DAY = 24 * HOUR, YEAR = 365.2425 * DAY;
|
|
40
|
+
/** The intervals a tick set is chosen from, with the length each is taken to have when comparing. Years beyond the first are the nice numbers of years. */
|
|
41
|
+
const LADDER = [
|
|
42
|
+
['second', 1, SECOND], ['second', 5, 5 * SECOND], ['second', 15, 15 * SECOND], ['second', 30, 30 * SECOND],
|
|
43
|
+
['minute', 1, MINUTE], ['minute', 5, 5 * MINUTE], ['minute', 15, 15 * MINUTE], ['minute', 30, 30 * MINUTE],
|
|
44
|
+
['hour', 1, HOUR], ['hour', 3, 3 * HOUR], ['hour', 6, 6 * HOUR], ['hour', 12, 12 * HOUR],
|
|
45
|
+
['day', 1, DAY], ['day', 2, 2 * DAY], ['week', 1, 7 * DAY],
|
|
46
|
+
['month', 1, YEAR / 12], ['month', 3, YEAR / 4], ['month', 6, YEAR / 2], ['year', 1, YEAR],
|
|
47
|
+
];
|
|
48
|
+
/** The interval for ticks about `target` milliseconds apart. */
|
|
49
|
+
function intervalFor(target) {
|
|
50
|
+
if (!(target > 0) || !isFinite(target))
|
|
51
|
+
return { unit: null, step: 1, ms: 1 };
|
|
52
|
+
if (target < 0.75 * SECOND) {
|
|
53
|
+
const step = niceStep(target);
|
|
54
|
+
return { unit: null, step, ms: step };
|
|
55
|
+
}
|
|
56
|
+
if (target > 1.4 * YEAR) {
|
|
57
|
+
const step = Math.max(1, niceStep(target / YEAR));
|
|
58
|
+
return { unit: 'year', step, ms: step * YEAR };
|
|
59
|
+
}
|
|
60
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- in range: the index comes from a loop bound or a length checked in the same function
|
|
61
|
+
let best = LADDER[0], off = Infinity;
|
|
62
|
+
for (const e of LADDER) {
|
|
63
|
+
const d = Math.abs(Math.log(e[2] / target));
|
|
64
|
+
if (d < off) {
|
|
65
|
+
off = d;
|
|
66
|
+
best = e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { unit: best[0], step: best[1], ms: best[2] };
|
|
70
|
+
}
|
|
71
|
+
/** The most ticks made for a range, a guard against a huge range with a tiny interval. */
|
|
72
|
+
const MOST = 2000;
|
|
73
|
+
export class TimeScale extends LinearScale {
|
|
74
|
+
static attrTypes = { ...LinearScale.attrTypes, timeZone: STRING, weekStart: NUMBER };
|
|
75
|
+
/** The day weeks start on, 0 to 6 from Sunday. */
|
|
76
|
+
get weekStart() { return this.attrs.weekStart ?? 1; }
|
|
77
|
+
#spec(rank) { return this.attrs.formats?.[rank] ?? this.defaultFormats[rank]; }
|
|
78
|
+
/** The ticks of an interval inside [lo, hi], in order. */
|
|
79
|
+
#walk(lo, hi, iv) {
|
|
80
|
+
const out = [];
|
|
81
|
+
if (iv.unit === null) {
|
|
82
|
+
for (let k = Math.ceil(lo / iv.step - 1e-9), v = k * iv.step; v <= hi + 1e-9 && out.length < MOST; v = ++k * iv.step)
|
|
83
|
+
out.push(v);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
const unit = iv.unit, step = iv.step;
|
|
87
|
+
if (unit === 'week') {
|
|
88
|
+
for (let t = this.startOf(lo, 'week'); t <= hi && out.length < MOST; t = this.add(t, 'week', 1))
|
|
89
|
+
if (t >= lo)
|
|
90
|
+
out.push(t);
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
if (unit === 'year') {
|
|
94
|
+
// Years can be many and far apart: go straight to the first aligned one and then by whole steps.
|
|
95
|
+
const base = this.startOf(lo, 'year'), y = this.field(base, 'year'), first = Math.ceil(y / step) * step;
|
|
96
|
+
for (let t = this.add(base, 'year', first - y); t <= hi && out.length < MOST; t = this.add(t, 'year', step))
|
|
97
|
+
if (t >= lo)
|
|
98
|
+
out.push(t);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
const aligned = (t) => { const f = this.field(t, unit); return unit === 'day' ? (f - 1) % step === 0 : f % step === 0; };
|
|
102
|
+
let guard = 0;
|
|
103
|
+
for (let t = this.startOf(lo, unit); t <= hi && guard++ < 20 * MOST;) {
|
|
104
|
+
if (t >= lo && aligned(t)) {
|
|
105
|
+
out.push(t);
|
|
106
|
+
if (out.length >= MOST)
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
let next = this.add(t, unit, 1);
|
|
110
|
+
// Stepping by whole hours (minutes, seconds) leaves the clock's grid when its offset changes by a part of one (Lord Howe's half hour): back onto it.
|
|
111
|
+
if (this.startOf(next, unit) !== next)
|
|
112
|
+
next = Math.max(this.startOf(this.add(next, unit, 1), unit), t + 1);
|
|
113
|
+
t = next;
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
ticks(r, count) {
|
|
118
|
+
if (!(r.max > r.min) || !isFinite(r.min) || !isFinite(r.max))
|
|
119
|
+
return [];
|
|
120
|
+
return this.#walk(r.min, r.max, intervalFor((r.max - r.min) / Math.max(1, count)));
|
|
121
|
+
}
|
|
122
|
+
nice(r) {
|
|
123
|
+
if (!(r.max > r.min) || !isFinite(r.min) || !isFinite(r.max))
|
|
124
|
+
return r;
|
|
125
|
+
const iv = intervalFor((r.max - r.min) / 5), pad = 2 * iv.ms;
|
|
126
|
+
const all = this.#walk(r.min - pad, r.max + pad, iv);
|
|
127
|
+
let min = r.min, max = r.max;
|
|
128
|
+
for (const t of all) {
|
|
129
|
+
if (t <= r.min)
|
|
130
|
+
min = t;
|
|
131
|
+
if (t >= r.max) {
|
|
132
|
+
max = t;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { min, max };
|
|
137
|
+
}
|
|
138
|
+
/** The reading of one instant: the `full` format. */
|
|
139
|
+
label(value) { return this.format(value, 'full', this.#spec('full')); }
|
|
140
|
+
labels(values) {
|
|
141
|
+
// How finely the ticks that begin no day are divided: the largest unit that every tick begins.
|
|
142
|
+
let fine = 'hour';
|
|
143
|
+
if (values.some((t) => this.startOf(t, 'hour') !== t))
|
|
144
|
+
fine = 'minute';
|
|
145
|
+
if (fine === 'minute' && values.some((t) => this.startOf(t, 'minute') !== t))
|
|
146
|
+
fine = 'second';
|
|
147
|
+
if (fine === 'second' && values.some((t) => this.startOf(t, 'second') !== t))
|
|
148
|
+
fine = 'millisecond';
|
|
149
|
+
return values.map((t) => {
|
|
150
|
+
const rank = this.startOf(t, 'year') === t ? 'year' : this.startOf(t, 'month') === t ? 'month' : this.startOf(t, 'day') === t ? 'day' : fine;
|
|
151
|
+
return this.format(t, rank, this.#spec(rank));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
package/dist/trend.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type DataNode, DataTransform, type FillPlan, type PlanContext } from './data.ts';
|
|
2
|
+
import { type BaseAttributes } from './element.ts';
|
|
3
|
+
import type { Extent } from './geometry.ts';
|
|
4
|
+
/** How well a trendline fits its points, and what it found. */
|
|
5
|
+
export interface TrendFit {
|
|
6
|
+
/** Points the fit used. */
|
|
7
|
+
readonly n: number;
|
|
8
|
+
/** The share of the values' variance the curve explains (1 is a perfect fit; it is below 0 when the curve does worse than the mean). */
|
|
9
|
+
readonly r2: number;
|
|
10
|
+
/** Root mean square of the distances from the curve to the points. */
|
|
11
|
+
readonly rmse: number;
|
|
12
|
+
/** A polynomial's coefficients lowest power first, in `u = (x - center) / scale`; a line's `[intercept, slope]` in x itself; a sinusoid's `[offset, a, b]` for `offset + a cos(w (x - center)) + b sin(w (x - center))` with `w = 2π / period`. */
|
|
13
|
+
readonly coefficients?: readonly number[];
|
|
14
|
+
readonly center?: number;
|
|
15
|
+
readonly scale?: number;
|
|
16
|
+
/** A sinusoid's period, and its amplitude. */
|
|
17
|
+
readonly period?: number;
|
|
18
|
+
readonly amplitude?: number;
|
|
19
|
+
/** A loess line's reach on each side of a point, in the domain's units (it is wider where the points are few). */
|
|
20
|
+
readonly bandwidth?: number;
|
|
21
|
+
}
|
|
22
|
+
/** A fitted curve: its value at a position (NaN where it has none), and what it found beside that. */
|
|
23
|
+
export interface TrendModel {
|
|
24
|
+
at(x: number): number;
|
|
25
|
+
readonly details?: Omit<TrendFit, 'n' | 'r2' | 'rmse'>;
|
|
26
|
+
}
|
|
27
|
+
export interface TrendlineAttributes extends BaseAttributes {
|
|
28
|
+
/** A channel of the source to fit against instead of its domain. */
|
|
29
|
+
x?: string;
|
|
30
|
+
/** How far past the data the line goes at each end, as a fraction of the data's span: one number, or `[before, after]`. Default 0. */
|
|
31
|
+
extend?: number | readonly [number, number];
|
|
32
|
+
/** Samples, evenly spaced over the whole span, always. Left out: one for each pixel of width across the part in view. */
|
|
33
|
+
count?: number;
|
|
34
|
+
}
|
|
35
|
+
/** The base of trendlines: reads the points, fits them once for each change, and samples the curve. */
|
|
36
|
+
export declare abstract class Trendline<A extends TrendlineAttributes = TrendlineAttributes> extends DataTransform<A, [source: DataNode]> {
|
|
37
|
+
#private;
|
|
38
|
+
static attrTypes: {
|
|
39
|
+
id: 1;
|
|
40
|
+
meta: {
|
|
41
|
+
title: 1;
|
|
42
|
+
description: 1;
|
|
43
|
+
role: 1;
|
|
44
|
+
};
|
|
45
|
+
x: 1;
|
|
46
|
+
extend: 0;
|
|
47
|
+
count: 2;
|
|
48
|
+
};
|
|
49
|
+
get source(): DataNode;
|
|
50
|
+
/** Make the curve from the points: ascending in x, every value finite, at least one. Null when they do not determine one. */
|
|
51
|
+
protected abstract fitModel(xs: Float64Array, ys: Float64Array): TrendModel | null;
|
|
52
|
+
invalidate(): void;
|
|
53
|
+
get version(): number;
|
|
54
|
+
get channelNames(): readonly string[];
|
|
55
|
+
get pointCount(): number;
|
|
56
|
+
/** How well the curve fits, or null when there are too few points to fit one. */
|
|
57
|
+
get fit(): TrendFit | null;
|
|
58
|
+
/** The fitted value at a position, or at each of an array of them. NaN where there is no fit, or the position is not a number. */
|
|
59
|
+
evaluate(x: number): number;
|
|
60
|
+
evaluate(x: ArrayLike<number>): Float64Array;
|
|
61
|
+
domain(): Extent | null;
|
|
62
|
+
valueExtent(): Extent | null;
|
|
63
|
+
channelExtent(name: string): Extent | null;
|
|
64
|
+
plan(context: PlanContext): FillPlan;
|
|
65
|
+
}
|
|
66
|
+
export interface LinearTrendAttributes extends TrendlineAttributes {
|
|
67
|
+
}
|
|
68
|
+
/** The least-squares line. `fit.coefficients` is `[intercept, slope]`. */
|
|
69
|
+
export declare class LinearTrend extends Trendline<LinearTrendAttributes> {
|
|
70
|
+
protected fitModel(xs: Float64Array, ys: Float64Array): TrendModel | null;
|
|
71
|
+
}
|
|
72
|
+
export interface PolynomialTrendAttributes extends TrendlineAttributes {
|
|
73
|
+
/** The highest power, 1 to 12. Default 2. */
|
|
74
|
+
degree?: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A polynomial of `degree` (2 by default: a parabola). It is fitted on the positions centred and scaled to -1..1, which
|
|
78
|
+
* keeps high degrees well-behaved; `fit.coefficients` are in that scaled position `u = (x - center) / scale`. It needs
|
|
79
|
+
* more distinct positions than its degree.
|
|
80
|
+
*/
|
|
81
|
+
export declare class PolynomialTrend extends Trendline<PolynomialTrendAttributes> {
|
|
82
|
+
static attrTypes: {
|
|
83
|
+
id: 1;
|
|
84
|
+
meta: {
|
|
85
|
+
title: 1;
|
|
86
|
+
description: 1;
|
|
87
|
+
role: 1;
|
|
88
|
+
};
|
|
89
|
+
x: 1;
|
|
90
|
+
extend: 0;
|
|
91
|
+
count: 2;
|
|
92
|
+
degree: 2;
|
|
93
|
+
};
|
|
94
|
+
protected fitModel(xs: Float64Array, ys: Float64Array): TrendModel | null;
|
|
95
|
+
}
|
|
96
|
+
export interface SinusoidalTrendAttributes extends TrendlineAttributes {
|
|
97
|
+
/** The period in the domain's units. Left out, the period that fits best is found. */
|
|
98
|
+
period?: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A constant plus one sine wave. Give the `period` and only the offset, amplitude and phase are fitted (a linear fit). Without
|
|
102
|
+
* it the period is found too: a periodogram over the periods the data can show (from the whole span down to about two of its
|
|
103
|
+
* typical gaps, which works for unevenly spaced points), then a refinement of the best on every point. `fit.period` and
|
|
104
|
+
* `fit.amplitude` say what was found. A wave with fewer than three points, or with every point at one position, has no fit.
|
|
105
|
+
*/
|
|
106
|
+
export declare class SinusoidalTrend extends Trendline<SinusoidalTrendAttributes> {
|
|
107
|
+
static attrTypes: {
|
|
108
|
+
id: 1;
|
|
109
|
+
meta: {
|
|
110
|
+
title: 1;
|
|
111
|
+
description: 1;
|
|
112
|
+
role: 1;
|
|
113
|
+
};
|
|
114
|
+
x: 1;
|
|
115
|
+
extend: 0;
|
|
116
|
+
count: 2;
|
|
117
|
+
period: 2;
|
|
118
|
+
};
|
|
119
|
+
protected fitModel(xs: Float64Array, ys: Float64Array): TrendModel | null;
|
|
120
|
+
}
|
|
121
|
+
export interface LoessTrendAttributes extends TrendlineAttributes {
|
|
122
|
+
/** 0 to 1: 0 joins the points, 1 is the least-squares line, and between them the line is smoother the larger it is. Default 0.5. */
|
|
123
|
+
smoothness?: number;
|
|
124
|
+
/** How a point's weight falls with its distance. Default 'tricube'. */
|
|
125
|
+
kernel?: 'tricube' | 'gaussian';
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Local linear regression (the method behind LOESS and LOWESS), centred on each position and reaching both ways: at each place
|
|
129
|
+
* it fits a straight line to the points near it, weighting nearer ones more, and takes that line's value. `smoothness` sets
|
|
130
|
+
* how far it reaches, as `smoothness / (1 - smoothness)` times 0.3 of the data's span: 0 reaches nowhere and the curve joins the
|
|
131
|
+
* points (straight between neighbours; points sharing a position are averaged), larger values are smoother, and 1 reaches everywhere
|
|
132
|
+
* equally, which is the least-squares line (it is that exactly). Where the points are sparse the reach widens to hold at least
|
|
133
|
+
* three of them. Past the ends of the data it continues the local line at the end, with a slope over a wide reach. Each place costs the points in its reach, so a very smooth curve over very many points is slow to sample: pick `count`.
|
|
134
|
+
* It needs sorted points, which it makes itself. `fit.bandwidth` is the reach.
|
|
135
|
+
*/
|
|
136
|
+
export declare class LoessTrend extends Trendline<LoessTrendAttributes> {
|
|
137
|
+
static attrTypes: {
|
|
138
|
+
id: 1;
|
|
139
|
+
meta: {
|
|
140
|
+
title: 1;
|
|
141
|
+
description: 1;
|
|
142
|
+
role: 1;
|
|
143
|
+
};
|
|
144
|
+
x: 1;
|
|
145
|
+
extend: 0;
|
|
146
|
+
count: 2;
|
|
147
|
+
smoothness: 2;
|
|
148
|
+
kernel: 1;
|
|
149
|
+
};
|
|
150
|
+
protected fitModel(xs: Float64Array, ys: Float64Array): TrendModel | null;
|
|
151
|
+
}
|