@pond-ts/charts 0.53.0 → 0.53.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 +37 -1
- package/dist/ChartContainer.js +10 -1
- package/dist/XAxis.js +13 -1
- package/dist/context.d.ts +11 -0
- package/dist/elapsed.js +43 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,8 @@ 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.53.
|
|
11
|
+
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.53.1...HEAD
|
|
12
|
+
[0.53.1]: https://github.com/pond-ts/pond/compare/v0.53.0...v0.53.1
|
|
12
13
|
[0.53.0]: https://github.com/pond-ts/pond/compare/v0.52.0...v0.53.0
|
|
13
14
|
[0.52.0]: https://github.com/pond-ts/pond/compare/v0.51.0...v0.52.0
|
|
14
15
|
[0.51.0]: https://github.com/pond-ts/pond/compare/v0.50.0...v0.51.0
|
|
@@ -53,6 +54,41 @@ and type-level changes; patch bumps are strictly additive.
|
|
|
53
54
|
|
|
54
55
|
## [Unreleased]
|
|
55
56
|
|
|
57
|
+
## [0.53.1] — 2026-07-25
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **charts:** **An `<XAxis format>` again owns its own cursor / marker pills**
|
|
62
|
+
under a container with `origin` set. The elapsed axis supplies a _default_
|
|
63
|
+
finer readout (`00:05:12` under `00:05` ticks), but it was being delivered
|
|
64
|
+
through the same frame field as an explicit `cursorFormat` — so it outranked
|
|
65
|
+
an axis-level `format`, inverting the documented pill precedence
|
|
66
|
+
(`cursorFormat → axis format → container`). The visible symptom was the
|
|
67
|
+
two-strip pattern the docs recommend: a wall-clock strip declared as
|
|
68
|
+
`<XAxis format="%H:%M">` labelled its ticks `11:33` and pilled them
|
|
69
|
+
`00:05:12`. A real `cursorFormat` still outranks an axis `format`, unchanged.
|
|
70
|
+
A container **`timeFormat`** was inverted the same way one rung down — its
|
|
71
|
+
documented back-compat is to shape the readout when no `cursorFormat` is set,
|
|
72
|
+
and the elapsed default was overruling it. Fixed with the same precedence.
|
|
73
|
+
|
|
74
|
+
- **charts:** **A duration axis no longer stacks ticks on one pixel across a
|
|
75
|
+
collapsed session.** The duration ladder strides in wall-clock time, so on a
|
|
76
|
+
trading axis several ticks could land inside closed time — where the scale
|
|
77
|
+
maps all of them to the same seam pixel, stroking labels over labels and
|
|
78
|
+
gridlines over gridlines. Coinciding ticks are now dropped, so a seam shows
|
|
79
|
+
one label rather than four — and the one kept is the **last** of the group,
|
|
80
|
+
the session open that genuinely sits on that pixel (`1d 00:00`), rather than
|
|
81
|
+
the first, which falls inside the collapsed night (`12:00` = 21:30, market
|
|
82
|
+
shut). Continuous axes are unaffected (their ticks are tens of pixels apart
|
|
83
|
+
by construction).
|
|
84
|
+
|
|
85
|
+
Both found by an adversarial review of the v0.53.0 duration axis
|
|
86
|
+
([#540](https://github.com/pond-ts/pond/issues/540)), which also corrected the
|
|
87
|
+
duration-axis docs: the trading-calendar caveat described uneven spacing where
|
|
88
|
+
the real behaviour is thinning around seams, a far-off `origin` (`origin={0}`
|
|
89
|
+
on a 2026 axis ⇒ `20468d 10:00` on every tick) was undocumented, and one row of
|
|
90
|
+
the label-shape table quoted a sample spacing where it meant a tick step.
|
|
91
|
+
|
|
56
92
|
## [0.53.0] — 2026-07-25
|
|
57
93
|
|
|
58
94
|
### Changed
|
package/dist/ChartContainer.js
CHANGED
|
@@ -510,7 +510,14 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
510
510
|
})
|
|
511
511
|
: cursorFormat !== undefined
|
|
512
512
|
? resolveTimeFormat(e, xTickCount, cursorFormat)
|
|
513
|
-
:
|
|
513
|
+
: // A container `timeFormat` shapes the readout too when no
|
|
514
|
+
// `cursorFormat` is set — its documented back-compat behaviour,
|
|
515
|
+
// and the same inversion as the `<XAxis format>` one a rung down:
|
|
516
|
+
// a custom format that owns the labels must own the pill rather
|
|
517
|
+
// than watch the elapsed default overrule it (PR #541 review).
|
|
518
|
+
timeFormat !== undefined
|
|
519
|
+
? labels
|
|
520
|
+
: fine;
|
|
514
521
|
return { xScale: e, formatTime: labels, formatReadout: readout };
|
|
515
522
|
};
|
|
516
523
|
if (xDiscontinuities !== undefined) {
|
|
@@ -688,6 +695,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
688
695
|
formatTime,
|
|
689
696
|
formatReadout,
|
|
690
697
|
xFormatCustom: timeFormat !== undefined,
|
|
698
|
+
xReadoutCustom: cursorFormat !== undefined,
|
|
691
699
|
xTickCount,
|
|
692
700
|
registerTrackerSource,
|
|
693
701
|
unregisterTrackerSource,
|
|
@@ -749,6 +757,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
749
757
|
formatTime,
|
|
750
758
|
formatReadout,
|
|
751
759
|
timeFormat,
|
|
760
|
+
cursorFormat,
|
|
752
761
|
xTickCount,
|
|
753
762
|
registerTrackerSource,
|
|
754
763
|
unregisterTrackerSource,
|
package/dist/XAxis.js
CHANGED
|
@@ -165,7 +165,19 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
165
165
|
// own channel, so precise-pill-over-terse-ticks works on any axis kind. A
|
|
166
166
|
// `transform`ed axis is exempt — its pill speaks the derived unit, which a
|
|
167
167
|
// data-unit `cursorFormat` can't address.
|
|
168
|
-
|
|
168
|
+
//
|
|
169
|
+
// The middle rung is why `xReadoutCustom` exists: `formatReadout` carries
|
|
170
|
+
// either a real `cursorFormat` (which outranks this axis's `format`) or the
|
|
171
|
+
// axis kind's own default readout (which does not — an elapsed container's
|
|
172
|
+
// finer `00:05:12`). Reading the field alone can't tell them apart, so a
|
|
173
|
+
// wall-clock strip declared as `<XAxis format="%H:%M">` under an elapsed
|
|
174
|
+
// container used to label its ticks `11:33` and pill them `00:05:12`
|
|
175
|
+
// (issue #540).
|
|
176
|
+
const readoutFmt = transform !== undefined
|
|
177
|
+
? fmt
|
|
178
|
+
: format !== undefined && !container.xReadoutCustom
|
|
179
|
+
? fmt
|
|
180
|
+
: (container.formatReadout ?? fmt);
|
|
169
181
|
// Marker annotations that opted into an axis indicator (`<Marker indicator>`)
|
|
170
182
|
// pin their **time** to this shared x-axis — a pill at `at`, in the annotation
|
|
171
183
|
// colour, reading like a tick. An indicator always shows the axis coordinate
|
package/dist/context.d.ts
CHANGED
|
@@ -163,6 +163,17 @@ export interface ContainerFrame {
|
|
|
163
163
|
* x axis suppresses its boundary (second) label row when it's set — a
|
|
164
164
|
* custom format owns the whole label, so the ladder mustn't second-line it. */
|
|
165
165
|
readonly xFormatCustom: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Whether an explicit container `cursorFormat` shaped {@link formatReadout} —
|
|
168
|
+
* as opposed to the axis kind supplying its own default readout (the elapsed
|
|
169
|
+
* axis's finer duration). The two are indistinguishable from the field alone,
|
|
170
|
+
* and `<XAxis>` must tell them apart to honour the documented pill precedence
|
|
171
|
+
* `cursorFormat → axis format → container`: a **`cursorFormat`** outranks an
|
|
172
|
+
* explicit `<XAxis format>`, a **default** does not. Without this the elapsed
|
|
173
|
+
* default silently occupied the `cursorFormat` slot and a wall-clock strip's
|
|
174
|
+
* pill read durations (issue #540, finding 2).
|
|
175
|
+
*/
|
|
176
|
+
readonly xReadoutCustom: boolean;
|
|
166
177
|
/**
|
|
167
178
|
* The shared **x-side tick count** — the `count` every x-side `ticks()` /
|
|
168
179
|
* `tickFormat()` call passes (`<XAxis>` labels, the canvas x gridlines and
|
package/dist/elapsed.js
CHANGED
|
@@ -94,6 +94,10 @@ export function durationStep(span, count) {
|
|
|
94
94
|
/** Backstop against a pathological (step, domain) pair flooding the axis; the
|
|
95
95
|
* step is derived from the domain span, so a real axis never comes close. */
|
|
96
96
|
const MAX_TICKS = 10_000;
|
|
97
|
+
/** Closest two ticks may sit in pixels before the second is dropped as a
|
|
98
|
+
* duplicate. Only ever fires where a discontinuous base scale collapses a span
|
|
99
|
+
* to a point — a continuous axis spaces its ticks tens of px apart. */
|
|
100
|
+
const MIN_TICK_PX = 1;
|
|
97
101
|
/**
|
|
98
102
|
* Tick values in **absolute axis units** at `origin + k·step`, covering
|
|
99
103
|
* `domain` — the anchored walk that makes `00:05` land exactly five minutes
|
|
@@ -214,6 +218,44 @@ export function scaleElapsed(base, options) {
|
|
|
214
218
|
: s.tickFormat(count);
|
|
215
219
|
return (v) => f(v - origin);
|
|
216
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* Drop ticks that land on a pixel another tick already claimed. The walk is in
|
|
223
|
+
* **wall-clock** ms, but the base scale need not be continuous: on a trading
|
|
224
|
+
* axis every instant inside a collapsed session maps to the one seam pixel, so
|
|
225
|
+
* a duration ladder striding through a closed market emits several ticks at
|
|
226
|
+
* the *same* x — labels stacked on labels, gridlines stroked on gridlines
|
|
227
|
+
* (issue #540, finding 1). A plain axis is untouched: its ticks are ~65px
|
|
228
|
+
* apart by construction, so nothing is ever within the gap.
|
|
229
|
+
*
|
|
230
|
+
* The survivor is the **last** tick of each pixel group, not the first. Ticks
|
|
231
|
+
* ascend, so a group spans a collapsed gap and ends at the first instant the
|
|
232
|
+
* axis actually draws — the session open. Keeping the first would label the
|
|
233
|
+
* seam with a moment the market was shut: on three 09:30–16:00 sessions the
|
|
234
|
+
* pixel would read `12:00` (21:30 that night) instead of `1d 00:00`, the
|
|
235
|
+
* Tuesday open that genuinely sits there (PR #541 review).
|
|
236
|
+
*/
|
|
237
|
+
const dedupeByPixel = (values) => {
|
|
238
|
+
const r = base.range();
|
|
239
|
+
// Pre-layout the whole range is one pixel wide; nothing is visible, and
|
|
240
|
+
// deduping there would collapse the axis to a single tick.
|
|
241
|
+
if (!(Math.abs(Number(r[1] ?? 0) - Number(r[0] ?? 0)) > 0))
|
|
242
|
+
return [...values];
|
|
243
|
+
const out = [];
|
|
244
|
+
let lastPx = 0;
|
|
245
|
+
for (const v of values) {
|
|
246
|
+
const px = base(v);
|
|
247
|
+
if (!Number.isFinite(px))
|
|
248
|
+
continue;
|
|
249
|
+
if (out.length > 0 && Math.abs(px - lastPx) < MIN_TICK_PX) {
|
|
250
|
+
out[out.length - 1] = v; // same pixel — the later instant wins
|
|
251
|
+
lastPx = px;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
out.push(v);
|
|
255
|
+
lastPx = px;
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
};
|
|
217
259
|
const scale = ((value) => base(value));
|
|
218
260
|
Object.assign(scale, {
|
|
219
261
|
origin,
|
|
@@ -223,7 +265,7 @@ export function scaleElapsed(base, options) {
|
|
|
223
265
|
const r = base.range();
|
|
224
266
|
return [Number(r[0] ?? 0), Number(r[1] ?? 0)];
|
|
225
267
|
},
|
|
226
|
-
ticks: (count = DEFAULT_COUNT) => originTicks(bounds(), origin, stepFor(count)),
|
|
268
|
+
ticks: (count = DEFAULT_COUNT) => dedupeByPixel(originTicks(bounds(), origin, stepFor(count))),
|
|
227
269
|
tickFormat: (count = DEFAULT_COUNT, specifier) => {
|
|
228
270
|
if (kind === 'value')
|
|
229
271
|
return offsetFormat(count, specifier);
|