@pond-ts/charts 0.44.1 → 0.46.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 +143 -1
- package/dist/ChartContainer.js +19 -8
- package/dist/LineChart.d.ts +15 -1
- package/dist/LineChart.js +17 -2
- package/dist/XAxis.d.ts +27 -1
- package/dist/XAxis.js +138 -21
- package/dist/YAxis.d.ts +9 -1
- package/dist/YAxis.js +4 -4
- package/dist/annotations.d.ts +1 -1
- package/dist/annotations.js +17 -6
- package/dist/context.d.ts +4 -0
- package/dist/derivedTicks.d.ts +36 -0
- package/dist/derivedTicks.js +92 -0
- package/dist/index.d.ts +1 -0
- package/dist/line.d.ts +25 -1
- package/dist/line.js +78 -13
- package/dist/tickLadder.d.ts +92 -0
- package/dist/tickLadder.js +286 -0
- package/dist/tradingTimeScale.d.ts +38 -26
- package/dist/tradingTimeScale.js +90 -88
- 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/pjm17971/pond-ts/compare/v0.
|
|
11
|
+
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.46.0...HEAD
|
|
12
|
+
[0.46.0]: https://github.com/pjm17971/pond-ts/compare/v0.45.0...v0.46.0
|
|
13
|
+
[0.45.0]: https://github.com/pjm17971/pond-ts/compare/v0.44.1...v0.45.0
|
|
12
14
|
[0.44.1]: https://github.com/pjm17971/pond-ts/compare/v0.44.0...v0.44.1
|
|
13
15
|
[0.44.0]: https://github.com/pjm17971/pond-ts/compare/v0.43.0...v0.44.0
|
|
14
16
|
[0.43.0]: https://github.com/pjm17971/pond-ts/compare/v0.42.0...v0.43.0
|
|
@@ -43,6 +45,146 @@ and type-level changes; patch bumps are strictly additive.
|
|
|
43
45
|
|
|
44
46
|
## [Unreleased]
|
|
45
47
|
|
|
48
|
+
## [0.46.0] — 2026-07-14
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **charts:** the boundary (second-row) axis label's **context** now pins to
|
|
53
|
+
the plot's left edge instead of riding the first tick: it shows the period
|
|
54
|
+
the *domain start* is in, and a crossing label sliding toward the edge
|
|
55
|
+
pushes it off (the sticky-header behavior). On a live sliding window the
|
|
56
|
+
old first-tick anchoring made `Jan 01` hop tick-to-tick as ticks scrolled
|
|
57
|
+
out; pinned, it stays put until the period actually changes. Crossing
|
|
58
|
+
labels (day/year turns) still ride their ticks — including a first tick
|
|
59
|
+
whose period differs from the domain start's. `TradingTimeScale` gains
|
|
60
|
+
`boundaryContext(count)`; `tickBoundaries` now labels crossings only.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- **charts:** a live (sliding-window) time axis no longer flickers between two
|
|
65
|
+
tick grains: the clock-rung choice now derives from the window's live span
|
|
66
|
+
(constant while sliding) instead of the enumerated anchor count, which
|
|
67
|
+
oscillates ±1 with the window's phase and flipped the grain for single
|
|
68
|
+
frames whenever it sat exactly at the width-derived cap.
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- **charts:** **dual x-axes** — two tick layouts on one shared scale. A second
|
|
73
|
+
`<XAxis>` stacks by declaration order (above/below the plot, either side,
|
|
74
|
+
same side twice); the new **`transform`** prop (`{ to, from }`, exported
|
|
75
|
+
`AxisTransform`) relabels an axis into a derived unit: strike ↔ moneyness on
|
|
76
|
+
a top axis, or a nonlinear BS-delta strip under a std-moneyness chart. Ticks
|
|
77
|
+
are nice derived-unit values chosen by a pixel-aware multi-resolution fill
|
|
78
|
+
(1-2-5 steps, coarsest first, admitted where they keep room), so a span the
|
|
79
|
+
transform compresses gets coarse ticks and a stretched span picks up finer
|
|
80
|
+
ones — and a label-honesty filter drops any tick whose formatted label would
|
|
81
|
+
lie about its position. Gridlines stay on the container's primary ticks; the
|
|
82
|
+
cursor pill on a transformed axis reads in the derived unit. Stories under
|
|
83
|
+
`Charts/Axes/DualX`. Each `<XAxis>` **and `<YAxis>`** also takes a
|
|
84
|
+
per-instance **`color`** (labels, tick marks, rule, title) — the lever that
|
|
85
|
+
distinguishes stacked x strips (a blue delta strip under a grey primary) and
|
|
86
|
+
colours a y axis to match its series (the dual-axis convention).
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- **charts:** annotation label chips now clip to the plot: a marker whose pole
|
|
91
|
+
pans off-plot no longer leaves its chip floating in the axis gutter, and a
|
|
92
|
+
partially visible region's chip clamps to the plot's left edge (culled only
|
|
93
|
+
when the region is entirely out of view). The lines/fills were already
|
|
94
|
+
SVG-clipped — only the DOM chips escaped.
|
|
95
|
+
|
|
96
|
+
## [0.45.0] — 2026-07-14
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- **charts:** the time axis now walks a **logical tick ladder** — clock/calendar
|
|
101
|
+
units (1s…30s, 1m…30m, 1H…12H, day / week / month / quarter / year), picking
|
|
102
|
+
the finest grain that fits the width-derived cap — and renders **two-tier labels**:
|
|
103
|
+
a first row at the tick grain (`14:00`, `Feb 02`, `Feb`, `2026`) plus a
|
|
104
|
+
**boundary row** carrying the coarser context the first row omits (the date
|
|
105
|
+
under clock ticks, the year under day/week/month ticks — never a unit the
|
|
106
|
+
first row already shows), shown once under
|
|
107
|
+
the first tick of each new period. Hour anchors are generated in **live**
|
|
108
|
+
trading time, so none lands in a collapsed gap, an early close, or a lunch
|
|
109
|
+
break. `TradingTimeScale` gains `tickBoundaries(count)`; a cramped leading
|
|
110
|
+
partial-period anchor (the `"Jun 23Jul 07"` pile-up) is dropped. Systematic
|
|
111
|
+
story matrix under `Charts/TimeAxisTicks`.
|
|
112
|
+
- **financial:** the rest of the first study batch — `rollingStdev`,
|
|
113
|
+
`rollingMin`, `rollingMax`, `rollingPercentile` (linear interpolation),
|
|
114
|
+
`zScore` (rolling), `envelope` (MA ± percent, `maType` sma/ema), and
|
|
115
|
+
`percentChange` (n-bar rate of change). Same shape as the others — `column` /
|
|
116
|
+
`output` (or `prefix`) on every one, bar-count periods, length-preserving
|
|
117
|
+
warm-up — and each has a fluent method (`bars.zScore({ period: 20 })`) and a
|
|
118
|
+
**pandas oracle** case (values cross-validated bar-for-bar). This completes the
|
|
119
|
+
#449 first batch.
|
|
120
|
+
- **financial:** opt-in **fluent studies** via `import '@pond-ts/financial/fluent'`
|
|
121
|
+
— mounts `sma` / `ema` / `bollinger` as chainable `TimeSeries` methods so
|
|
122
|
+
composition reads like the core operators beside it:
|
|
123
|
+
`bars.sma({ period: 20 }).ema({ period: 12 }).bollinger({ period: 20 })`.
|
|
124
|
+
Opt-in by import (the default entry leaves `TimeSeries` untouched, so a
|
|
125
|
+
non-financial series never sees `.sma()`); the methods are exactly the
|
|
126
|
+
standalone functions bound to `this`, fully typed (each appends its column to
|
|
127
|
+
the schema). Same prototype-augmentation pattern core uses for column methods.
|
|
128
|
+
- **financial:** `@pond-ts/financial` gains its first **studies** — `sma`, `ema`,
|
|
129
|
+
and `bollinger` (Bollinger Bands®) — pure functions that append a column (or a
|
|
130
|
+
`${prefix}Middle/Upper/Lower` family) to a bar series. Every study takes a
|
|
131
|
+
`column` source (default `'close'`) and an `output` name, so a study runs over
|
|
132
|
+
any numeric column including another study's output; periods are **bar counts**
|
|
133
|
+
(on core's count window, gap-correct); warm-up rows are `undefined`
|
|
134
|
+
(length-preserving). Plus the `OhlcvColumns` contract + `DEFAULT_OHLCV`. (Core
|
|
135
|
+
`AppendColumn` is now exported so study return types name their appended
|
|
136
|
+
column.)
|
|
137
|
+
- **core:** `smooth(col, 'ema', …)` gains the financial **`span`** rate
|
|
138
|
+
convention (`α = 2/(span+1)`, e.g. `{ span: 12 }` for a 12-period EMA) as an
|
|
139
|
+
alternative to `alpha` (exactly one required), and a length-preserving
|
|
140
|
+
**`minSamples`** warm-up that emits `undefined` for the first `N` present
|
|
141
|
+
values while **keeping the row count** — mirroring `rolling`'s `minSamples`,
|
|
142
|
+
the one warm-up convention studies use so a smoothed line aligns on its
|
|
143
|
+
source's time axis. (The existing `warmup` option is unchanged — it still
|
|
144
|
+
*drops* the head rows; `minSamples` is the length-preserving counterpart.)
|
|
145
|
+
- **core:** `TimeSeries.rolling` accepts a **count-based** window —
|
|
146
|
+
`rolling({ count: N }, mapping, opts?)` reduces the last / next / centered
|
|
147
|
+
`N` _rows_ (bars) by position instead of a time span. Unlike a duration
|
|
148
|
+
window, an N-bar window stays correct across session gaps (weekends,
|
|
149
|
+
overnight) where `N` bars do not equal `N × barSize` of time — the load-
|
|
150
|
+
bearing primitive for financial N-bar studies (SMA-20, Bollinger-20, rolling
|
|
151
|
+
stdev). Honours `alignment` and `minSamples` exactly like a duration window
|
|
152
|
+
(`minSamples: N` gives the conventional first-`N-1`-rows-`undefined` warmup);
|
|
153
|
+
per-row output only (not supported with a sequence). Same amortized-O(1)-per-
|
|
154
|
+
row sweep as the duration path.
|
|
155
|
+
- **charts:** `<LineChart sessionBreaks>` breaks the line at each trading-axis
|
|
156
|
+
discontinuity (a session / day / lunch close→open) instead of connecting the
|
|
157
|
+
prior close straight to the next open across the collapsed gap. For intraday
|
|
158
|
+
data on a `discontinuities` / `calendar` axis this gives the terminal look —
|
|
159
|
+
the line ends at the close and re-starts at the open, so one session's price
|
|
160
|
+
doesn't visually flow into the next. It's a **scale** break (driven by the
|
|
161
|
+
axis's collapsed gaps), orthogonal to `gaps` (a **data** break, a NaN run) —
|
|
162
|
+
set both independently. Default `false`; a no-op on a continuous axis or a
|
|
163
|
+
provider without `boundaries`. New `Charts/TradingTimeAxis/SessionBreaks`
|
|
164
|
+
story (connected vs broken) + `sessionRuns` / `gappingTicks` helpers.
|
|
165
|
+
|
|
166
|
+
### Changed
|
|
167
|
+
|
|
168
|
+
- **charts:** a **plain continuous time axis** now runs the same ladder as a
|
|
169
|
+
trading-calendar axis (via an internal gap-free identity provider) instead of
|
|
170
|
+
d3's multi-scale default — so a year of daily data ticks cleanly on month
|
|
171
|
+
starts (`Jul Aug … Jun` + the year underneath) rather than mixed
|
|
172
|
+
`"Jun 23" / "Sep" / "Dec"` labels, and its tick count is now width-derived
|
|
173
|
+
like the trading axis's. Month/quarter-grain anchor labels are now bare
|
|
174
|
+
months (`Feb`, was `Feb 02`) with the year on the boundary row;
|
|
175
|
+
`coarsenCalendar`'s finest granularity is renamed `'session'` → `'day'`.
|
|
176
|
+
|
|
177
|
+
### Fixed
|
|
178
|
+
|
|
179
|
+
- **core:** `TimeSeries.withColumn` now appends an **optional** column
|
|
180
|
+
(`required: false`) rather than a required one. It already accepted
|
|
181
|
+
`(number | undefined)[]`, but marking the column required meant a later
|
|
182
|
+
strict-intake rebuild (`smooth`, any row reconstruction) threw on the gaps —
|
|
183
|
+
so a column with a warm-up (a rolling study fed into an EMA, e.g.
|
|
184
|
+
`ema(sma(bars, …), …)`) crashed. The appended column's type is now
|
|
185
|
+
`number | undefined`, matching `smooth`'s optional append. (`OptionalNumberColumn`
|
|
186
|
+
is exported for the widened return type.)
|
|
187
|
+
|
|
46
188
|
## [0.44.1] — 2026-07-13
|
|
47
189
|
|
|
48
190
|
A `@pond-ts/charts` patch: fixes trading-time axis tick density. `pond-ts`,
|
package/dist/ChartContainer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
-
import { scaleLinear
|
|
4
|
-
import { scaleTradingTime, } from './tradingTimeScale.js';
|
|
3
|
+
import { scaleLinear } from 'd3-scale';
|
|
4
|
+
import { identityProvider, scaleTradingTime, } from './tradingTimeScale.js';
|
|
5
5
|
import { scaleBand } from './bandScale.js';
|
|
6
6
|
import { Sequence } from 'pond-ts';
|
|
7
7
|
import { ContainerContext, } from './context.js';
|
|
@@ -335,10 +335,11 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
335
335
|
// The shared x-side tick count — labels, x gridlines, session dividers, and
|
|
336
336
|
// `formatTime` all pass this one value, so they derive from the same instants
|
|
337
337
|
// (the alignment previously held by three hardcoded constants agreeing).
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
// pre-layout zero width still
|
|
341
|
-
|
|
338
|
+
// Time axis (trading or plain — both run the logical tick ladder):
|
|
339
|
+
// width-derived, since the ladder's `count` caps its calendar buckets rather
|
|
340
|
+
// than targeting a tick total; floored at 2 so a pre-layout zero width still
|
|
341
|
+
// requests a drawable tick set. Value/category axes keep the d3 target count.
|
|
342
|
+
const xTickCount = resolvedKind === 'time'
|
|
342
343
|
? Math.max(2, Math.floor(plotWidth / TRADING_TICK_PX))
|
|
343
344
|
: TIME_TICK_COUNT;
|
|
344
345
|
const { xScale, formatTime } = useMemo(() => {
|
|
@@ -373,7 +374,15 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
373
374
|
formatTime: resolveTimeFormat(s, xTickCount, timeFormat),
|
|
374
375
|
};
|
|
375
376
|
}
|
|
376
|
-
|
|
377
|
+
// Plain continuous time axis: the same trading-time scale over the
|
|
378
|
+
// gap-free identity provider, so it runs the same logical tick ladder
|
|
379
|
+
// (month starts over a year, clock-aligned hours over an afternoon) —
|
|
380
|
+
// never d3's mixed multi-scale default. Interactions stay on continuous
|
|
381
|
+
// time math: the frame's `discontinuities` remains undefined, and identity
|
|
382
|
+
// distance/offset are plain subtraction/addition anyway.
|
|
383
|
+
const s = scaleTradingTime(identityProvider())
|
|
384
|
+
.domain([d0, d1])
|
|
385
|
+
.range([0, plotWidth]);
|
|
377
386
|
return {
|
|
378
387
|
xScale: s,
|
|
379
388
|
formatTime: resolveTimeFormat(s, xTickCount, timeFormat),
|
|
@@ -461,7 +470,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
461
470
|
}, [cursorX, xScale, sources, plotWidth]);
|
|
462
471
|
// Pack overlapping top-flag labels (markers + regions) into stacked lanes so
|
|
463
472
|
// close-in-x labels don't collide; chips read their lane back off the frame.
|
|
464
|
-
const labelLanes = useMemo(() => computeLabelLanes(annotations, (v) => xScale(v), draggingKey), [annotations, xScale, draggingKey]);
|
|
473
|
+
const labelLanes = useMemo(() => computeLabelLanes(annotations, (v) => xScale(v), draggingKey, plotWidth), [annotations, xScale, draggingKey]);
|
|
465
474
|
const frame = useMemo(() => ({
|
|
466
475
|
timeRange: [d0, d1],
|
|
467
476
|
width,
|
|
@@ -499,6 +508,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
499
508
|
onHoverAnnotation,
|
|
500
509
|
onEditAnnotation,
|
|
501
510
|
formatTime,
|
|
511
|
+
xFormatCustom: timeFormat !== undefined,
|
|
502
512
|
xTickCount,
|
|
503
513
|
registerTrackerSource,
|
|
504
514
|
unregisterTrackerSource,
|
|
@@ -553,6 +563,7 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
553
563
|
onHoverAnnotation,
|
|
554
564
|
onEditAnnotation,
|
|
555
565
|
formatTime,
|
|
566
|
+
timeFormat,
|
|
556
567
|
xTickCount,
|
|
557
568
|
registerTrackerSource,
|
|
558
569
|
unregisterTrackerSource,
|
package/dist/LineChart.d.ts
CHANGED
|
@@ -49,6 +49,20 @@ export interface LineChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
49
49
|
* `gap.connectorOpacity`.)
|
|
50
50
|
*/
|
|
51
51
|
gaps?: GapMode;
|
|
52
|
+
/**
|
|
53
|
+
* Break the line at each **trading-axis discontinuity** (a session / day /
|
|
54
|
+
* lunch close→open) when the container renders on a trading-time axis (a
|
|
55
|
+
* `discontinuities` / `calendar` provider). **Omitted ⇒ `false`**: the line
|
|
56
|
+
* connects the last pre-close point straight to the next open across the
|
|
57
|
+
* collapsed gap (the near-vertical connector). `true` ends the line at the
|
|
58
|
+
* close and re-starts it at the open — the intraday look, where a session's
|
|
59
|
+
* price shouldn't visually flow into the next.
|
|
60
|
+
*
|
|
61
|
+
* This is a **scale** break (driven by the axis's collapsed gaps), orthogonal
|
|
62
|
+
* to {@link gaps} (a **data** break, a NaN run) — set both independently. A
|
|
63
|
+
* no-op on a continuous axis (no provider) or a provider without `boundaries`.
|
|
64
|
+
*/
|
|
65
|
+
sessionBreaks?: boolean;
|
|
52
66
|
/**
|
|
53
67
|
* @internal Declaration position among the `<Layers>` children, injected by
|
|
54
68
|
* `Layers` so z-order follows JSX order. Do not set.
|
|
@@ -61,5 +75,5 @@ export interface LineChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
61
75
|
* (scaling against its `axis`), and renders nothing to the DOM — the row draws
|
|
62
76
|
* it. The line breaks at gaps rather than spanning them.
|
|
63
77
|
*/
|
|
64
|
-
export declare function LineChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, as: semantic, axis, curve, gaps, index, }: LineChartProps<S, VS>): null;
|
|
78
|
+
export declare function LineChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, as: semantic, axis, curve, gaps, sessionBreaks, index, }: LineChartProps<S, VS>): null;
|
|
65
79
|
//# sourceMappingURL=LineChart.d.ts.map
|
package/dist/LineChart.js
CHANGED
|
@@ -6,13 +6,16 @@ import { resolveCurve } from './curve.js';
|
|
|
6
6
|
import { DEFAULT_GAP_MODE, DEFAULT_GAP_CONNECTOR_OPACITY, } from './gaps.js';
|
|
7
7
|
import { ContainerContext, LayersContext } from './context.js';
|
|
8
8
|
import { useSlotKey } from './use-slot-key.js';
|
|
9
|
+
/** Stable empty boundary list — so `sessionBreaks={false}` keeps a referentially
|
|
10
|
+
* constant array and the layer entry isn't rebuilt every render. */
|
|
11
|
+
const NO_BREAKS = [];
|
|
9
12
|
/**
|
|
10
13
|
* A line draw layer. Reads `column` from `series` into a {@link ChartSeries}
|
|
11
14
|
* (columnar, gaps as NaN), registers itself into the enclosing {@link Layers}
|
|
12
15
|
* (scaling against its `axis`), and renders nothing to the DOM — the row draws
|
|
13
16
|
* it. The line breaks at gaps rather than spanning them.
|
|
14
17
|
*/
|
|
15
|
-
export function LineChart({ series, column, as: semantic, axis, curve, gaps = DEFAULT_GAP_MODE, index = 0, }) {
|
|
18
|
+
export function LineChart({ series, column, as: semantic, axis, curve, gaps = DEFAULT_GAP_MODE, sessionBreaks = false, index = 0, }) {
|
|
16
19
|
const container = useContext(ContainerContext);
|
|
17
20
|
if (container === null) {
|
|
18
21
|
throw new Error('<LineChart> must be rendered inside a <ChartContainer>');
|
|
@@ -33,6 +36,17 @@ export function LineChart({ series, column, as: semantic, axis, curve, gaps = DE
|
|
|
33
36
|
// Faintness of the inferred dashed connectors (dashed / step) — theme-level,
|
|
34
37
|
// falling back to the shared default so a theme without it still renders faint.
|
|
35
38
|
const gapConnectorOpacity = container.theme.gap?.connectorOpacity ?? DEFAULT_GAP_CONNECTOR_OPACITY;
|
|
39
|
+
// Trading-axis session breaks: the collapse instants inside this series' span
|
|
40
|
+
// (session/day/lunch opens the axis skips). Data instants, not pixels — so the
|
|
41
|
+
// set is view-independent (pan/zoom reuse it). Only computed when opted in and
|
|
42
|
+
// the container carries a boundary-reporting discontinuity provider.
|
|
43
|
+
const sessionBreakInstants = useMemo(() => {
|
|
44
|
+
const provider = container.discontinuities;
|
|
45
|
+
if (!sessionBreaks || provider?.boundaries === undefined || cs.length < 2) {
|
|
46
|
+
return NO_BREAKS;
|
|
47
|
+
}
|
|
48
|
+
return provider.boundaries(cs.x[0], cs.x[cs.length - 1]);
|
|
49
|
+
}, [sessionBreaks, container.discontinuities, cs]);
|
|
36
50
|
const entry = useMemo(() => ({
|
|
37
51
|
layer: {
|
|
38
52
|
yExtent: () => yExtent(cs),
|
|
@@ -67,7 +81,7 @@ export function LineChart({ series, column, as: semantic, axis, curve, gaps = DE
|
|
|
67
81
|
? [{ x: e.begin(), value: v, color: style.color, label }]
|
|
68
82
|
: [];
|
|
69
83
|
},
|
|
70
|
-
draw: (ctx, xScale, yScale) => drawLine(ctx, cs, xScale, yScale, style, curveFactory, gaps, gapConnectorOpacity),
|
|
84
|
+
draw: (ctx, xScale, yScale) => drawLine(ctx, cs, xScale, yScale, style, curveFactory, gaps, gapConnectorOpacity, sessionBreakInstants),
|
|
71
85
|
},
|
|
72
86
|
axisId: axis,
|
|
73
87
|
index,
|
|
@@ -80,6 +94,7 @@ export function LineChart({ series, column, as: semantic, axis, curve, gaps = DE
|
|
|
80
94
|
curveFactory,
|
|
81
95
|
gaps,
|
|
82
96
|
gapConnectorOpacity,
|
|
97
|
+
sessionBreakInstants,
|
|
83
98
|
axis,
|
|
84
99
|
index,
|
|
85
100
|
]);
|
package/dist/XAxis.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AxisTransform } from './derivedTicks.js';
|
|
1
2
|
import { type AxisFormat } from './format.js';
|
|
2
3
|
export interface XAxisProps {
|
|
3
4
|
/**
|
|
@@ -24,6 +25,31 @@ export interface XAxisProps {
|
|
|
24
25
|
readonly at: number;
|
|
25
26
|
readonly label: string;
|
|
26
27
|
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Relabel this axis into a **derived unit on the same scale** — a second
|
|
30
|
+
* tick layout, not a second scale (the pixel mapping never changes). E.g. a
|
|
31
|
+
* BS-delta strip under a std-moneyness chart (`transform={{ to: sigmaToDelta,
|
|
32
|
+
* from: deltaToSigma }}`) or a moneyness axis over a strike chart
|
|
33
|
+
* (`transform={{ to: (k) => k / spot, from: (m) => m * spot }}`). `to`/`from`
|
|
34
|
+
* are monotonic inverses (either direction); they may be **nonlinear** —
|
|
35
|
+
* ticks are nice derived-unit values at mixed 1-2-5 step sizes, admitted
|
|
36
|
+
* wherever they keep pixel room, so a span the transform compresses gets
|
|
37
|
+
* coarser ticks and a span it stretches gets finer ones. `format` (or the
|
|
38
|
+
* d3 number default) formats the derived values; the cursor pill and marker
|
|
39
|
+
* indicators on this axis read in the derived unit too. Ignored on a
|
|
40
|
+
* category axis; explicit {@link ticks} win. Typically used on a second
|
|
41
|
+
* `<XAxis>` stacked with the primary one — declaration order places the
|
|
42
|
+
* strips; gridlines stay on the container's own (primary) ticks.
|
|
43
|
+
*/
|
|
44
|
+
transform?: AxisTransform;
|
|
45
|
+
/**
|
|
46
|
+
* This axis instance's colour — tick marks, labels, the plot-facing rule,
|
|
47
|
+
* and the `label` title all take it, overriding the theme's `axis.label` /
|
|
48
|
+
* `axis.grid` / `axis.title.color`. The lever that distinguishes stacked
|
|
49
|
+
* dual axes (a blue derived-unit strip under a grey primary). Cursor and
|
|
50
|
+
* marker pills keep their own colours. Omit for the theme's axis colours.
|
|
51
|
+
*/
|
|
52
|
+
color?: string;
|
|
27
53
|
/**
|
|
28
54
|
* Horizontal placement of each tick label relative to its tick.
|
|
29
55
|
* - **`'center'` (default)** — every label centred on its tick. Note the
|
|
@@ -48,5 +74,5 @@ export interface XAxisProps {
|
|
|
48
74
|
*
|
|
49
75
|
* `<TimeAxis>` is the time-flavoured preset (`<XAxis />`).
|
|
50
76
|
*/
|
|
51
|
-
export declare function XAxis({ format, label, side, height, ticks: customTicks, align, }?: XAxisProps): import("react/jsx-runtime").JSX.Element;
|
|
77
|
+
export declare function XAxis({ format, label, side, height, ticks: customTicks, transform, color, align, }?: XAxisProps): import("react/jsx-runtime").JSX.Element;
|
|
52
78
|
//# sourceMappingURL=XAxis.d.ts.map
|
package/dist/XAxis.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useContext } from 'react';
|
|
3
|
+
import { scaleLinear } from 'd3-scale';
|
|
4
|
+
import { derivedTicks } from './derivedTicks.js';
|
|
3
5
|
import { ContainerContext } from './context.js';
|
|
4
6
|
import { axisPillStyle } from './chip.js';
|
|
5
7
|
import { resolveAxisFormat, resolveTimeFormat, } from './format.js';
|
|
@@ -7,6 +9,12 @@ import { resolveAxisFormat, resolveTimeFormat, } from './format.js';
|
|
|
7
9
|
const TICK_STRIP = 22;
|
|
8
10
|
/** Extra height reserved for an axis `label` line. */
|
|
9
11
|
const LABEL_STRIP = 16;
|
|
12
|
+
/** Extra height reserved for the boundary (second) label row. */
|
|
13
|
+
const BOUNDARY_STRIP = 15;
|
|
14
|
+
/** Minimum pixel gap between derived-unit (`transform`) ticks — the room a
|
|
15
|
+
* short numeric label needs plus breathing space, in the spirit of the
|
|
16
|
+
* ladder's per-tick budget (a hair tighter: derived labels are short). */
|
|
17
|
+
const TRANSFORM_TICK_PX = 48;
|
|
10
18
|
/**
|
|
11
19
|
* Thin + truncate a **category** axis's labels so a dense axis stays legible: keep
|
|
12
20
|
* every `stride`-th label (so a kept label has room), and ellipsize one that still
|
|
@@ -47,7 +55,7 @@ function thinCategoryLabels(ticks, plotWidth, fontSize) {
|
|
|
47
55
|
*
|
|
48
56
|
* `<TimeAxis>` is the time-flavoured preset (`<XAxis />`).
|
|
49
57
|
*/
|
|
50
|
-
export function XAxis({ format, label, side = 'bottom', height, ticks: customTicks, align = 'center', } = {}) {
|
|
58
|
+
export function XAxis({ format, label, side = 'bottom', height, ticks: customTicks, transform, color, align = 'center', } = {}) {
|
|
51
59
|
const container = useContext(ContainerContext);
|
|
52
60
|
if (container === null) {
|
|
53
61
|
throw new Error('<XAxis> must be rendered inside a <ChartContainer>');
|
|
@@ -67,19 +75,37 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
67
75
|
cursorX <= plotWidth;
|
|
68
76
|
const cursorColor = theme.cursor ?? theme.axis.label;
|
|
69
77
|
const annotationColor = theme.annotation?.color ?? '#0d9488';
|
|
78
|
+
// Derived-unit (`transform`) layout: nice ticks in the derived unit at
|
|
79
|
+
// mixed step sizes, admitted where they keep pixel room (see derivedTicks).
|
|
80
|
+
// Explicit `ticks` win; a category axis has no numeric unit to derive from.
|
|
81
|
+
const derived = transform !== undefined && xKind !== 'category' && customTicks === undefined
|
|
82
|
+
? derivedTicks(transform, xScale.domain(), (v) => xScale(v), plotWidth, TRANSFORM_TICK_PX)
|
|
83
|
+
: null;
|
|
84
|
+
// Formatter for derived-unit values — `format` resolved against a u-space
|
|
85
|
+
// linear scale (so `'+.2f'` and the d3 number default both work).
|
|
86
|
+
const uFmt = transform !== undefined && xKind !== 'category'
|
|
87
|
+
? (() => {
|
|
88
|
+
const [d0, d1] = xScale.domain();
|
|
89
|
+
const u = [transform.to(d0), transform.to(d1)].sort((a, b) => a - b);
|
|
90
|
+
return resolveAxisFormat(scaleLinear().domain(u), xTickCount, format);
|
|
91
|
+
})()
|
|
92
|
+
: null;
|
|
70
93
|
// Tick formatter: an explicit `format` is resolved against the axis kind
|
|
71
94
|
// (a time specifier through the time scale, a number specifier through the
|
|
72
95
|
// value scale); otherwise the container's shared formatter — the one the
|
|
73
|
-
// cursor readout uses, so a tick and the cursor read identically.
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
// cursor readout uses, so a tick and the cursor read identically. On a
|
|
97
|
+
// transformed axis every readout (cursor pill, marker indicator) speaks the
|
|
98
|
+
// **derived unit** — the axis's own language.
|
|
99
|
+
const fmt = transform !== undefined && uFmt !== null && xKind !== 'category'
|
|
100
|
+
? (v) => uFmt(transform.to(v))
|
|
101
|
+
: // A category axis labels by name (the container's `formatTime` = the band
|
|
102
|
+
// scale's label lookup); a d3 number/time `format` can't name a category, so
|
|
103
|
+
// it's ignored here (customize the labels in the `categories` data instead).
|
|
104
|
+
format === undefined || xKind === 'category'
|
|
105
|
+
? formatTime
|
|
106
|
+
: xKind === 'time'
|
|
107
|
+
? resolveTimeFormat(xScale, xTickCount, format)
|
|
108
|
+
: resolveAxisFormat(xScale, xTickCount, format);
|
|
83
109
|
// Marker annotations that opted into an axis indicator (`<Marker indicator>`)
|
|
84
110
|
// pin their **time** to this shared x-axis — a pill at `at`, in the annotation
|
|
85
111
|
// colour, reading like a tick. An indicator always shows the axis coordinate
|
|
@@ -126,12 +152,62 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
126
152
|
markerLanes.set(t.id, lane);
|
|
127
153
|
}
|
|
128
154
|
const maxPillLane = Math.max(0, pillLaneEnds.length - 1);
|
|
155
|
+
// The boundary (second) label row — the coarser calendar unit the first-row
|
|
156
|
+
// label omits (the year under day / week / month ticks, the date under
|
|
157
|
+
// clock ticks), placed under the first tick of each new
|
|
158
|
+
// period. Only a ladder-driven time scale supplies it; explicit `ticks`, an
|
|
159
|
+
// explicit axis `format`, and a container-level `timeFormat` all opt out (a
|
|
160
|
+
// custom format owns the whole label, and custom ticks have no grain).
|
|
161
|
+
const boundaryOf = xKind === 'time' &&
|
|
162
|
+
customTicks === undefined &&
|
|
163
|
+
transform === undefined &&
|
|
164
|
+
format === undefined &&
|
|
165
|
+
!container.xFormatCustom &&
|
|
166
|
+
'tickBoundaries' in xScale
|
|
167
|
+
? xScale.tickBoundaries(xTickCount)
|
|
168
|
+
: undefined;
|
|
169
|
+
// The pinned left-edge **context** label — what period the domain starts in
|
|
170
|
+
// (`Jan 01` over an intraday axis, the year over a month axis). A property
|
|
171
|
+
// of the domain, not of any tick: anchoring it to the first tick made it
|
|
172
|
+
// hop tick-to-tick on a live sliding window. Crossing labels ride their
|
|
173
|
+
// ticks and **push it off** the left edge as they approach (below).
|
|
174
|
+
const boundaryContext = boundaryOf !== undefined && 'boundaryContext' in xScale
|
|
175
|
+
? xScale.boundaryContext(xTickCount)
|
|
176
|
+
: undefined;
|
|
177
|
+
// Derived ticks pass a **label-honesty filter**: the fill can descend below
|
|
178
|
+
// the format's resolution (a delta tick at u = 0.498 renders as "+0.50" under
|
|
179
|
+
// `+.2f` — a lie about its position), so a tick survives only when its
|
|
180
|
+
// formatted label parses back to a value that maps to (±1px of) the tick's
|
|
181
|
+
// own pixel. This also caps density at the format's precision and drops
|
|
182
|
+
// would-be duplicate labels. Non-numeric labels (a custom format function)
|
|
183
|
+
// are trusted as-is.
|
|
184
|
+
const honestDerived = () => {
|
|
185
|
+
const out = [];
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
for (const t of derived) {
|
|
188
|
+
const text = uFmt(t.u);
|
|
189
|
+
if (seen.has(text))
|
|
190
|
+
continue;
|
|
191
|
+
const back = parseFloat(text.replace(/\u2212/g, '-').replace(/,/g, ''));
|
|
192
|
+
if (Number.isFinite(back)) {
|
|
193
|
+
const bx = xScale(transform.from(back));
|
|
194
|
+
if (!Number.isFinite(bx) || Math.abs(bx - t.x) > 1)
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
seen.add(text);
|
|
198
|
+
out.push({ x: t.x, label: text });
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
};
|
|
129
202
|
const rawTicks = customTicks
|
|
130
203
|
? customTicks.map((t) => ({ x: xScale(t.at), label: t.label }))
|
|
131
|
-
:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
204
|
+
: derived !== null
|
|
205
|
+
? honestDerived()
|
|
206
|
+
: xScale.ticks(xTickCount).map((d) => ({
|
|
207
|
+
x: xScale(d),
|
|
208
|
+
label: fmt(+d),
|
|
209
|
+
boundary: boundaryOf?.(+d),
|
|
210
|
+
}));
|
|
135
211
|
// A category axis ticks once per category; thin + truncate its labels when they
|
|
136
212
|
// crowd (an explicit `customTicks` axis keeps its labels verbatim).
|
|
137
213
|
const placed = xKind === 'category' && customTicks === undefined && rawTicks.length > 1
|
|
@@ -143,7 +219,34 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
143
219
|
const pillOffset = align === 'right' ? 2 : 6;
|
|
144
220
|
// Per-lane vertical step for stacked pills; grow the strip to fit the stack.
|
|
145
221
|
const PILL_LANE_H = theme.font.size + 6;
|
|
222
|
+
// Any boundary label in view grows the strip by one row (like pill lanes do).
|
|
223
|
+
const hasBoundary = boundaryContext !== undefined ||
|
|
224
|
+
placed.some((t) => t.boundary !== undefined);
|
|
225
|
+
// The pinned context anchors at the plot's left edge (the y-axis line) with
|
|
226
|
+
// the SAME alignment as the tick labels — centred on it in `center` mode
|
|
227
|
+
// (exactly where a first-tick label at x=0 sat, half into the gutter by the
|
|
228
|
+
// same documented rule), left-anchored in `auto`, beside the line in
|
|
229
|
+
// `right`. As the leftmost crossing label slides toward the edge it would
|
|
230
|
+
// collide, so the context CULLS once the crossing label's left edge reaches
|
|
231
|
+
// the context's right edge (plus a gap) — no overlap, and nothing slides
|
|
232
|
+
// loose into the gutter. Widths from the same rough glyph metric the pills
|
|
233
|
+
// use; the crossing's label is centred on its tick.
|
|
234
|
+
const charW = theme.font.size * 0.62;
|
|
235
|
+
const contextWidth = (boundaryContext?.length ?? 0) * charW;
|
|
236
|
+
const contextRight = align === 'center'
|
|
237
|
+
? contextWidth / 2
|
|
238
|
+
: align === 'right'
|
|
239
|
+
? 4 + contextWidth
|
|
240
|
+
: contextWidth;
|
|
241
|
+
const firstCrossing = placed.find((t) => t.boundary !== undefined);
|
|
242
|
+
const crossingLeft = firstCrossing
|
|
243
|
+
? align === 'right'
|
|
244
|
+
? firstCrossing.x + 4
|
|
245
|
+
: firstCrossing.x - (firstCrossing.boundary.length * charW) / 2
|
|
246
|
+
: Infinity;
|
|
247
|
+
const showContext = boundaryContext !== undefined && crossingLeft > contextRight + 6;
|
|
146
248
|
const stripHeight = (height ?? TICK_STRIP + (label ? LABEL_STRIP : 0)) +
|
|
249
|
+
(hasBoundary ? BOUNDARY_STRIP : 0) +
|
|
147
250
|
maxPillLane * PILL_LANE_H;
|
|
148
251
|
return (_jsxs("div", { style: {
|
|
149
252
|
position: 'relative',
|
|
@@ -151,10 +254,10 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
151
254
|
width: `${plotWidth}px`,
|
|
152
255
|
height: `${stripHeight}px`,
|
|
153
256
|
// The plot-facing edge carries the rule; a top axis rules its bottom.
|
|
154
|
-
[onTop ? 'borderBottom' : 'borderTop']: `1px solid ${theme.axis.grid}`,
|
|
257
|
+
[onTop ? 'borderBottom' : 'borderTop']: `1px solid ${color ?? theme.axis.grid}`,
|
|
155
258
|
fontFamily: theme.font.family,
|
|
156
259
|
fontSize: `${theme.font.size}px`,
|
|
157
|
-
color: theme.axis.label,
|
|
260
|
+
color: color ?? theme.axis.label,
|
|
158
261
|
}, children: [placed.map((t, i) => {
|
|
159
262
|
const isFirst = i === 0;
|
|
160
263
|
const isLast = i === placed.length - 1;
|
|
@@ -178,15 +281,29 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
178
281
|
[onTop ? 'bottom' : 'top']: 0,
|
|
179
282
|
width: '1px',
|
|
180
283
|
height: `${tickHeight}px`,
|
|
181
|
-
background: theme.axis.grid,
|
|
284
|
+
background: color ?? theme.axis.grid,
|
|
182
285
|
} }), _jsx("div", { style: {
|
|
183
286
|
position: 'absolute',
|
|
184
287
|
left: `${labelLeft}px`,
|
|
185
288
|
[onTop ? 'bottom' : 'top']: `${labelOffset}px`,
|
|
186
289
|
transform: labelTransform,
|
|
187
290
|
whiteSpace: 'nowrap',
|
|
188
|
-
}, children: t.label })
|
|
189
|
-
|
|
291
|
+
}, children: t.label }), t.boundary !== undefined && (_jsx("div", { "data-boundary-label": true, style: {
|
|
292
|
+
position: 'absolute',
|
|
293
|
+
left: `${labelLeft}px`,
|
|
294
|
+
[onTop ? 'bottom' : 'top']: `${labelOffset + theme.font.size + 3}px`,
|
|
295
|
+
transform: labelTransform,
|
|
296
|
+
whiteSpace: 'nowrap',
|
|
297
|
+
opacity: 0.75,
|
|
298
|
+
}, children: t.boundary }))] }, `${t.x}-${i}`));
|
|
299
|
+
}), showContext && (_jsx("div", { "data-boundary-label": true, "data-boundary-context": true, style: {
|
|
300
|
+
position: 'absolute',
|
|
301
|
+
left: `${align === 'right' ? 4 : 0}px`,
|
|
302
|
+
[onTop ? 'bottom' : 'top']: `${(align === 'right' ? 2 : 6) + theme.font.size + 3}px`,
|
|
303
|
+
transform: align === 'center' ? 'translateX(-50%)' : 'none',
|
|
304
|
+
whiteSpace: 'nowrap',
|
|
305
|
+
opacity: 0.75,
|
|
306
|
+
}, children: boundaryContext })), label !== undefined && (_jsx("div", { style: {
|
|
190
307
|
position: 'absolute',
|
|
191
308
|
left: 0,
|
|
192
309
|
width: '100%',
|
|
@@ -194,7 +311,7 @@ export function XAxis({ format, label, side = 'bottom', height, ticks: customTic
|
|
|
194
311
|
[onTop ? 'top' : 'bottom']: 0,
|
|
195
312
|
// Themeable axis-title text (shared with the rotated y-axis title).
|
|
196
313
|
fontSize: `${theme.axis.title?.size ?? theme.font.size + 1}px`,
|
|
197
|
-
color: theme.axis.title?.color ?? theme.axis.label,
|
|
314
|
+
color: color ?? theme.axis.title?.color ?? theme.axis.label,
|
|
198
315
|
opacity: theme.axis.title?.opacity ?? 0.85,
|
|
199
316
|
whiteSpace: 'nowrap',
|
|
200
317
|
}, children: label })), markerTags.map((t) => {
|
package/dist/YAxis.d.ts
CHANGED
|
@@ -70,6 +70,14 @@ export interface YAxisProps {
|
|
|
70
70
|
boundaryLabels?: boolean;
|
|
71
71
|
/** Gutter width in CSS pixels (default 50). */
|
|
72
72
|
width?: number;
|
|
73
|
+
/**
|
|
74
|
+
* This axis instance's colour — tick labels and the axis title take it,
|
|
75
|
+
* overriding the theme's `axis.label` / `axis.title.color`. The multi-axis
|
|
76
|
+
* convention of colouring each y axis to match its series (`color`
|
|
77
|
+
* matching the layer's) — busy, but standard. Omit for the theme's axis
|
|
78
|
+
* colours. Presentation-only: it never re-registers the axis.
|
|
79
|
+
*/
|
|
80
|
+
color?: string;
|
|
73
81
|
/**
|
|
74
82
|
* @internal Declaration position among the row's children, injected by
|
|
75
83
|
* `ChartRow` so the first-declared axis stays the default. Do not set.
|
|
@@ -84,5 +92,5 @@ export interface YAxisProps {
|
|
|
84
92
|
* tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
|
|
85
93
|
* (default: the first axis).
|
|
86
94
|
*/
|
|
87
|
-
export declare function YAxis({ id, side, label, min, max, format, ticks, pad, boundaryLabels, width, labelPlacement, index, }: YAxisProps): import("react/jsx-runtime").JSX.Element;
|
|
95
|
+
export declare function YAxis({ id, side, label, min, max, format, ticks, pad, boundaryLabels, width, labelPlacement, color, index, }: YAxisProps): import("react/jsx-runtime").JSX.Element;
|
|
88
96
|
//# sourceMappingURL=YAxis.d.ts.map
|
package/dist/YAxis.js
CHANGED
|
@@ -13,7 +13,7 @@ const TICK_COUNT = 5;
|
|
|
13
13
|
* tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
|
|
14
14
|
* (default: the first axis).
|
|
15
15
|
*/
|
|
16
|
-
export function YAxis({ id, side = 'left', label, min, max, format, ticks, pad = 0, boundaryLabels = true, width = DEFAULT_WIDTH, labelPlacement = 'rotated', index = 0, }) {
|
|
16
|
+
export function YAxis({ id, side = 'left', label, min, max, format, ticks, pad = 0, boundaryLabels = true, width = DEFAULT_WIDTH, labelPlacement = 'rotated', color, index = 0, }) {
|
|
17
17
|
const container = useContext(ContainerContext);
|
|
18
18
|
if (container === null) {
|
|
19
19
|
throw new Error('<YAxis> must be rendered inside a <ChartContainer>');
|
|
@@ -76,7 +76,7 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, pad =
|
|
|
76
76
|
height: `${row.height}px`,
|
|
77
77
|
fontFamily: theme.font.family,
|
|
78
78
|
fontSize: `${theme.font.size}px`,
|
|
79
|
-
color: theme.axis.label,
|
|
79
|
+
color: color ?? theme.axis.label,
|
|
80
80
|
}, children: [yScale &&
|
|
81
81
|
tickList.map(({ value, label }, i) => {
|
|
82
82
|
// Drop just the top & bottom labels when boundary labels are off
|
|
@@ -102,7 +102,7 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, pad =
|
|
|
102
102
|
// labels' alignment, rather than floating at the outer gutter edge.
|
|
103
103
|
[side === 'left' ? 'right' : 'left']: '4px',
|
|
104
104
|
fontSize: `${theme.axis.title?.size ?? theme.font.size + 1}px`,
|
|
105
|
-
color: theme.axis.title?.color ?? theme.axis.label,
|
|
105
|
+
color: color ?? theme.axis.title?.color ?? theme.axis.label,
|
|
106
106
|
opacity: theme.axis.title?.opacity ?? 0.85,
|
|
107
107
|
whiteSpace: 'nowrap',
|
|
108
108
|
pointerEvents: 'none',
|
|
@@ -116,7 +116,7 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, pad =
|
|
|
116
116
|
alignItems: 'center',
|
|
117
117
|
justifyContent: 'center',
|
|
118
118
|
fontSize: `${theme.axis.title?.size ?? theme.font.size + 1}px`,
|
|
119
|
-
color: theme.axis.title?.color ?? theme.axis.label,
|
|
119
|
+
color: color ?? theme.axis.title?.color ?? theme.axis.label,
|
|
120
120
|
opacity: theme.axis.title?.opacity ?? 0.85,
|
|
121
121
|
pointerEvents: 'none',
|
|
122
122
|
}, children: _jsx("span", { style: {
|