@pond-ts/charts 0.47.0 → 0.48.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/CHANGELOG.md +45 -1
- package/dist/ChartContainer.d.ts +27 -6
- package/dist/ChartContainer.js +28 -9
- package/dist/format.d.ts +20 -0
- package/dist/index.d.ts +2 -2
- package/dist/tickLadder.d.ts +20 -0
- package/dist/tickLadder.js +38 -0
- package/dist/tradingTimeScale.d.ts +26 -7
- package/dist/tradingTimeScale.js +12 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,9 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
|
|
|
8
8
|
tag, so this file covers them all. Pre-1.0: minor bumps may include new features
|
|
9
9
|
and type-level changes; patch bumps are strictly additive.
|
|
10
10
|
|
|
11
|
-
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.
|
|
11
|
+
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.48.1...HEAD
|
|
12
|
+
[0.48.1]: https://github.com/pond-ts/pond/compare/v0.48.0...v0.48.1
|
|
13
|
+
[0.48.0]: https://github.com/pond-ts/pond/compare/v0.47.0...v0.48.0
|
|
12
14
|
[0.47.0]: https://github.com/pond-ts/pond/compare/v0.46.0...v0.47.0
|
|
13
15
|
[0.46.0]: https://github.com/pjm17971/pond-ts/compare/v0.45.0...v0.46.0
|
|
14
16
|
[0.45.0]: https://github.com/pjm17971/pond-ts/compare/v0.44.1...v0.45.0
|
|
@@ -46,6 +48,48 @@ and type-level changes; patch bumps are strictly additive.
|
|
|
46
48
|
|
|
47
49
|
## [Unreleased]
|
|
48
50
|
|
|
51
|
+
## [0.48.1] — 2026-07-19
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- `smooth(column, 'loess', …)` is now numerically stable on absolute epoch-ms
|
|
56
|
+
timestamps. The local weighted regression is now conditioned on `x` centred at
|
|
57
|
+
the query point; previously the un-centred normal equations lost precision to
|
|
58
|
+
floating-point cancellation (anchors ~1.7e12 → `x²` terms ~3e24). Severity
|
|
59
|
+
scales with (anchor magnitude / window width)²: on finely-spaced series
|
|
60
|
+
(second-level cadence) the fit overshot wildly outside the data range instead
|
|
61
|
+
of smoothing, while coarser day-spaced series erred only subtly. Output is now
|
|
62
|
+
shift-invariant to the time origin. Series built from small integer anchors
|
|
63
|
+
(e.g. in unit tests) were unaffected, which is why this hid.
|
|
64
|
+
|
|
65
|
+
## [0.48.0] — 2026-07-17
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **charts:** `<ChartContainer>` gains **`cursorFormat`** — an independent
|
|
70
|
+
format for the **cursor / marker readout** (the crosshair time pill, marker
|
|
71
|
+
axis indicators, annotation auto-labels), separate from the tick-label
|
|
72
|
+
`timeFormat` / `format`. Unlike `timeFormat` (which _owns the labels_ and so
|
|
73
|
+
opts the axis out of the `dateStyle` ladder by design), `cursorFormat` shapes
|
|
74
|
+
only the readout and **keeps the flat / stacked date style** — resolving the
|
|
75
|
+
"one knob, two concerns" bind where the only way to fix the pill was to give
|
|
76
|
+
up the styled axis. A d3 specifier **string** formats uniformly; a
|
|
77
|
+
**function** `(epochMs, { grain, defaultText }) => string` is handed the
|
|
78
|
+
axis's resolved coarse **`TimeGrain`** (`year` … `second`) and the
|
|
79
|
+
grain-aware default text, so it can branch on zoom and pass the default
|
|
80
|
+
through — no re-deriving the grain from the range. New public types
|
|
81
|
+
**`CursorFormat`** / **`TimeGrain`**; `TradingTimeScale` gains
|
|
82
|
+
`readoutFormat(count)` and `grain(count)`.
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- **charts:** the cursor / marker time readout now formats at the axis's
|
|
87
|
+
**own grain** by default instead of d3's multi-scale default — a
|
|
88
|
+
day-or-coarser axis reads a **date** (`Sep 14, 2026`), a sub-day axis reads
|
|
89
|
+
date + clock. Fixes the 0.47.0 flat-axis regression where a daily bar at a
|
|
90
|
+
foreign-timezone midnight rendered as a bare time-of-day (`02 AM`) in the
|
|
91
|
+
crosshair pill (Tidal F-charts-7). Tick labels are unchanged.
|
|
92
|
+
|
|
49
93
|
## [0.47.0] — 2026-07-17
|
|
50
94
|
|
|
51
95
|
### Added
|
package/dist/ChartContainer.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type DiscontinuityProvider, type TradingCalendarLike } from './tradingT
|
|
|
3
3
|
import { Sequence, BoundedSequence } from 'pond-ts';
|
|
4
4
|
import type { TimeRange } from 'pond-ts';
|
|
5
5
|
import { type AnnotationKind, type CreateSpec, type CursorMode, type SelectInfo, type TrackerInfo } from './context.js';
|
|
6
|
-
import { type AxisFormat } from './format.js';
|
|
6
|
+
import { type AxisFormat, type CursorFormat } from './format.js';
|
|
7
7
|
import { type ChartTheme } from './theme.js';
|
|
8
8
|
export interface ChartContainerProps {
|
|
9
9
|
/**
|
|
@@ -290,12 +290,33 @@ export interface ChartContainerProps {
|
|
|
290
290
|
*/
|
|
291
291
|
snap?: boolean;
|
|
292
292
|
/**
|
|
293
|
-
* Time-axis
|
|
294
|
-
* `(epochMs) => string` function ({@link AxisFormat})
|
|
295
|
-
*
|
|
296
|
-
*
|
|
293
|
+
* Time-axis **label** formatting — a d3 time specifier string (e.g. `'%H:%M'`)
|
|
294
|
+
* or a `(epochMs) => string` function ({@link AxisFormat}). A custom format
|
|
295
|
+
* **owns the labels**, so it opts the axis out of the `dateStyle` ladder
|
|
296
|
+
* (flat / stacked) by design. **Omitted ⇒ the flat/stacked date style.** To
|
|
297
|
+
* shape only the cursor readout while keeping a date style, use
|
|
298
|
+
* {@link cursorFormat} instead. (For back-compat this also shapes the readout
|
|
299
|
+
* when `cursorFormat` is absent.)
|
|
297
300
|
*/
|
|
298
301
|
timeFormat?: AxisFormat;
|
|
302
|
+
/**
|
|
303
|
+
* The **cursor / marker readout** format — the crosshair time pill, marker
|
|
304
|
+
* axis indicators, and annotation auto-labels — **independent of the tick
|
|
305
|
+
* labels**, so it does **not** disqualify the `dateStyle` ladder.
|
|
306
|
+
* **Omitted ⇒ a grain-aware default**: the readout formats at the axis's own
|
|
307
|
+
* granularity, so a day-or-coarser axis reads a **date** (never a
|
|
308
|
+
* time-of-day) and a sub-day axis reads date + clock — a daily bar at a
|
|
309
|
+
* foreign-tz midnight no longer renders as `02 AM`.
|
|
310
|
+
*
|
|
311
|
+
* A d3 time-specifier **string** formats uniformly at every zoom; a
|
|
312
|
+
* **function** `(epochMs, { grain, defaultText }) => string` receives the
|
|
313
|
+
* axis's resolved coarse {@link TimeGrain} and the grain-aware default text,
|
|
314
|
+
* so it can branch on the zoom level and pass `defaultText` through for
|
|
315
|
+
* grains it doesn't override (no re-deriving the grain from the range). See
|
|
316
|
+
* {@link CursorFormat}. This is the independent readout channel;
|
|
317
|
+
* {@link timeFormat} owns the labels.
|
|
318
|
+
*/
|
|
319
|
+
cursorFormat?: CursorFormat;
|
|
299
320
|
/** Visual theme for all rows; defaults to {@link defaultTheme}. */
|
|
300
321
|
theme?: ChartTheme;
|
|
301
322
|
children?: ReactNode;
|
|
@@ -309,5 +330,5 @@ export interface ChartContainerProps {
|
|
|
309
330
|
* {@link TimeAxis} at the bottom, aligned under the plots. Y axes are per-row
|
|
310
331
|
* (`<YAxis>`).
|
|
311
332
|
*/
|
|
312
|
-
export declare function ChartContainer({ range, width, rowGap, showAxis, trackerPosition, onTrackerChanged, selected, onSelect, hovered, onHover, panZoom, onTimeRangeChange, minDuration, cursor, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime, crosshairSnap, editAnnotations, creating, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap, timeFormat, theme, discontinuities, calendar, spacing, grid, sessionDividers, children, }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
333
|
+
export declare function ChartContainer({ range, width, rowGap, showAxis, trackerPosition, onTrackerChanged, selected, onSelect, hovered, onHover, panZoom, onTimeRangeChange, minDuration, cursor, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime, crosshairSnap, editAnnotations, creating, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap, timeFormat, cursorFormat, theme, discontinuities, calendar, spacing, grid, sessionDividers, children, }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
313
334
|
//# sourceMappingURL=ChartContainer.d.ts.map
|
package/dist/ChartContainer.js
CHANGED
|
@@ -43,7 +43,7 @@ function normalizeRange(range) {
|
|
|
43
43
|
* {@link TimeAxis} at the bottom, aligned under the plots. Y axes are per-row
|
|
44
44
|
* (`<YAxis>`).
|
|
45
45
|
*/
|
|
46
|
-
export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trackerPosition, onTrackerChanged, selected, onSelect, hovered, onHover, panZoom = false, onTimeRangeChange, minDuration = 1, cursor = DEFAULT_CURSOR_MODE, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime = false, crosshairSnap = true, editAnnotations = false, creating = null, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap = true, timeFormat, theme, discontinuities, calendar, spacing, grid = true, sessionDividers = 'none', children, }) {
|
|
46
|
+
export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trackerPosition, onTrackerChanged, selected, onSelect, hovered, onHover, panZoom = false, onTimeRangeChange, minDuration = 1, cursor = DEFAULT_CURSOR_MODE, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime = false, crosshairSnap = true, editAnnotations = false, creating = null, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap = true, timeFormat, cursorFormat, theme, discontinuities, calendar, spacing, grid = true, sessionDividers = 'none', children, }) {
|
|
47
47
|
// The explicit base domain from `range` (a tuple or a TimeRange). `undefined`
|
|
48
48
|
// ⇒ auto-fit (resolved from the layers below). Pan/zoom seeds from it; `seed`
|
|
49
49
|
// is the placeholder while auto-fitting.
|
|
@@ -361,6 +361,30 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
361
361
|
formatTime: resolveAxisFormat(s, xTickCount, timeFormat),
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
|
+
// The cursor / marker / annotation **readout** formatter. `cursorFormat`
|
|
365
|
+
// wins (the independent readout channel — it never disqualifies the date
|
|
366
|
+
// style); else a container `timeFormat` (back-compat: shapes the readout
|
|
367
|
+
// too, and already opts labels out of the ladder); else the scale's
|
|
368
|
+
// **grain-aware** default (a day-or-coarser axis reads a date, not a
|
|
369
|
+
// time-of-day — the F-charts-7 `02 AM` fix), never d3's multi-scale default.
|
|
370
|
+
const timeReadout = (s) => {
|
|
371
|
+
// A `cursorFormat` **function** gets the axis's resolved coarse grain and
|
|
372
|
+
// the grain-aware default text per instant, so it can branch on zoom and
|
|
373
|
+
// pass the default through. A **string** formats uniformly (d3 specifier).
|
|
374
|
+
if (typeof cursorFormat === 'function') {
|
|
375
|
+
const grain = s.grain(xTickCount);
|
|
376
|
+
const def = s.readoutFormat(xTickCount);
|
|
377
|
+
return (v) => cursorFormat(v, { grain, defaultText: def(v) });
|
|
378
|
+
}
|
|
379
|
+
if (cursorFormat !== undefined) {
|
|
380
|
+
return resolveTimeFormat(s, xTickCount, cursorFormat);
|
|
381
|
+
}
|
|
382
|
+
// No cursorFormat: a container timeFormat still shapes the readout
|
|
383
|
+
// (back-compat); else the scale's grain-aware default.
|
|
384
|
+
return timeFormat !== undefined
|
|
385
|
+
? resolveTimeFormat(s, xTickCount, timeFormat)
|
|
386
|
+
: s.readoutFormat(xTickCount);
|
|
387
|
+
};
|
|
364
388
|
if (xDiscontinuities !== undefined) {
|
|
365
389
|
// Trading-time axis: closed-market gaps collapse, time proportional within
|
|
366
390
|
// sessions. Same tickFormat surface as scaleTime, so the readout is shared.
|
|
@@ -369,10 +393,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
369
393
|
const s = scaleTradingTime(xDiscontinuities)
|
|
370
394
|
.domain([d0, d1])
|
|
371
395
|
.range([0, plotWidth]);
|
|
372
|
-
return {
|
|
373
|
-
xScale: s,
|
|
374
|
-
formatTime: resolveTimeFormat(s, xTickCount, timeFormat),
|
|
375
|
-
};
|
|
396
|
+
return { xScale: s, formatTime: timeReadout(s) };
|
|
376
397
|
}
|
|
377
398
|
// Plain continuous time axis: the same trading-time scale over the
|
|
378
399
|
// gap-free identity provider, so it runs the same logical tick ladder
|
|
@@ -383,10 +404,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
383
404
|
const s = scaleTradingTime(identityProvider())
|
|
384
405
|
.domain([d0, d1])
|
|
385
406
|
.range([0, plotWidth]);
|
|
386
|
-
return {
|
|
387
|
-
xScale: s,
|
|
388
|
-
formatTime: resolveTimeFormat(s, xTickCount, timeFormat),
|
|
389
|
-
};
|
|
407
|
+
return { xScale: s, formatTime: timeReadout(s) };
|
|
390
408
|
}, [
|
|
391
409
|
resolvedKind,
|
|
392
410
|
categories,
|
|
@@ -394,6 +412,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
394
412
|
d1,
|
|
395
413
|
plotWidth,
|
|
396
414
|
timeFormat,
|
|
415
|
+
cursorFormat,
|
|
397
416
|
xDiscontinuities,
|
|
398
417
|
xTickCount,
|
|
399
418
|
]);
|
package/dist/format.d.ts
CHANGED
|
@@ -6,12 +6,32 @@
|
|
|
6
6
|
* uses for the ticks), a **function** is used verbatim, and `undefined` falls
|
|
7
7
|
* back to the scale's default `tickFormat`.
|
|
8
8
|
*/
|
|
9
|
+
import type { TimeGrain } from './tickLadder.js';
|
|
9
10
|
/**
|
|
10
11
|
* How to format an axis's values — a d3 [format specifier]
|
|
11
12
|
* (https://github.com/d3/d3-format#locale_format) string, or a custom
|
|
12
13
|
* `(value) => string` function. Omit for the scale's d3 default.
|
|
13
14
|
*/
|
|
14
15
|
export type AxisFormat = string | ((value: number) => string);
|
|
16
|
+
/**
|
|
17
|
+
* How to format the **cursor / marker readout** on a time axis
|
|
18
|
+
* ({@link ChartContainerProps.cursorFormat}). Either:
|
|
19
|
+
*
|
|
20
|
+
* - a d3 time specifier **string** (e.g. `'%b %-d'`) applied uniformly at every
|
|
21
|
+
* zoom; or
|
|
22
|
+
* - a **function** `(epochMs, ctx) => string`, where `ctx.grain` is the axis's
|
|
23
|
+
* resolved coarse {@link TimeGrain} (`year` … `second`) and `ctx.defaultText`
|
|
24
|
+
* is the library's grain-aware default readout for that instant — so a
|
|
25
|
+
* consumer can branch on the zoom level (`grain === 'year' ? … : …`) and
|
|
26
|
+
* pass `defaultText` through for the grains they don't want to override.
|
|
27
|
+
*
|
|
28
|
+
* The library hands you the grain because it already resolved it — you never
|
|
29
|
+
* re-derive it from the range.
|
|
30
|
+
*/
|
|
31
|
+
export type CursorFormat = string | ((epochMs: number, ctx: {
|
|
32
|
+
readonly grain: TimeGrain;
|
|
33
|
+
readonly defaultText: string;
|
|
34
|
+
}) => string);
|
|
15
35
|
/** The slice of a d3 scale {@link resolveAxisFormat} needs — `tickFormat` with an
|
|
16
36
|
* optional specifier. A d3 `ScaleLinear` / `ScaleTime` satisfies it. */
|
|
17
37
|
interface Tickable {
|
package/dist/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type { XAxisProps } from './XAxis.js';
|
|
|
31
31
|
export type { AxisTransform } from './derivedTicks.js';
|
|
32
32
|
export { TimeAxis } from './TimeAxis.js';
|
|
33
33
|
export { CategoryAxis } from './CategoryAxis.js';
|
|
34
|
-
export type { AxisFormat } from './format.js';
|
|
34
|
+
export type { AxisFormat, CursorFormat } from './format.js';
|
|
35
35
|
export { LineChart } from './LineChart.js';
|
|
36
36
|
export type { LineChartProps } from './LineChart.js';
|
|
37
37
|
export { BandChart } from './BandChart.js';
|
|
@@ -48,7 +48,7 @@ export { Candlestick } from './Candlestick.js';
|
|
|
48
48
|
export type { CandlestickProps } from './Candlestick.js';
|
|
49
49
|
export type { CandleVariant, ColorBy } from './ohlc.js';
|
|
50
50
|
export { scaleTradingTime } from './tradingTimeScale.js';
|
|
51
|
-
export type { TradingTimeScale, DiscontinuityProvider, } from './tradingTimeScale.js';
|
|
51
|
+
export type { TradingTimeScale, DiscontinuityProvider, TimeGrain, } from './tradingTimeScale.js';
|
|
52
52
|
export { scaleBand } from './bandScale.js';
|
|
53
53
|
export type { ScaleBand } from './bandScale.js';
|
|
54
54
|
export { Region, Baseline, Marker } from './annotations.js';
|
package/dist/tickLadder.d.ts
CHANGED
|
@@ -24,6 +24,16 @@ import type { DiscontinuityProvider } from './tradingTimeScale.js';
|
|
|
24
24
|
*/
|
|
25
25
|
/** The calendar grain a run of tick anchors is bucketed to. */
|
|
26
26
|
export type TickGranularity = 'second1' | 'second5' | 'second15' | 'second30' | 'minute1' | 'minute5' | 'minute15' | 'minute30' | 'hour1' | 'hour3' | 'hour6' | 'hour12' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
27
|
+
/**
|
|
28
|
+
* The **coarse calendar unit** of a resolved grain — the public grain a
|
|
29
|
+
* `cursorFormat` callback branches on, with the internal 1/5/15/30 strides
|
|
30
|
+
* collapsed to their unit (`hour1|3|6|12` → `hour`). Stable across zoom steps
|
|
31
|
+
* within a unit, so a consumer's `grain === 'hour'` check doesn't churn as the
|
|
32
|
+
* stride changes.
|
|
33
|
+
*/
|
|
34
|
+
export type TimeGrain = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
|
|
35
|
+
/** Collapse a {@link TickGranularity} to its coarse {@link TimeGrain} unit. */
|
|
36
|
+
export declare function coarseUnitOf(g: TickGranularity): TimeGrain;
|
|
27
37
|
/**
|
|
28
38
|
* The local-time bucket key for `t` at grain `g` — two instants in the same
|
|
29
39
|
* day / week / month / quarter / year share a key. Local time (not UTC) so it
|
|
@@ -143,6 +153,16 @@ export declare function boundaryFormatFor(g: TickGranularity): string;
|
|
|
143
153
|
* the next coarser unit — sub-day → day, day/week → month, month/quarter →
|
|
144
154
|
* year, year → none. */
|
|
145
155
|
export declare function bandGrainFor(g: TickGranularity): TickGranularity | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* d3 specifier for the **cursor / marker readout** at grain `g` — a hovered
|
|
158
|
+
* instant formatted at the axis's own granularity, never finer. A day-or-coarser
|
|
159
|
+
* axis reads a **date** (no time-of-day), so a daily bar at a foreign-tz
|
|
160
|
+
* midnight can't render as `02 AM`; a sub-day axis reads date **+** clock. This
|
|
161
|
+
* is the grain-aware default that replaces d3's multi-scale default for the
|
|
162
|
+
* readout (a `cursorFormat` override, when given, wins over it). Unambiguous by
|
|
163
|
+
* design — the readout carries the year / date the terse tick labels omit.
|
|
164
|
+
*/
|
|
165
|
+
export declare function readoutFormatFor(g: TickGranularity): string;
|
|
146
166
|
/** d3 specifier for a **band** label at band grain `g`: the date for a day band
|
|
147
167
|
* (`Jan 12`), the full month for a month band (`January`), the year for a year
|
|
148
168
|
* band (`2031`). Left-aligned in the band by the renderer. */
|
package/dist/tickLadder.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/** Collapse a {@link TickGranularity} to its coarse {@link TimeGrain} unit. */
|
|
2
|
+
export function coarseUnitOf(g) {
|
|
3
|
+
if (g.startsWith('second'))
|
|
4
|
+
return 'second';
|
|
5
|
+
if (g.startsWith('minute'))
|
|
6
|
+
return 'minute';
|
|
7
|
+
if (g.startsWith('hour'))
|
|
8
|
+
return 'hour';
|
|
9
|
+
return g; // 'day' | 'week' | 'month' | 'quarter' | 'year'
|
|
10
|
+
}
|
|
1
11
|
const SEC_MS = 1_000;
|
|
2
12
|
const MIN_MS = 60_000;
|
|
3
13
|
const HOUR_MS = 3_600_000;
|
|
@@ -651,6 +661,34 @@ export function bandGrainFor(g) {
|
|
|
651
661
|
return undefined; // year — no coarser band
|
|
652
662
|
}
|
|
653
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* d3 specifier for the **cursor / marker readout** at grain `g` — a hovered
|
|
666
|
+
* instant formatted at the axis's own granularity, never finer. A day-or-coarser
|
|
667
|
+
* axis reads a **date** (no time-of-day), so a daily bar at a foreign-tz
|
|
668
|
+
* midnight can't render as `02 AM`; a sub-day axis reads date **+** clock. This
|
|
669
|
+
* is the grain-aware default that replaces d3's multi-scale default for the
|
|
670
|
+
* readout (a `cursorFormat` override, when given, wins over it). Unambiguous by
|
|
671
|
+
* design — the readout carries the year / date the terse tick labels omit.
|
|
672
|
+
*/
|
|
673
|
+
export function readoutFormatFor(g) {
|
|
674
|
+
if (g === 'second1' ||
|
|
675
|
+
g === 'second5' ||
|
|
676
|
+
g === 'second15' ||
|
|
677
|
+
g === 'second30')
|
|
678
|
+
return '%b %-d, %H:%M:%S';
|
|
679
|
+
if (isSubDay(g))
|
|
680
|
+
return '%b %-d, %H:%M';
|
|
681
|
+
switch (g) {
|
|
682
|
+
case 'day':
|
|
683
|
+
case 'week':
|
|
684
|
+
return '%b %-d, %Y';
|
|
685
|
+
case 'month':
|
|
686
|
+
case 'quarter':
|
|
687
|
+
return '%b %Y';
|
|
688
|
+
default:
|
|
689
|
+
return '%Y'; // year
|
|
690
|
+
}
|
|
691
|
+
}
|
|
654
692
|
/** d3 specifier for a **band** label at band grain `g`: the date for a day band
|
|
655
693
|
* (`Jan 12`), the full month for a month band (`January`), the year for a year
|
|
656
694
|
* band (`2031`). Left-aligned in the band by the renderer. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TickGranularity } from './tickLadder.js';
|
|
1
|
+
import { type TickGranularity, type TimeGrain } from './tickLadder.js';
|
|
2
2
|
/**
|
|
3
3
|
* The structural discontinuity-provider surface `scaleTradingTime` consumes to
|
|
4
4
|
* collapse closed-market time. Charts declares this **shape** itself and never
|
|
@@ -59,7 +59,8 @@ export interface TradingCalendarLike {
|
|
|
59
59
|
* month grain) and returns the finest rung that fits `count`, and
|
|
60
60
|
* `.tickFormat` labels each anchor at that grain (`%H:%M` for hours, `%b %d`
|
|
61
61
|
* for days/weeks, `%b` for months/quarters, `%Y` for years) while formatting
|
|
62
|
-
* any other instant
|
|
62
|
+
* any other instant with the d3 multi-scale default (the cursor readout uses
|
|
63
|
+
* `.readoutFormat`, a grain-aware date/clock format, not this). The
|
|
63
64
|
* coarser context a label drops lives on `.tickBoundaries` — the second-row
|
|
64
65
|
* boundary labels of the **stacked** date style (the date over a clock axis,
|
|
65
66
|
* the year over a day / week / month axis), one per boundary crossing plus the
|
|
@@ -97,10 +98,10 @@ export interface TradingTimeScale {
|
|
|
97
98
|
* base label (bare day-of-month, month abbrev, clock time) otherwise — the
|
|
98
99
|
* TradingView default axis, the alternative to the two-row {@link tickFormat}
|
|
99
100
|
* + {@link tickBoundaries} stack. Same grain selection as {@link ticks} at the
|
|
100
|
-
* same `count`, so the labels sit on the tick instants. A non-tick value
|
|
101
|
-
*
|
|
102
|
-
* {@link
|
|
103
|
-
*
|
|
101
|
+
* same `count`, so the labels sit on the tick instants. A non-tick value
|
|
102
|
+
* formats with the d3 multi-scale default, like {@link tickFormat}; the
|
|
103
|
+
* cursor readout itself uses {@link readoutFormat} (grain-aware), so ticks
|
|
104
|
+
* read terse while the crosshair reads an unambiguous date/clock.
|
|
104
105
|
*/
|
|
105
106
|
flatFormat(count?: number): (value: number) => string;
|
|
106
107
|
/**
|
|
@@ -147,6 +148,24 @@ export interface TradingTimeScale {
|
|
|
147
148
|
* band row ({@link bands}) instead of inline.
|
|
148
149
|
*/
|
|
149
150
|
baseFormat(count?: number): (value: number) => string;
|
|
151
|
+
/**
|
|
152
|
+
* The **cursor / marker readout** formatter — a hovered instant formatted at
|
|
153
|
+
* the axis's own grain, never finer (a day-or-coarser axis reads a **date**,
|
|
154
|
+
* so a daily bar at a foreign-tz midnight never renders as a time-of-day like
|
|
155
|
+
* `02 AM`; a sub-day axis reads date **+** clock). This is the grain-aware
|
|
156
|
+
* default the crosshair pill, marker indicators, and annotation auto-labels
|
|
157
|
+
* use — replacing d3's multi-scale default, which showed local time-of-day
|
|
158
|
+
* for any off-local-midnight instant. A container `cursorFormat` overrides it.
|
|
159
|
+
*/
|
|
160
|
+
readoutFormat(count?: number): (value: number) => string;
|
|
161
|
+
/**
|
|
162
|
+
* The axis's resolved **coarse grain** ({@link TimeGrain}) at `count` — the
|
|
163
|
+
* unit the ticks currently sit on (`year` … `second`), strides collapsed. A
|
|
164
|
+
* `cursorFormat` callback receives this so it can branch on the zoom level
|
|
165
|
+
* (return a year when zoomed out, a clock when zoomed in) without
|
|
166
|
+
* re-deriving the ladder the axis already ran.
|
|
167
|
+
*/
|
|
168
|
+
grain(count?: number): TimeGrain;
|
|
150
169
|
/**
|
|
151
170
|
* The **date bands** — the segmented second row of the stacked style. One
|
|
152
171
|
* entry per next-coarser calendar period touching the domain (day bands
|
|
@@ -171,7 +190,7 @@ export interface TradingTimeScale {
|
|
|
171
190
|
copy(): TradingTimeScale;
|
|
172
191
|
}
|
|
173
192
|
export { coarsenCalendar } from './tickLadder.js';
|
|
174
|
-
export type { TickGranularity } from './tickLadder.js';
|
|
193
|
+
export type { TickGranularity, TimeGrain } from './tickLadder.js';
|
|
175
194
|
/**
|
|
176
195
|
* The trivial gap-free {@link DiscontinuityProvider}: live time **is** wall
|
|
177
196
|
* time, and every local midnight is a "session open". Backing a plain
|
package/dist/tradingTimeScale.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { scaleTime } from 'd3-scale';
|
|
2
|
-
import { bandFormatFor, bandGrainFor, bandNext, bandShaded, bandStartOf, boundaryFormatFor, boundaryGrainFor, boundaryTicks, buildGridLevels, buildTicks, flatBaseFormatFor, flatFormats, majorFormatFor, nominalStepMs, } from './tickLadder.js';
|
|
2
|
+
import { bandFormatFor, bandGrainFor, bandNext, bandShaded, bandStartOf, boundaryFormatFor, boundaryGrainFor, boundaryTicks, buildGridLevels, buildTicks, coarseUnitOf, flatBaseFormatFor, flatFormats, majorFormatFor, nominalStepMs, readoutFormatFor, } from './tickLadder.js';
|
|
3
3
|
// Grain selection lives in `tickLadder.ts` (the full hour1…year ladder plus
|
|
4
4
|
// the boundary-row helpers); re-exported here so existing imports keep working.
|
|
5
5
|
export { coarsenCalendar } from './tickLadder.js';
|
|
@@ -140,9 +140,9 @@ export function scaleTradingTime(provider) {
|
|
|
140
140
|
return (value) => labelled.get(value);
|
|
141
141
|
};
|
|
142
142
|
scale.flatFormat = (count = 10) => {
|
|
143
|
-
// A non-tick instant
|
|
144
|
-
//
|
|
145
|
-
//
|
|
143
|
+
// A non-tick instant always uses the d3 multi-scale default — same as
|
|
144
|
+
// tickFormat. (The cursor readout doesn't route through here; it uses
|
|
145
|
+
// readoutFormat, grain-aware.) Without a calendar there are no ladder
|
|
146
146
|
// anchors, so every value falls through to the default.
|
|
147
147
|
const defFmt = base.tickFormat(count);
|
|
148
148
|
if (!hasCalendar())
|
|
@@ -181,6 +181,14 @@ export function scaleTradingTime(provider) {
|
|
|
181
181
|
const terse = base.tickFormat(count, flatBaseFormatFor(granularity));
|
|
182
182
|
return (value) => anchors.has(value) ? terse(new Date(value)) : defFmt(new Date(value));
|
|
183
183
|
};
|
|
184
|
+
scale.readoutFormat = (count = 10) => {
|
|
185
|
+
const defFmt = base.tickFormat(count);
|
|
186
|
+
if (!hasCalendar())
|
|
187
|
+
return (value) => defFmt(new Date(value));
|
|
188
|
+
const fmt = base.tickFormat(count, readoutFormatFor(resolved(count).granularity));
|
|
189
|
+
return (value) => fmt(new Date(value));
|
|
190
|
+
};
|
|
191
|
+
scale.grain = (count = 10) => hasCalendar() ? coarseUnitOf(resolved(count).granularity) : 'day';
|
|
184
192
|
scale.bands = (count = 10) => {
|
|
185
193
|
if (!hasCalendar())
|
|
186
194
|
return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pond-ts/charts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Canvas-rendered, streaming-first time-series charts for pond-ts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"perf": "PERF_BENCH=1 playwright test perf.spec.ts --workers=1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@pond-ts/react": "^0.
|
|
42
|
-
"pond-ts": "^0.
|
|
41
|
+
"@pond-ts/react": "^0.48.0",
|
|
42
|
+
"pond-ts": "^0.48.0",
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|