packages/core/src/features/viz/lib/trading-time-scale.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import {
2 resolveSignalValue,
3 useLayoutBox,
4 type SignalValue,
5 type PibblNode,
6 type Signal,
7 } from "@pibbl/core";
8 import type {
9 ScaleId,
10 ScaleRange,
11 ScaleLayoutStyle,
12 ResolvedScale,
13 } from "./types.js";
14 import type { SignalStyle } from "@pibbl/core/internal";
15 import { createTradingTimeMapping } from "./trading-time-mapping.js";
16 import { resolveLinearScale } from "./linear-scale.js";
17 import { useScaleProvider } from "./scale-provider.js";
18 import { scaleView } from "./scale-view.js";
19
20 /** Controlled gapless observation slots; UTCScale retains elapsed-time semantics.
21 * @see {@link TradingTimeScale}
22 */
23 export interface TradingTimeScaleProps {
24 /** Ancestor scale identity. */ readonly id: ScaleId;
25 /** Strictly increasing, unique UTC-millisecond observations; copied by the mapping. */ readonly timestamps: SignalValue<
26 readonly number[]
27 >;
28 /** Visible logical slot endpoints, possibly fractional/reversed; default covers supplied slots. */ readonly logicalRange?: SignalValue<
29 readonly [number, number]
30 >;
31 /** Output in local logical units or the receiving allocation's width/height. */ readonly range: SignalValue<ScaleRange>;
32 /** Reverses physical output without reordering supplied observations. */ readonly reverse?: SignalValue<boolean>;
33 /** Receiving scale frame allocation and layout participation. */ readonly style?: SignalValue<
34 SignalStyle<ScaleLayoutStyle>
35 >;
36 /** Descendant visualization composition. */ readonly children?: PibblNode;
37 }
38 /** Dedicated exact-time/slot mapping; no interpolated UTC timestamps are fabricated.
39 * @see {@link TradingTimeScale}
40 */
41 export interface ResolvedTradingTimeScale {
42 /** Describes this resolved mapping. */ readonly type: "trading-time";
43 /** Owned supplied timestamps in ascending order, also used as the data domain. */ readonly timestamps: readonly number[];
44 /** Owned supplied timestamp domain, not logical slot endpoints. */ readonly domain: readonly number[];
45 /** Controlled viewport endpoints in logical slot units. */ readonly logicalRange: readonly [
46 number,
47 number,
48 ];
49 /** Output local-logical endpoints. */ readonly range: readonly [
50 number,
51 number,
52 ];
53 /** Receiving allocation in local logical units. */ readonly frame: Readonly<{
54 /** Local allocation x. */ x: number;
55 /** Local allocation y. */ y: number;
56 /** Local width. */ width: number;
57 /** Local height. */ height: number;
58 }>;
59 /** Maps an exact supplied UTC timestamp; unknown timestamps throw.
60 * @param time - Supplied UTC milliseconds.
61 * @returns Series-local coordinate.
62 */ map(time: number): number;
63 /** Maps a possibly fractional logical slot, including viewport whitespace.
64 * @param slot - Logical observation slot.
65 * @returns Series-local coordinate.
66 */ mapSlot(slot: number): number;
67 /** Exact timestamp lookup.
68 * @param time - UTC milliseconds.
69 * @returns Supplied slot index, or undefined.
70 */ slotAtTime(time: number): number | undefined;
71 /** Exact integer slot lookup.
72 * @param slot - Integer logical observation slot.
73 * @returns Supplied timestamp, or undefined.
74 */ timeAtSlot(slot: number): number | undefined;
75 /** Inverts local geometry to fractional slots.
76 * @param position - Series-local logical coordinate.
77 * @returns Fractional logical slot.
78 */ slotAtPosition(position: number): number;
79 /** Selects a supplied timestamp; ties choose earlier, outside positions clamp.
80 * @param position - Series-local logical coordinate.
81 * @returns Supplied UTC milliseconds, or undefined for empty input.
82 */ nearestTimeAtPosition(position: number): number | undefined;
83 /** Chooses at most count visible supplied observations as date ticks.
84 * @param count - Integer maximum from zero through 100, default five.
85 * @returns Original UTC timestamps in physical axis order.
86 */ ticks(count?: number): readonly number[];
87 /** Formats supplied or finite valid UTC timestamps without inferring sessions.
88 * @param time - UTC milliseconds in Date range.
89 * @returns ISO UTC timestamp.
90 */ formatTick(time: number): string;
91 }
92 const views = new WeakMap<
93 Signal<ResolvedScale>,
94 Signal<ResolvedTradingTimeScale>
95 >();
96 /**
97 * Reads a stable dedicated ancestor trading-time signal.
98 * @param id - Ancestor TradingTimeScale identity.
99 * @returns Readonly mapping with explicit timestamp and logical-slot operations.
100 * @see {@link TradingTimeScale}
101 */
102 export function useTradingTimeScale(
103 id: ScaleId,
104 ): Signal<ResolvedTradingTimeScale> {
105 return scaleView(
106 views,
107 id,
108 "useTradingTimeScale",
109 (kind) => kind === "trading-time",
110 );
111 }
112 /**
113 * Provides gapless positions for ordered supplied observations, leaving application-owned viewport control explicit.
114 * @param props - Timestamps, optional logical viewport, output range and children.
115 * @returns Descendant composition in the named mapping scope.
116 * @see {@link TradingTimeScaleProps}
117 */
118 export function TradingTimeScale(props: TradingTimeScaleProps): PibblNode {
119 const { width, height } = useLayoutBox();
120 useScaleProvider(
121 props.id,
122 "trading-time",
123 () => {
124 const timestamps = Object.freeze([
125 ...resolveSignalValue(props.timestamps),
126 ]);
127 const logicalRange = Object.freeze([
128 ...(props.logicalRange
129 ? resolveSignalValue(props.logicalRange)
130 : [-0.5, Math.max(0.5, timestamps.length - 0.5)]),
131 ]) as readonly [number, number];
132 const linear = resolveLinearScale(
133 logicalRange,
134 resolveSignalValue(props.range),
135 resolveSignalValue(props.reverse ?? false),
136 width,
137 height,
138 );
139 const mapping = createTradingTimeMapping({
140 timestamps,
141 logicalRange,
142 range: linear.range,
143 });
144 return Object.freeze({
145 type: "trading-time" as const,
146 timestamps,
147 domain: timestamps,
148 logicalRange,
149 range: linear.range,
150 frame: linear.frame,
151 map: mapping.mapTime,
152 mapSlot: linear.map,
153 slotAtTime: mapping.slotAtTime,
154 timeAtSlot: mapping.timeAtSlot,
155 slotAtPosition: mapping.slotAtPosition,
156 nearestTimeAtPosition: mapping.nearestTimeAtPosition,
157 ticks(count = 5) {
158 if (!Number.isInteger(count) || count < 0 || count > 100)
159 throw new RangeError(
160 "TradingTime ticks count must be an integer 0–100.",
161 );
162 const lo = Math.max(0, Math.ceil(Math.min(...logicalRange))),
163 hi = Math.min(
164 timestamps.length - 1,
165 Math.floor(Math.max(...logicalRange)),
166 ),
167 n = Math.max(0, hi - lo + 1),
168 m = Math.min(n, count);
169 const result = Array.from(
170 { length: m },
171 (_, i) =>
172 timestamps[
173 lo + (m === 1 ? 0 : Math.round((i * (n - 1)) / (m - 1)))
174 ]!,
175 );
176 return Object.freeze(
177 logicalRange[0] > logicalRange[1] ? result.reverse() : result,
178 );
179 },
180 formatTick(time: number) {
181 if (!Number.isFinite(time) || Math.abs(time) > 8_640_000_000_000_000)
182 throw new RangeError(
183 "TradingTime format requires UTC milliseconds within Date range.",
184 );
185 return new Date(time).toISOString();
186 },
187 });
188 },
189 [
190 props.timestamps,
191 props.logicalRange,
192 props.range,
193 props.reverse,
194 width,
195 height,
196 ],
197 );
198 return props.children;
199 }
200
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision dd39ce2. ALPHA — NOT FOR PRODUCTION USE.