@pond-ts/charts 0.68.0 → 0.69.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/AGENTS.md +12 -3
- package/API.md +79 -75
- package/CHANGELOG.md +81 -1
- package/README.md +4 -0
- package/dist/ChartContainer.d.ts +23 -0
- package/dist/ChartContainer.js +15 -4
- package/dist/XAxis.d.ts +13 -1
- package/dist/XAxis.js +35 -3
- package/dist/context.d.ts +14 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/tickLadder.d.ts +66 -20
- package/dist/tickLadder.js +178 -105
- package/dist/tradingTimeScale.d.ts +44 -3
- package/dist/tradingTimeScale.js +113 -38
- package/package.json +6 -4
package/dist/XAxis.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, useContext, useRef } from 'react';
|
|
2
|
+
import { Fragment, useContext, useMemo, useRef } from 'react';
|
|
3
3
|
import { scaleLinear } from 'd3-scale';
|
|
4
|
+
import { TimeZone } from 'pond-ts';
|
|
4
5
|
import { derivedTicks } from './derivedTicks.js';
|
|
5
6
|
import { ContainerContext, CursorContext, } from './context.js';
|
|
6
7
|
import { tickValues } from './yticks.js';
|
|
@@ -166,7 +167,7 @@ export function thinCategoryLabels(ticks, slot, plotWidth, fontSize, fontFamily)
|
|
|
166
167
|
* plot's own drag, including `bounds` / `minDuration` and the trading calendar.
|
|
167
168
|
* A category axis has no continuous domain and stays inert.
|
|
168
169
|
*/
|
|
169
|
-
export function XAxis({ format, label, side = 'bottom', height, ticks: customTicks, transform, color, align = 'center', dateStyle = 'flat', onMouseEvent, } = {}) {
|
|
170
|
+
export function XAxis({ format, label, side = 'bottom', height, ticks: customTicks, transform, color, align = 'center', dateStyle = 'flat', timeZone, onMouseEvent, } = {}) {
|
|
170
171
|
const container = useContext(ContainerContext);
|
|
171
172
|
if (container === null) {
|
|
172
173
|
throw new Error('<XAxis> must be rendered inside a <ChartContainer>');
|
|
@@ -175,7 +176,38 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
175
176
|
// `xTickCount` is the container's shared x-side count — the same value the x
|
|
176
177
|
// gridlines and `formatTime` use, so labels and grid stay on the same instants
|
|
177
178
|
// (width-derived on a trading-time axis).
|
|
178
|
-
const { xScale, plotWidth, leftGutter, theme, formatTime, xKind, xTickCount, } = container;
|
|
179
|
+
const { xScale: containerScale, plotWidth, leftGutter, theme, formatTime: containerFormatTime, xKind, xTickCount, } = container;
|
|
180
|
+
// A per-strip zone: the container's shared scale re-derived with its
|
|
181
|
+
// calendar in `timeZone` — identical pixel mapping, so every `xScale(v)`
|
|
182
|
+
// below lands where the plot puts it, but ticks / labels / bands / this
|
|
183
|
+
// strip's pills come from this zone's ladder. Canonicalised (and validated)
|
|
184
|
+
// so an unknown id throws by name and `'utc'` / `'UTC'` memoize as one.
|
|
185
|
+
const zonedScale = useMemo(() => {
|
|
186
|
+
if (timeZone === undefined ||
|
|
187
|
+
xKind !== 'time' ||
|
|
188
|
+
!('withTimeZone' in containerScale)) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const id = TimeZone.of(timeZone).id;
|
|
192
|
+
const s = containerScale;
|
|
193
|
+
return s.timeZone() === id ? undefined : s.withTimeZone(id);
|
|
194
|
+
}, [containerScale, timeZone, xKind]);
|
|
195
|
+
const xScale = zonedScale ?? containerScale;
|
|
196
|
+
// The label formatter this strip falls back to when no explicit `format`
|
|
197
|
+
// shapes it. The container's `formatTime` was resolved in the container's
|
|
198
|
+
// zone; a zoned strip needs the same channel in its own — the grain-aware
|
|
199
|
+
// default, or the container's `timeFormat` specifier re-resolved (a
|
|
200
|
+
// function `timeFormat` receives epoch ms and is used verbatim either way).
|
|
201
|
+
const formatTime = useMemo(() => {
|
|
202
|
+
if (zonedScale === undefined)
|
|
203
|
+
return containerFormatTime;
|
|
204
|
+
const custom = container.timeFormat;
|
|
205
|
+
if (typeof custom === 'function')
|
|
206
|
+
return custom;
|
|
207
|
+
return custom === undefined
|
|
208
|
+
? zonedScale.readoutFormat(xTickCount)
|
|
209
|
+
: resolveTimeFormat(zonedScale, xTickCount, custom);
|
|
210
|
+
}, [zonedScale, containerFormatTime, container.timeFormat, xTickCount]);
|
|
179
211
|
// The cursor's x-axis slot: did the mounted cursor in effect register one
|
|
180
212
|
// (`renderXAxis` — the crosshair's time pill)? While hovering, that is the
|
|
181
213
|
// **hovered row's** effective cursor — so a per-row override reaches this
|
package/dist/context.d.ts
CHANGED
|
@@ -270,6 +270,20 @@ export interface ContainerFrame {
|
|
|
270
270
|
* tick labels) without moving them.
|
|
271
271
|
*/
|
|
272
272
|
readonly formatReadout?: ((value: number) => string) | undefined;
|
|
273
|
+
/**
|
|
274
|
+
* The IANA zone the time axis renders in — the container's resolved
|
|
275
|
+
* `timeZone` (explicit prop, else the calendar's), canonical id; `undefined`
|
|
276
|
+
* when the axis is in the runtime's local zone. For a consumer's own
|
|
277
|
+
* formatter (`timeFormat` / `cursorFormat` functions receive epoch ms) to
|
|
278
|
+
* read the same zone the ticks do.
|
|
279
|
+
*/
|
|
280
|
+
readonly timeZone: string | undefined;
|
|
281
|
+
/**
|
|
282
|
+
* The container's raw `timeFormat` prop, for a strip that must re-resolve it
|
|
283
|
+
* in another zone (`<XAxis timeZone>`): a specifier string is re-resolved
|
|
284
|
+
* against that strip's zoned scale, a function is used verbatim.
|
|
285
|
+
*/
|
|
286
|
+
readonly timeFormat: AxisFormat | undefined;
|
|
273
287
|
/** Whether an explicit container `timeFormat` shaped {@link formatTime}. The
|
|
274
288
|
* x axis suppresses its boundary (second) label row when it's set — a
|
|
275
289
|
* custom format owns the whole label, so the ladder mustn't second-line it. */
|
package/dist/index.d.ts
CHANGED
|
@@ -68,8 +68,8 @@ export type { ChartLegend, LegendRow, LegendItem } from './useChartLegend.js';
|
|
|
68
68
|
export { useChartFrame } from './useChartFrame.js';
|
|
69
69
|
export type { ChartFrame, ChartFrameRow, ChartBands, ChartBand, } from './useChartFrame.js';
|
|
70
70
|
export type { ChartXScale } from './context.js';
|
|
71
|
-
export { scaleTradingTime } from './tradingTimeScale.js';
|
|
72
|
-
export type { TradingTimeScale, DiscontinuityProvider, TimeGrain, } from './tradingTimeScale.js';
|
|
71
|
+
export { scaleTradingTime, identityProvider } from './tradingTimeScale.js';
|
|
72
|
+
export type { TradingTimeScale, DiscontinuityProvider, TradingCalendarLike, ScaleTimeZoneOptions, TimeGrain, } from './tradingTimeScale.js';
|
|
73
73
|
export { scaleBand } from './bandScale.js';
|
|
74
74
|
export type { ScaleBand } from './bandScale.js';
|
|
75
75
|
export { Region, Baseline, Marker, Zone } from './annotations.js';
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ export { useChartLegend } from './useChartLegend.js';
|
|
|
58
58
|
// by mirroring the library's own gutter arithmetic — a duplicate that drifts
|
|
59
59
|
// silently the moment the library changes how a gutter is sized.
|
|
60
60
|
export { useChartFrame } from './useChartFrame.js';
|
|
61
|
-
export { scaleTradingTime } from './tradingTimeScale.js';
|
|
61
|
+
export { scaleTradingTime, identityProvider } from './tradingTimeScale.js';
|
|
62
62
|
// The ordinal category (band) scale — the transpose view's "columns on x" axis.
|
|
63
63
|
export { scaleBand } from './bandScale.js';
|
|
64
64
|
// Annotations — user-authored marks in the turquoise register (distinct from the
|
package/dist/tickLadder.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TimeZone } from 'pond-ts';
|
|
1
2
|
import type { DiscontinuityProvider } from './tradingTimeScale.js';
|
|
2
3
|
/**
|
|
3
4
|
* The logical tick ladder — grain selection for a time axis. Ticks sit on real
|
|
@@ -34,16 +35,62 @@ export type TickGranularity = 'second1' | 'second5' | 'second15' | 'second30' |
|
|
|
34
35
|
export type TimeGrain = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
|
|
35
36
|
/** Collapse a {@link TickGranularity} to its coarse {@link TimeGrain} unit. */
|
|
36
37
|
export declare function coarseUnitOf(g: TickGranularity): TimeGrain;
|
|
38
|
+
/** The calendar operations the tick ladder needs. Months are `1…12`. */
|
|
39
|
+
export interface TickCalendar {
|
|
40
|
+
/** Midnight of the day containing `t`. */
|
|
41
|
+
startOfDay(t: number): number;
|
|
42
|
+
/** Midnight of the day after the one containing `t`. */
|
|
43
|
+
nextDay(t: number): number;
|
|
44
|
+
/** Midnight of the Monday of the week containing `t`. */
|
|
45
|
+
startOfWeek(t: number): number;
|
|
46
|
+
/** The civil date of `t`. */
|
|
47
|
+
parts(t: number): {
|
|
48
|
+
year: number;
|
|
49
|
+
month: number;
|
|
50
|
+
day: number;
|
|
51
|
+
};
|
|
52
|
+
/** Midnight of the first of `month` (`1…12`; `13` carries into the next year, `0` into the previous). */
|
|
53
|
+
monthStart(year: number, month: number): number;
|
|
54
|
+
/** Days in `month` of `year`. */
|
|
55
|
+
daysInMonth(year: number, month: number): number;
|
|
56
|
+
/**
|
|
57
|
+
* The first clock-aligned `stepMs` multiple at or after `t`, counted from
|
|
58
|
+
* `t`'s own midnight — 00:00 / 03:00 / 06:00 … for a 3 h step. The local
|
|
59
|
+
* calendar steps **fixed milliseconds** from midnight (so on a DST day the
|
|
60
|
+
* later anchors drift off the wall clock by the shift, until the next
|
|
61
|
+
* midnight re-anchors — the pre-seam behaviour, kept); a zoned calendar
|
|
62
|
+
* aligns to the **wall clock**, so 6 h anchors read 00 / 06 / 12 / 18 on
|
|
63
|
+
* both sides of the jump, with one short or long gap at the transition.
|
|
64
|
+
* Always `>= t`.
|
|
65
|
+
*/
|
|
66
|
+
nextAligned(t: number, stepMs: number): number;
|
|
67
|
+
/**
|
|
68
|
+
* The anchor after an aligned anchor `t` at `stepMs`. The local calendar
|
|
69
|
+
* steps `t + stepMs` — fixed elapsed milliseconds, the pre-seam loop
|
|
70
|
+
* verbatim, which is what keeps a session that spans a DST midnight (a
|
|
71
|
+
* futures 18:00 → 17:00 session) ticking exactly as it did. A zoned
|
|
72
|
+
* calendar re-aligns through {@link nextAligned} so the anchors stay on
|
|
73
|
+
* the wall clock across the jump.
|
|
74
|
+
*/
|
|
75
|
+
nextAnchor(t: number, stepMs: number): number;
|
|
76
|
+
}
|
|
77
|
+
/** The runtime-local calendar — `Date`'s local accessors, exactly as the
|
|
78
|
+
* ladder computed before it had a zone. The default {@link TickCalendar}. */
|
|
79
|
+
export declare const localTickCalendar: TickCalendar;
|
|
80
|
+
/** A {@link TickCalendar} for an IANA zone, on core's `TimeZone`. */
|
|
81
|
+
export declare function zonedTickCalendar(zone: TimeZone): TickCalendar;
|
|
82
|
+
/** Resolve an optional IANA id to the calendar the ladder should use: the
|
|
83
|
+
* runtime-local calendar when `timeZone` is undefined, else the zone's. */
|
|
84
|
+
export declare function tickCalendarFor(timeZone: string | undefined): TickCalendar;
|
|
37
85
|
/**
|
|
38
|
-
* The
|
|
39
|
-
* day / week / month / quarter / year share a key.
|
|
40
|
-
* agrees with the
|
|
41
|
-
* zone
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* anchor is its own tick), so they key by identity.
|
|
86
|
+
* The calendar bucket key for `t` at grain `g` — two instants in the same
|
|
87
|
+
* day / week / month / quarter / year share a key. Computed in `cal`'s zone
|
|
88
|
+
* (runtime-local by default) so it agrees with the label formatter for the
|
|
89
|
+
* same zone; a trading axis passes its exchange zone so the grain buckets by
|
|
90
|
+
* the exchange day rather than the viewer's. Hour grains are never bucketed
|
|
91
|
+
* (each anchor is its own tick), so they key by identity.
|
|
45
92
|
*/
|
|
46
|
-
export declare function bucketKey(t: number, g: TickGranularity): number;
|
|
93
|
+
export declare function bucketKey(t: number, g: TickGranularity, cal?: TickCalendar): number;
|
|
47
94
|
/**
|
|
48
95
|
* Thin an ascending run of **session opens** down to about `count` axis ticks.
|
|
49
96
|
* Picks the finest rung: every session → **per-month uniform session stride**
|
|
@@ -82,7 +129,7 @@ export declare function bucketKey(t: number, g: TickGranularity): number;
|
|
|
82
129
|
* This is the day-and-coarser half of the ladder; {@link buildTicks} adds the
|
|
83
130
|
* sub-day rungs.
|
|
84
131
|
*/
|
|
85
|
-
export declare function coarsenCalendar(opens: readonly number[], count: number, spanDays?: number, provider?: DiscontinuityProvider): {
|
|
132
|
+
export declare function coarsenCalendar(opens: readonly number[], count: number, spanDays?: number, provider?: DiscontinuityProvider, cal?: TickCalendar): {
|
|
86
133
|
ticks: number[];
|
|
87
134
|
granularity: TickGranularity;
|
|
88
135
|
};
|
|
@@ -105,7 +152,7 @@ export declare function coarsenCalendar(opens: readonly number[], count: number,
|
|
|
105
152
|
* the live-span estimate first (like {@link buildTicks}) and skipped when they
|
|
106
153
|
* add no anchor beyond the session opens themselves (that is the day level).
|
|
107
154
|
*/
|
|
108
|
-
export declare function buildGridLevels(provider: DiscontinuityProvider, opens: readonly number[], domainEnd: number, cap: number): Array<{
|
|
155
|
+
export declare function buildGridLevels(provider: DiscontinuityProvider, opens: readonly number[], domainEnd: number, cap: number, cal?: TickCalendar): Array<{
|
|
109
156
|
granularity: TickGranularity;
|
|
110
157
|
values: number[];
|
|
111
158
|
}>;
|
|
@@ -130,7 +177,7 @@ export declare function nominalStepMs(g: TickGranularity): number;
|
|
|
130
177
|
* themselves fit — a year of daily sessions never wastes time generating hour
|
|
131
178
|
* anchors.
|
|
132
179
|
*/
|
|
133
|
-
export declare function buildTicks(provider: DiscontinuityProvider, opens: readonly number[], domainEnd: number, cap: number): {
|
|
180
|
+
export declare function buildTicks(provider: DiscontinuityProvider, opens: readonly number[], domainEnd: number, cap: number, cal?: TickCalendar): {
|
|
134
181
|
ticks: number[];
|
|
135
182
|
granularity: TickGranularity;
|
|
136
183
|
};
|
|
@@ -181,14 +228,13 @@ export declare function bandFormatFor(g: TickGranularity): string;
|
|
|
181
228
|
* same-shade day-bands side by side on a gappy calendar. A rare, cosmetic
|
|
182
229
|
* consequence of keeping the shade fixed to the date rather than the slot.
|
|
183
230
|
*/
|
|
184
|
-
export declare function bandShaded(t: number, g: TickGranularity): boolean;
|
|
185
|
-
/** The
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
export declare function bandStartOf(t: number, g: TickGranularity): number;
|
|
231
|
+
export declare function bandShaded(t: number, g: TickGranularity, cal?: TickCalendar): boolean;
|
|
232
|
+
/** The start of the band grain `g` containing `t` (the band's left edge) in
|
|
233
|
+
* `cal`'s zone: midnight, month start, or Jan 1. */
|
|
234
|
+
export declare function bandStartOf(t: number, g: TickGranularity, cal?: TickCalendar): number;
|
|
189
235
|
/** The start of the band grain `g` **after** the one containing `t` — the next
|
|
190
|
-
*
|
|
191
|
-
export declare function bandNext(t: number, g: TickGranularity): number;
|
|
236
|
+
* midnight / month start / Jan 1 in `cal`'s zone. */
|
|
237
|
+
export declare function bandNext(t: number, g: TickGranularity, cal?: TickCalendar): number;
|
|
192
238
|
/**
|
|
193
239
|
* Which of `ticks` (at grain `granularity`) carry a boundary label: every tick
|
|
194
240
|
* whose boundary-grain bucket differs from the previous tick's — i.e. a
|
|
@@ -199,7 +245,7 @@ export declare function bandNext(t: number, g: TickGranularity): number;
|
|
|
199
245
|
* tick-to-tick on a live sliding window). Empty when the grain has no
|
|
200
246
|
* boundary row (year grain).
|
|
201
247
|
*/
|
|
202
|
-
export declare function boundaryTicks(ticks: readonly number[], granularity: TickGranularity, domainStart?: number): number[];
|
|
248
|
+
export declare function boundaryTicks(ticks: readonly number[], granularity: TickGranularity, domainStart?: number, cal?: TickCalendar): number[];
|
|
203
249
|
/**
|
|
204
250
|
* The terse **base** (non-promoted) flat label format for grain `g` — the label
|
|
205
251
|
* a tick carries when it opens no coarser period: the clock time for a sub-day
|
|
@@ -228,5 +274,5 @@ export declare function flatBaseFormatFor(g: TickGranularity): string;
|
|
|
228
274
|
* opening at May 1 midnight reads `May 16 …`, not `1 16 …`). Without
|
|
229
275
|
* `domainStart` the first tick is never promoted.
|
|
230
276
|
*/
|
|
231
|
-
export declare function flatFormats(ticks: readonly number[], granularity: TickGranularity, domainStart?: number): string[];
|
|
277
|
+
export declare function flatFormats(ticks: readonly number[], granularity: TickGranularity, domainStart?: number, cal?: TickCalendar): string[];
|
|
232
278
|
//# sourceMappingURL=tickLadder.d.ts.map
|