Skip to content

packages/core/src/features/viz/lib/trading-time-scale.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

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 built with @pibbl/core 0.0.2, revision dd39ce2. ALPHA — NOT FOR PRODUCTION USE.