@matchstick-trading/primitives 0.0.10
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/AGENTS.md +44 -0
- package/CHANGELOG.md +19 -0
- package/LICENSE +201 -0
- package/README.md +636 -0
- package/dist/Chart.d.ts +18 -0
- package/dist/Chart.js +6 -0
- package/dist/ChartParts.d.ts +151 -0
- package/dist/ChartParts.js +389 -0
- package/dist/CsvDrop.d.ts +8 -0
- package/dist/CsvDrop.js +5 -0
- package/dist/CsvDropParts.d.ts +33 -0
- package/dist/CsvDropParts.js +77 -0
- package/dist/Cursor.d.ts +6 -0
- package/dist/Cursor.js +5 -0
- package/dist/CursorParts.d.ts +51 -0
- package/dist/CursorParts.js +40 -0
- package/dist/DepthProfile.d.ts +13 -0
- package/dist/DepthProfile.js +5 -0
- package/dist/DepthProfileParts.d.ts +45 -0
- package/dist/DepthProfileParts.js +96 -0
- package/dist/DiffTable.d.ts +13 -0
- package/dist/DiffTable.js +5 -0
- package/dist/DiffTableParts.d.ts +57 -0
- package/dist/DiffTableParts.js +103 -0
- package/dist/Drawings.d.ts +13 -0
- package/dist/Drawings.js +5 -0
- package/dist/DrawingsParts.d.ts +194 -0
- package/dist/DrawingsParts.js +775 -0
- package/dist/Fullscreen.d.ts +7 -0
- package/dist/Fullscreen.js +5 -0
- package/dist/FullscreenParts.d.ts +54 -0
- package/dist/FullscreenParts.js +250 -0
- package/dist/Ladder.d.ts +13 -0
- package/dist/Ladder.js +5 -0
- package/dist/LadderParts.d.ts +49 -0
- package/dist/LadderParts.js +56 -0
- package/dist/Menu.d.ts +11 -0
- package/dist/Menu.js +5 -0
- package/dist/MenuParts.d.ts +52 -0
- package/dist/MenuParts.js +202 -0
- package/dist/Notice.d.ts +18 -0
- package/dist/Notice.js +9 -0
- package/dist/Panels.d.ts +9 -0
- package/dist/Panels.js +5 -0
- package/dist/PanelsParts.d.ts +62 -0
- package/dist/PanelsParts.js +387 -0
- package/dist/Receipt.d.ts +11 -0
- package/dist/Receipt.js +5 -0
- package/dist/ReceiptParts.d.ts +45 -0
- package/dist/ReceiptParts.js +71 -0
- package/dist/Scrubber.d.ts +9 -0
- package/dist/Scrubber.js +5 -0
- package/dist/ScrubberParts.d.ts +74 -0
- package/dist/ScrubberParts.js +126 -0
- package/dist/Selection.d.ts +14 -0
- package/dist/Selection.js +7 -0
- package/dist/SelectionParts.d.ts +63 -0
- package/dist/SelectionParts.js +208 -0
- package/dist/Studies.d.ts +9 -0
- package/dist/Studies.js +5 -0
- package/dist/StudiesParts.d.ts +45 -0
- package/dist/StudiesParts.js +153 -0
- package/dist/Submenu.d.ts +7 -0
- package/dist/Submenu.js +4 -0
- package/dist/SymbolSearch.d.ts +10 -0
- package/dist/SymbolSearch.js +5 -0
- package/dist/SymbolSearchParts.d.ts +39 -0
- package/dist/SymbolSearchParts.js +86 -0
- package/dist/Tape.d.ts +15 -0
- package/dist/Tape.js +5 -0
- package/dist/TapeParts.d.ts +69 -0
- package/dist/TapeParts.js +139 -0
- package/dist/Toolbar.d.ts +8 -0
- package/dist/Toolbar.js +5 -0
- package/dist/ToolbarParts.d.ts +61 -0
- package/dist/ToolbarParts.js +122 -0
- package/dist/Transport.d.ts +15 -0
- package/dist/Transport.js +5 -0
- package/dist/TransportParts.d.ts +52 -0
- package/dist/TransportParts.js +47 -0
- package/dist/VolumeProfile.d.ts +7 -0
- package/dist/VolumeProfile.js +5 -0
- package/dist/VolumeProfileParts.d.ts +37 -0
- package/dist/VolumeProfileParts.js +57 -0
- package/dist/accessible-name.d.ts +26 -0
- package/dist/accessible-name.js +14 -0
- package/dist/attributes.d.ts +8 -0
- package/dist/attributes.js +9 -0
- package/dist/core/adapter.d.ts +38 -0
- package/dist/core/adapter.js +64 -0
- package/dist/core/attributes.d.ts +36 -0
- package/dist/core/attributes.js +22 -0
- package/dist/core/candles.d.ts +6 -0
- package/dist/core/candles.js +19 -0
- package/dist/core/chart-gesture.d.ts +109 -0
- package/dist/core/chart-gesture.js +161 -0
- package/dist/core/chart-scale.d.ts +141 -0
- package/dist/core/chart-scale.js +302 -0
- package/dist/core/csv-bars.d.ts +54 -0
- package/dist/core/csv-bars.js +325 -0
- package/dist/core/depth-profile.d.ts +43 -0
- package/dist/core/depth-profile.js +66 -0
- package/dist/core/diff-table.d.ts +57 -0
- package/dist/core/diff-table.js +77 -0
- package/dist/core/drawing-fields.d.ts +11 -0
- package/dist/core/drawing-fields.js +15 -0
- package/dist/core/drawings.d.ts +194 -0
- package/dist/core/drawings.js +403 -0
- package/dist/core/fullscreen.d.ts +32 -0
- package/dist/core/fullscreen.js +16 -0
- package/dist/core/index.d.ts +41 -0
- package/dist/core/index.js +23 -0
- package/dist/core/ladder-keys.d.ts +35 -0
- package/dist/core/ladder-keys.js +43 -0
- package/dist/core/ladder.d.ts +65 -0
- package/dist/core/ladder.js +93 -0
- package/dist/core/panels.d.ts +72 -0
- package/dist/core/panels.js +151 -0
- package/dist/core/receipt-rules.d.ts +42 -0
- package/dist/core/receipt-rules.js +20 -0
- package/dist/core/scrubber-gesture.d.ts +66 -0
- package/dist/core/scrubber-gesture.js +69 -0
- package/dist/core/selection.d.ts +24 -0
- package/dist/core/selection.js +33 -0
- package/dist/core/series.d.ts +161 -0
- package/dist/core/series.js +331 -0
- package/dist/core/studies.d.ts +80 -0
- package/dist/core/studies.js +237 -0
- package/dist/core/symbol-search.d.ts +89 -0
- package/dist/core/symbol-search.js +223 -0
- package/dist/core/tape.d.ts +77 -0
- package/dist/core/tape.js +136 -0
- package/dist/core/volume-profile.d.ts +47 -0
- package/dist/core/volume-profile.js +86 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +42 -0
- package/dist/ladder-keys.d.ts +16 -0
- package/dist/ladder-keys.js +86 -0
- package/dist/panels-context.d.ts +47 -0
- package/dist/panels-context.js +3 -0
- package/dist/selection-context.d.ts +41 -0
- package/dist/selection-context.js +4 -0
- package/examples/csv-to-chart-frame.mjs +17 -0
- package/examples/studies.mjs +12 -0
- package/llms.txt +16 -0
- package/package.json +67 -0
- package/src/Chart.tsx +7 -0
- package/src/ChartParts.tsx +529 -0
- package/src/CsvDrop.tsx +6 -0
- package/src/CsvDropParts.tsx +128 -0
- package/src/Cursor.tsx +6 -0
- package/src/CursorParts.tsx +77 -0
- package/src/DepthProfile.tsx +6 -0
- package/src/DepthProfileParts.tsx +154 -0
- package/src/DiffTable.tsx +6 -0
- package/src/DiffTableParts.tsx +160 -0
- package/src/Drawings.tsx +6 -0
- package/src/DrawingsParts.tsx +1080 -0
- package/src/Fullscreen.tsx +6 -0
- package/src/FullscreenParts.tsx +318 -0
- package/src/Ladder.tsx +6 -0
- package/src/LadderParts.tsx +114 -0
- package/src/Menu.tsx +6 -0
- package/src/MenuParts.tsx +364 -0
- package/src/Notice.tsx +27 -0
- package/src/Panels.tsx +6 -0
- package/src/PanelsParts.tsx +454 -0
- package/src/Receipt.tsx +6 -0
- package/src/ReceiptParts.tsx +118 -0
- package/src/Scrubber.tsx +6 -0
- package/src/ScrubberParts.tsx +207 -0
- package/src/Selection.tsx +12 -0
- package/src/SelectionParts.tsx +251 -0
- package/src/Studies.tsx +6 -0
- package/src/StudiesParts.tsx +226 -0
- package/src/Submenu.tsx +5 -0
- package/src/SymbolSearch.tsx +6 -0
- package/src/SymbolSearchParts.tsx +166 -0
- package/src/Tape.tsx +6 -0
- package/src/TapeParts.tsx +208 -0
- package/src/Toolbar.tsx +6 -0
- package/src/ToolbarParts.tsx +179 -0
- package/src/Transport.tsx +6 -0
- package/src/TransportParts.tsx +128 -0
- package/src/VolumeProfile.tsx +6 -0
- package/src/VolumeProfileParts.tsx +106 -0
- package/src/accessible-name.tsx +34 -0
- package/src/attributes.ts +12 -0
- package/src/core/adapter.ts +91 -0
- package/src/core/attributes.ts +51 -0
- package/src/core/candles.ts +22 -0
- package/src/core/chart-gesture.ts +228 -0
- package/src/core/chart-scale.ts +368 -0
- package/src/core/csv-bars.ts +328 -0
- package/src/core/depth-profile.ts +84 -0
- package/src/core/diff-table.ts +105 -0
- package/src/core/drawing-fields.ts +17 -0
- package/src/core/drawings.ts +478 -0
- package/src/core/fullscreen.ts +32 -0
- package/src/core/index.ts +42 -0
- package/src/core/ladder-keys.ts +61 -0
- package/src/core/ladder.ts +156 -0
- package/src/core/panels.ts +177 -0
- package/src/core/receipt-rules.ts +45 -0
- package/src/core/scrubber-gesture.ts +102 -0
- package/src/core/selection.ts +41 -0
- package/src/core/series.ts +374 -0
- package/src/core/studies.ts +260 -0
- package/src/core/symbol-search.ts +256 -0
- package/src/core/tape.ts +159 -0
- package/src/core/volume-profile.ts +116 -0
- package/src/index.ts +83 -0
- package/src/ladder-keys.ts +83 -0
- package/src/panels-context.ts +46 -0
- package/src/selection-context.ts +38 -0
- package/src/tokens.css +235 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
export interface TransportRootProps {
|
|
3
|
+
playing: boolean;
|
|
4
|
+
/** Another operation is running: Previous, Next, Start and End are disabled; Play is aria-disabled and ignores activation. */
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
/** Set when the cursor sits at the start or the end of the session. */
|
|
7
|
+
boundary?: 'start' | 'end' | null;
|
|
8
|
+
className?: string;
|
|
9
|
+
'aria-label'?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
declare function Root({ playing, loading, boundary, className, 'aria-label': label, children }: TransportRootProps): import("react").JSX.Element;
|
|
13
|
+
declare function Group({ className, label, children }: {
|
|
14
|
+
className?: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export type PartButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'disabled'>;
|
|
19
|
+
/** The play toggle is never `disabled`: disabling the focused button would drop focus to <body> mid-gesture. */
|
|
20
|
+
declare function Play({ onClick, ...rest }: PartButtonProps): import("react").JSX.Element;
|
|
21
|
+
export type StepButton = {
|
|
22
|
+
(props: PartButtonProps): ReactElement;
|
|
23
|
+
displayName?: string;
|
|
24
|
+
};
|
|
25
|
+
declare const Previous: StepButton;
|
|
26
|
+
declare const Next: StepButton;
|
|
27
|
+
declare const Start: StepButton;
|
|
28
|
+
declare const End: StepButton;
|
|
29
|
+
export interface TransportSpeedProps {
|
|
30
|
+
speed: number;
|
|
31
|
+
options: readonly number[];
|
|
32
|
+
onSpeedChange: (speed: number) => void;
|
|
33
|
+
/** Text for one option, for example `formatSpeed`. */
|
|
34
|
+
format: (speed: number) => string;
|
|
35
|
+
className?: string;
|
|
36
|
+
labelClassName?: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
'aria-label'?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function Speed({ speed, options, onSpeedChange, format, className, labelClassName, label, 'aria-label': ariaLabel }: TransportSpeedProps): import("react").JSX.Element;
|
|
41
|
+
/** Not announced while playing: the readout changes every frame. */
|
|
42
|
+
declare function Readout({ className, children }: {
|
|
43
|
+
className?: string;
|
|
44
|
+
children?: ReactNode;
|
|
45
|
+
}): import("react").JSX.Element;
|
|
46
|
+
/** A normal boundary condition, not a failure: polite status text, nothing when no boundary is reached. */
|
|
47
|
+
declare function Boundary({ label, className }: {
|
|
48
|
+
label: string | null;
|
|
49
|
+
className?: string;
|
|
50
|
+
}): import("react").JSX.Element | null;
|
|
51
|
+
export { Root, Group, Play, Previous, Next, Start, End, Speed, Readout, Boundary };
|
|
52
|
+
//# sourceMappingURL=TransportParts.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { Notice } from './Notice.js';
|
|
5
|
+
const Context = createContext({ playing: false, loading: false });
|
|
6
|
+
function Root({ playing, loading = false, boundary = null, className, 'aria-label': label = 'Replay transport', children }) {
|
|
7
|
+
return (_jsx(Context.Provider, { value: { playing, loading }, children: _jsx("nav", { className: className, "aria-label": label, "data-state": playing ? 'playing' : 'paused', "data-boundary": boundary ?? undefined, children: children }) }));
|
|
8
|
+
}
|
|
9
|
+
function Group({ className, label, children }) {
|
|
10
|
+
return label ? (_jsx("div", { className: className, role: "group", "aria-label": label, children: children })) : (_jsx("div", { className: className, children: children }));
|
|
11
|
+
}
|
|
12
|
+
/** The play toggle is never `disabled`: disabling the focused button would drop focus to <body> mid-gesture. */
|
|
13
|
+
function Play({ onClick, ...rest }) {
|
|
14
|
+
const { playing, loading } = useContext(Context);
|
|
15
|
+
return (_jsx("button", { type: "button", ...rest, onClick: (event) => {
|
|
16
|
+
if (loading)
|
|
17
|
+
return;
|
|
18
|
+
onClick?.(event);
|
|
19
|
+
}, "aria-disabled": loading ? 'true' : undefined, "data-state": playing ? 'playing' : 'paused', "data-disabled": loading ? '' : undefined }));
|
|
20
|
+
}
|
|
21
|
+
function stepButton(name) {
|
|
22
|
+
const Part = (props) => {
|
|
23
|
+
const { loading } = useContext(Context);
|
|
24
|
+
return _jsx("button", { type: "button", ...props, disabled: loading, "data-disabled": loading ? '' : undefined });
|
|
25
|
+
};
|
|
26
|
+
Part.displayName = `Transport.${name}`;
|
|
27
|
+
return Part;
|
|
28
|
+
}
|
|
29
|
+
const Previous = stepButton('Previous');
|
|
30
|
+
const Next = stepButton('Next');
|
|
31
|
+
const Start = stepButton('Start');
|
|
32
|
+
const End = stepButton('End');
|
|
33
|
+
function Speed({ speed, options = [], onSpeedChange, format = (value) => `${value}x`, className, labelClassName, label = 'Speed', 'aria-label': ariaLabel = 'Playback speed, multiple of real time' }) {
|
|
34
|
+
return (_jsxs("label", { className: className, children: [_jsx("span", { className: labelClassName, children: label }), _jsx("select", { value: String(speed), onChange: (event) => onSpeedChange(Number(event.target.value)), "aria-label": ariaLabel, children: (options ?? []).map((option) => (_jsx("option", { value: String(option), children: format(option) }, option))) })] }));
|
|
35
|
+
}
|
|
36
|
+
/** Not announced while playing: the readout changes every frame. */
|
|
37
|
+
function Readout({ className, children }) {
|
|
38
|
+
const { playing } = useContext(Context);
|
|
39
|
+
return (_jsx("output", { className: className, "aria-live": playing ? 'off' : 'polite', children: children }));
|
|
40
|
+
}
|
|
41
|
+
/** A normal boundary condition, not a failure: polite status text, nothing when no boundary is reached. */
|
|
42
|
+
function Boundary({ label, className }) {
|
|
43
|
+
if (!label)
|
|
44
|
+
return null;
|
|
45
|
+
return (_jsx(Notice, { as: "span", kind: "info", className: className, children: label }));
|
|
46
|
+
}
|
|
47
|
+
export { Root, Group, Play, Previous, Next, Start, End, Speed, Readout, Boundary };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Histogram, Table } from './VolumeProfileParts.js';
|
|
2
|
+
// The parts live in VolumeProfileParts.js (a client module). The namespace object is built here, outside it, so a server
|
|
3
|
+
// component can write `VolumeProfile.Root`: a property of an object exported from a client module is not reachable there.
|
|
4
|
+
export * from './VolumeProfileParts.js';
|
|
5
|
+
export const VolumeProfile = { Histogram, Table };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VolumeProfile as parts: Histogram, Table. The Histogram goes inside `Chart.Plot` and draws horizontal bars on the
|
|
3
|
+
* right of the price pane, one per price row, lined up with the chart's price scale; lines mark the point of control
|
|
4
|
+
* (the busiest row) and the value area's top and bottom. The Table says the same rows in text.
|
|
5
|
+
*
|
|
6
|
+
* Both read the bars in view, or a fixed `{ from, to }` range of timestamps (inclusive). Give both the same options.
|
|
7
|
+
*/
|
|
8
|
+
export interface VolumeProfileOptionsProps {
|
|
9
|
+
/** Number of price rows. Default 24. */
|
|
10
|
+
rows?: number;
|
|
11
|
+
/** Share of the volume the value area holds. Default 0.7. */
|
|
12
|
+
valueArea?: number;
|
|
13
|
+
/** A fixed range of Unix-millisecond timestamps instead of the bars in view. */
|
|
14
|
+
range?: {
|
|
15
|
+
from: number;
|
|
16
|
+
to: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface VolumeProfileHistogramProps extends VolumeProfileOptionsProps {
|
|
20
|
+
/** Width of the longest bar in chart units. Default a quarter of the frame. */
|
|
21
|
+
width?: number;
|
|
22
|
+
/** Draw the POC, VAH and VAL lines across the plot. Default true. */
|
|
23
|
+
lines?: boolean;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Horizontal bars growing left from the right edge. Each row is a `<g data-part="profile-row">` with an up and a down
|
|
28
|
+
* rect (`data-change`); `data-value-area` marks rows inside the value area and `data-poc` the busiest row.
|
|
29
|
+
*/
|
|
30
|
+
declare function Histogram({ width, lines, className, ...options }: VolumeProfileHistogramProps): import("react").JSX.Element | null;
|
|
31
|
+
/** The rows as a table, highest price first: price range, volume and share of the total. */
|
|
32
|
+
declare function Table({ className, caption, ...options }: VolumeProfileOptionsProps & {
|
|
33
|
+
className?: string;
|
|
34
|
+
caption?: string;
|
|
35
|
+
}): import("react").JSX.Element;
|
|
36
|
+
export { Histogram, Table };
|
|
37
|
+
//# sourceMappingURL=VolumeProfileParts.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { useChart } from './ChartParts.js';
|
|
5
|
+
import { indexRangeOfTime, volumeProfile } from './core/volume-profile.js';
|
|
6
|
+
function useProfile({ rows, valueArea, range }) {
|
|
7
|
+
const { bars, frame } = useChart('VolumeProfile');
|
|
8
|
+
// The length is a dependency too: a host that pushes onto the same array must still get the new bars counted.
|
|
9
|
+
const fixed = useMemo(() => (range ? indexRangeOfTime(bars, range.from, range.to) : null), [bars, bars.length, range?.from, range?.to]);
|
|
10
|
+
const first = fixed ? fixed.first : frame.first;
|
|
11
|
+
const last = fixed ? fixed.last : frame.last;
|
|
12
|
+
return useMemo(() => volumeProfile(bars, { first, last, rows, valueArea }), [bars, bars.length, first, last, rows, valueArea]);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Horizontal bars growing left from the right edge. Each row is a `<g data-part="profile-row">` with an up and a down
|
|
16
|
+
* rect (`data-change`); `data-value-area` marks rows inside the value area and `data-poc` the busiest row.
|
|
17
|
+
*/
|
|
18
|
+
function Histogram({ width, lines = true, className, ...options }) {
|
|
19
|
+
const { frame, readoutId } = useChart('VolumeProfile.Histogram');
|
|
20
|
+
const result = useProfile(options);
|
|
21
|
+
if (result.rows.length === 0 || result.poc < 0)
|
|
22
|
+
return null;
|
|
23
|
+
const longest = Math.max(1, Math.min(frame.plotWidth, Number.isFinite(width) ? Math.max(0, width) : frame.plotWidth * 0.25));
|
|
24
|
+
const peak = result.rows[result.poc].volume;
|
|
25
|
+
const [vaLow, vaHigh] = result.valueAreaRows;
|
|
26
|
+
const right = frame.plotWidth;
|
|
27
|
+
const nodes = [];
|
|
28
|
+
result.rows.forEach((row, i) => {
|
|
29
|
+
const top = frame.y(row.high);
|
|
30
|
+
const height = Math.max(1, frame.y(row.low) - top);
|
|
31
|
+
const upWidth = (row.upVolume / peak) * longest;
|
|
32
|
+
const downWidth = (row.downVolume / peak) * longest;
|
|
33
|
+
nodes.push(_jsxs("g", { "data-part": "profile-row", "data-value-area": i >= vaLow && i <= vaHigh ? '' : undefined, "data-poc": i === result.poc ? '' : undefined, children: [_jsx("rect", { "data-change": "up", x: right - upWidth, y: top, width: upWidth, height: height }), _jsx("rect", { "data-change": "down", x: right - upWidth - downWidth, y: top, width: downWidth, height: height })] }, i));
|
|
34
|
+
});
|
|
35
|
+
const line = (part, price) => {
|
|
36
|
+
if (price === null || !Number.isFinite(price))
|
|
37
|
+
return null;
|
|
38
|
+
const y = frame.y(price);
|
|
39
|
+
return _jsx("line", { "data-part": part, x1: 0, x2: frame.plotWidth, y1: y, y2: y });
|
|
40
|
+
};
|
|
41
|
+
const poc = result.rows[result.poc];
|
|
42
|
+
return (_jsxs("g", { className: className, "data-part": "volume-profile", clipPath: `url(#${readoutId}-price)`, "aria-hidden": "true", children: [nodes, lines && line('poc', (poc.low + poc.high) / 2), lines && line('vah', result.vah), lines && line('val', result.val)] }));
|
|
43
|
+
}
|
|
44
|
+
const volumeText = (v) => String(Math.round(v * 100) / 100);
|
|
45
|
+
/** The rows as a table, highest price first: price range, volume and share of the total. */
|
|
46
|
+
function Table({ className, caption = 'Volume by price', ...options }) {
|
|
47
|
+
const { decimals } = useChart('VolumeProfile.Table');
|
|
48
|
+
const result = useProfile(options);
|
|
49
|
+
const [vaLow, vaHigh] = result.valueAreaRows;
|
|
50
|
+
const body = [];
|
|
51
|
+
for (let i = result.rows.length - 1; i >= 0; i--) {
|
|
52
|
+
const row = result.rows[i];
|
|
53
|
+
body.push(_jsxs("tr", { "data-value-area": i >= vaLow && i <= vaHigh ? '' : undefined, "data-poc": i === result.poc ? '' : undefined, children: [_jsxs("td", { "data-mt-numeric": true, children: [row.low.toFixed(decimals), " \u2013 ", row.high.toFixed(decimals)] }), _jsx("td", { "data-mt-numeric": true, children: volumeText(row.volume) }), _jsx("td", { "data-mt-numeric": true, children: result.total > 0 ? `${((row.volume / result.total) * 100).toFixed(1)}%` : '–' })] }, i));
|
|
54
|
+
}
|
|
55
|
+
return (_jsxs("table", { className: className, children: [_jsx("caption", { children: caption }), _jsx("thead", { children: _jsx("tr", { children: ['Price', 'Volume', 'Share'].map((h) => _jsx("th", { scope: "col", children: h }, h)) }) }), _jsx("tbody", { children: body })] }));
|
|
56
|
+
}
|
|
57
|
+
export { Histogram, Table };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The accessible-name rule. A control that is an icon and nothing else still names its action in words: the
|
|
4
|
+
* words are the `aria-label`, and the icon is a decorative `data-part='icon'` span that assistive technology skips. No
|
|
5
|
+
* icon ships in this package; `icon` is a slot the host fills.
|
|
6
|
+
*/
|
|
7
|
+
export interface NameProps {
|
|
8
|
+
/** A graphic for the button, drawn before any words. It is hidden from assistive technology, so give the button words too. */
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
/** The action in words. It is the visible text unless `hideLabel` is set, and always the accessible name. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Show only the icon. The `label` then becomes the `aria-label`. Needs `icon`. */
|
|
13
|
+
hideLabel?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* What a button shows and what it is called. `fallback` is the part's own word ("Lock", "Reset view"). Visible text is
|
|
17
|
+
* `children`, else `label`, else `fallback`; with `hideLabel` there is none and the name moves to `aria-label`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buttonFace(fallback: string | undefined, { icon, label, hideLabel, children, ariaLabel }: NameProps & {
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
}): {
|
|
23
|
+
'aria-label': string | undefined;
|
|
24
|
+
content: ReactNode;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=accessible-name.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* What a button shows and what it is called. `fallback` is the part's own word ("Lock", "Reset view"). Visible text is
|
|
4
|
+
* `children`, else `label`, else `fallback`; with `hideLabel` there is none and the name moves to `aria-label`.
|
|
5
|
+
*/
|
|
6
|
+
export function buttonFace(fallback, { icon, label, hideLabel, children, ariaLabel }) {
|
|
7
|
+
const hasIcon = icon !== undefined && icon !== null && icon !== false;
|
|
8
|
+
const visible = hideLabel && hasIcon ? null : (children ?? label ?? fallback ?? null);
|
|
9
|
+
const name = ariaLabel ?? label ?? (typeof children === 'string' ? children : fallback);
|
|
10
|
+
return {
|
|
11
|
+
'aria-label': visible === null || visible === false || visible === '' ? name : ariaLabel,
|
|
12
|
+
content: (_jsxs(_Fragment, { children: [hasIcon && _jsx("span", { "aria-hidden": "true", "data-part": "icon", children: icon }), visible] })),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
export * from './core/attributes.js';
|
|
3
|
+
/**
|
|
4
|
+
* A column width that holds the widest value without growing: set the result as the element's style and give
|
|
5
|
+
* the cells `data-mt-numeric`. Counts characters of the longest sample (digits, separator, sign), in `ch`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function numericWidth(...samples: readonly string[]): CSSProperties;
|
|
8
|
+
//# sourceMappingURL=attributes.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { numericWidthValue } from './core/attributes.js';
|
|
2
|
+
export * from './core/attributes.js';
|
|
3
|
+
/**
|
|
4
|
+
* A column width that holds the widest value without growing: set the result as the element's style and give
|
|
5
|
+
* the cells `data-mt-numeric`. Counts characters of the longest sample (digits, separator, sign), in `ch`.
|
|
6
|
+
*/
|
|
7
|
+
export function numericWidth(...samples) {
|
|
8
|
+
return { ['--mt-numeric-width']: numericWidthValue(...samples) };
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type CsvBarsError, type CsvBarsOptions } from './csv-bars.js';
|
|
2
|
+
import type { CandleBar } from './chart-scale.js';
|
|
3
|
+
import type { DataSeries, SeriesSet } from './series.js';
|
|
4
|
+
/** A problem found while reading. `line` is 1-based in the source; 0 means the source as a whole. */
|
|
5
|
+
export type AdapterIssue = CsvBarsError;
|
|
6
|
+
export interface AdapterResult {
|
|
7
|
+
/** Who read it, shown beside the data so a person can tell where it came from. */
|
|
8
|
+
adapter: {
|
|
9
|
+
name: string;
|
|
10
|
+
version: string;
|
|
11
|
+
};
|
|
12
|
+
bars: CandleBar[];
|
|
13
|
+
/** Series beside the bars, on the bars' timestamps. */
|
|
14
|
+
series: DataSeries[];
|
|
15
|
+
errors: AdapterIssue[];
|
|
16
|
+
/** Things that were assumed or changed on the way in, in words a status line can show. */
|
|
17
|
+
notes: string[];
|
|
18
|
+
}
|
|
19
|
+
/** The shape every adapter has: a name, a version and a `read` that never throws. */
|
|
20
|
+
export interface DataAdapter<Input, Options = undefined> {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
read(input: Input, options?: Options): AdapterResult;
|
|
24
|
+
}
|
|
25
|
+
export type CsvAdapterOptions = Pick<CsvBarsOptions, 'maxRows' | 'utcOffsetMinutes'>;
|
|
26
|
+
/**
|
|
27
|
+
* Reads a CSV of bars and any all-number columns beside them. Times without a zone are UTC unless `utcOffsetMinutes`
|
|
28
|
+
* says otherwise, and the note says which was used. An extra column's empty cell (or `-`, `NA`, `NaN`, `null`) is an
|
|
29
|
+
* `unknown` point, because the file does not say why it is empty; a `0` is the value 0. A column with any other text in
|
|
30
|
+
* it is not a series and is named in the notes.
|
|
31
|
+
*/
|
|
32
|
+
export declare const csvAdapter: DataAdapter<string, CsvAdapterOptions>;
|
|
33
|
+
/**
|
|
34
|
+
* The result as one set, with the bars' close as the primary series (`bars`), so `report` can say where another
|
|
35
|
+
* series has no point or a different kind of gap. The bars must be in time order (every adapter's are).
|
|
36
|
+
*/
|
|
37
|
+
export declare function toSeriesSet(result: AdapterResult): SeriesSet;
|
|
38
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// An adapter turns something a person has (a file, later a feed) into what the parts draw: bars, any series beside
|
|
2
|
+
// them, and the problems it found, named by line. It is a plain function over its input: it reads, never repairs,
|
|
3
|
+
// never throws, and never writes a zero where the source said nothing. `csvAdapter` is the first; a host can write
|
|
4
|
+
// its own to the same shape and check the result with `seriesSetSchema`.
|
|
5
|
+
import { parseBarsCsv } from './csv-bars.js';
|
|
6
|
+
const NOTES = {
|
|
7
|
+
sorted: 'The rows were out of time order, so they were sorted.',
|
|
8
|
+
'no-volume': 'There is no volume column, so volume is 0.',
|
|
9
|
+
'no-header': 'There was no header row, so the columns were read as time, open, high, low, close, volume.',
|
|
10
|
+
};
|
|
11
|
+
/** A header turned into an id: lower case, words joined by a dash, and a number added when two would collide. */
|
|
12
|
+
function idFromHeader(header, taken) {
|
|
13
|
+
const base = header.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'column';
|
|
14
|
+
let id = base;
|
|
15
|
+
for (let n = 2; taken.has(id); n++)
|
|
16
|
+
id = `${base}-${n}`;
|
|
17
|
+
taken.add(id);
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reads a CSV of bars and any all-number columns beside them. Times without a zone are UTC unless `utcOffsetMinutes`
|
|
22
|
+
* says otherwise, and the note says which was used. An extra column's empty cell (or `-`, `NA`, `NaN`, `null`) is an
|
|
23
|
+
* `unknown` point, because the file does not say why it is empty; a `0` is the value 0. A column with any other text in
|
|
24
|
+
* it is not a series and is named in the notes.
|
|
25
|
+
*/
|
|
26
|
+
export const csvAdapter = {
|
|
27
|
+
name: 'csv',
|
|
28
|
+
version: '1',
|
|
29
|
+
read(text, options = {}) {
|
|
30
|
+
const given = options.utcOffsetMinutes;
|
|
31
|
+
if (given !== undefined && !(Number.isInteger(given) && given >= -720 && given <= 840)) {
|
|
32
|
+
return { adapter: { name: 'csv', version: '1' }, bars: [], series: [], errors: [{ line: 0, message: `utcOffsetMinutes must be a whole number of minutes from -720 to 840, not ${String(given)}, so nothing was read.` }], notes: [] };
|
|
33
|
+
}
|
|
34
|
+
const parsed = parseBarsCsv(text, { ...options, extraColumns: true });
|
|
35
|
+
const offset = Number.isFinite(options.utcOffsetMinutes) ? options.utcOffsetMinutes : 0;
|
|
36
|
+
const notes = parsed.notes.map((n) => NOTES[n]);
|
|
37
|
+
if (parsed.bars.length > 0 && (parsed.zoneless ?? 0) > 0)
|
|
38
|
+
notes.push(offset === 0 ? 'Times with no zone were read as UTC.' : `Times with no zone were read as UTC${offset > 0 ? '+' : '-'}${Math.floor(Math.abs(offset) / 60)}:${String(Math.abs(offset) % 60).padStart(2, '0')}.`);
|
|
39
|
+
for (const h of parsed.skippedColumns ?? [])
|
|
40
|
+
notes.push(`Column "${h}" has a cell that is not a number or a no-value word (blank, -, NA, NaN, null), so it was left out and is not a series.`);
|
|
41
|
+
const taken = new Set(['bars']);
|
|
42
|
+
const series = (parsed.extras ?? []).map((col) => {
|
|
43
|
+
const points = parsed.bars.map((bar, i) => {
|
|
44
|
+
const t = new Date(bar.timestamp).toISOString();
|
|
45
|
+
const v = col.values[i];
|
|
46
|
+
return typeof v === 'number' ? { t, kind: 'value', v } : { t, kind: 'unknown' };
|
|
47
|
+
});
|
|
48
|
+
return { id: idFromHeader(col.header, taken), label: col.header, points };
|
|
49
|
+
});
|
|
50
|
+
return { adapter: { name: 'csv', version: '1' }, bars: parsed.bars, series, errors: parsed.errors, notes };
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The result as one set, with the bars' close as the primary series (`bars`), so `report` can say where another
|
|
55
|
+
* series has no point or a different kind of gap. The bars must be in time order (every adapter's are).
|
|
56
|
+
*/
|
|
57
|
+
export function toSeriesSet(result) {
|
|
58
|
+
const bars = {
|
|
59
|
+
id: 'bars',
|
|
60
|
+
label: 'Close',
|
|
61
|
+
points: result.bars.map((b) => ({ t: new Date(b.timestamp).toISOString(), kind: 'value', v: b.close })),
|
|
62
|
+
};
|
|
63
|
+
return { schemaVersion: '0', alignment: { mode: 'exact', primary: 'bars' }, series: [bars, ...result.series] };
|
|
64
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data-attribute vocabulary every primitive speaks. State lives in `data-*`
|
|
3
|
+
* attributes, never in class names, so a host styles with `[data-side="bid"]` and the tokens in
|
|
4
|
+
* `tokens.css`. Values are the whole vocabulary: a part never invents another.
|
|
5
|
+
*/
|
|
6
|
+
export type Side = 'bid' | 'ask';
|
|
7
|
+
/** Movement since the previous step. Not the diff status below: `data-change` is movement, `data-diff` is difference. */
|
|
8
|
+
export type Change = 'up' | 'down' | 'flat';
|
|
9
|
+
/** How a level differs between two books; the report's `change` field, as an attribute. */
|
|
10
|
+
export type DiffStatus = 'added' | 'removed' | 'modified';
|
|
11
|
+
export type Quality = 'ok' | 'gap' | 'stale' | 'truncated';
|
|
12
|
+
export type CursorMark = 'a' | 'b';
|
|
13
|
+
export interface PrimitiveState {
|
|
14
|
+
side?: Side;
|
|
15
|
+
change?: Change;
|
|
16
|
+
diff?: DiffStatus;
|
|
17
|
+
quality?: Quality;
|
|
18
|
+
cursor?: CursorMark;
|
|
19
|
+
/** True when the value comes from a synthetic session. */
|
|
20
|
+
synthetic?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export type PrimitiveAttributes = {
|
|
23
|
+
/** Scopes tokens.css to primitive markup. */
|
|
24
|
+
'data-mt': '';
|
|
25
|
+
'data-side'?: Side;
|
|
26
|
+
'data-change'?: Change;
|
|
27
|
+
'data-diff'?: DiffStatus;
|
|
28
|
+
'data-quality'?: Quality;
|
|
29
|
+
'data-cursor'?: CursorMark;
|
|
30
|
+
'data-synthetic'?: '';
|
|
31
|
+
};
|
|
32
|
+
/** Spreads onto a DOM element. Unset states emit nothing, so an attribute selector never matches by accident. */
|
|
33
|
+
export declare function primitiveAttributes(state: PrimitiveState): PrimitiveAttributes;
|
|
34
|
+
/** The width that holds the widest sample without growing, in `ch`: the value for `--mt-numeric-width`. */
|
|
35
|
+
export declare function numericWidthValue(...samples: readonly string[]): string;
|
|
36
|
+
//# sourceMappingURL=attributes.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Spreads onto a DOM element. Unset states emit nothing, so an attribute selector never matches by accident. */
|
|
2
|
+
export function primitiveAttributes(state) {
|
|
3
|
+
const out = { 'data-mt': '' };
|
|
4
|
+
if (state.side)
|
|
5
|
+
out['data-side'] = state.side;
|
|
6
|
+
if (state.change)
|
|
7
|
+
out['data-change'] = state.change;
|
|
8
|
+
if (state.diff)
|
|
9
|
+
out['data-diff'] = state.diff;
|
|
10
|
+
if (state.quality)
|
|
11
|
+
out['data-quality'] = state.quality;
|
|
12
|
+
if (state.cursor)
|
|
13
|
+
out['data-cursor'] = state.cursor;
|
|
14
|
+
if (state.synthetic)
|
|
15
|
+
out['data-synthetic'] = '';
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
/** The width that holds the widest sample without growing, in `ch`: the value for `--mt-numeric-width`. */
|
|
19
|
+
export function numericWidthValue(...samples) {
|
|
20
|
+
const longest = samples.reduce((n, s) => Math.max(n, s.length), 0);
|
|
21
|
+
return `${longest}ch`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CandleBar } from './chart-scale.js';
|
|
2
|
+
/** Heikin Ashi bars from ordinary ones: each bar averages its own prices and the previous Heikin Ashi bar. */
|
|
3
|
+
export declare function heikinAshi(bars: readonly CandleBar[]): CandleBar[];
|
|
4
|
+
/** The default time-axis label: HH:MM in UTC, with the month and day when the day changes from the previous label. */
|
|
5
|
+
export declare const formatBarTime: (t: number, previous: number | null) => string;
|
|
6
|
+
//# sourceMappingURL=candles.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Heikin Ashi bars from ordinary ones: each bar averages its own prices and the previous Heikin Ashi bar. */
|
|
2
|
+
export function heikinAshi(bars) {
|
|
3
|
+
const out = [];
|
|
4
|
+
for (let i = 0; i < bars.length; i++) {
|
|
5
|
+
const b = bars[i];
|
|
6
|
+
const close = (b.open + b.high + b.low + b.close) / 4;
|
|
7
|
+
const open = i === 0 ? (b.open + b.close) / 2 : (out[i - 1].open + out[i - 1].close) / 2;
|
|
8
|
+
out.push({ timestamp: b.timestamp, open, close, high: Math.max(b.high, open, close), low: Math.min(b.low, open, close), volume: b.volume });
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
13
|
+
/** The default time-axis label: HH:MM in UTC, with the month and day when the day changes from the previous label. */
|
|
14
|
+
export const formatBarTime = (t, previous) => {
|
|
15
|
+
const d = new Date(t);
|
|
16
|
+
const time = `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
17
|
+
const sameDay = previous !== null && new Date(previous).toISOString().slice(0, 10) === d.toISOString().slice(0, 10);
|
|
18
|
+
return sameDay ? time : `${d.toISOString().slice(5, 10)} ${time}`;
|
|
19
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chart's pointer and wheel gestures as plain data in, plain data out: press and drag to pan, a drag on the price
|
|
3
|
+
* labels, the wheel, a two-finger pinch and the double tap that gives the price back to the bars. The host reads
|
|
4
|
+
* coordinates off its DOM events, passes them in with the frame, viewport and bar count of the moment, and applies the
|
|
5
|
+
* change the result names. Nothing here touches a DOM, so the rules run and test without one.
|
|
6
|
+
*/
|
|
7
|
+
import { type ChartFrame, type ChartViewport, type PriceSpan } from './chart-scale.js';
|
|
8
|
+
export interface ChartPoint {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
/** What the chart looks like at the moment of an event: the host reads it from its current render. */
|
|
13
|
+
export interface ChartGestureView {
|
|
14
|
+
frame: ChartFrame;
|
|
15
|
+
viewport: ChartViewport;
|
|
16
|
+
/** Number of bars in the series. */
|
|
17
|
+
total: number;
|
|
18
|
+
/** Lowest and highest price of all the bars, or null for none; what a fixed price range is kept near. */
|
|
19
|
+
dataSpan: PriceSpan | null;
|
|
20
|
+
/** How far down the pointer still picks a bar: the bottom of the last panel. Default `frame.plotHeight` (one panel). The price is still the first panel's. */
|
|
21
|
+
hoverBottom?: number;
|
|
22
|
+
}
|
|
23
|
+
interface DragState {
|
|
24
|
+
id: number;
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
viewport: ChartViewport;
|
|
28
|
+
touch: boolean;
|
|
29
|
+
decided: boolean;
|
|
30
|
+
vertical: boolean;
|
|
31
|
+
price: {
|
|
32
|
+
range: PriceSpan;
|
|
33
|
+
perPixel: number;
|
|
34
|
+
moved: boolean;
|
|
35
|
+
} | null;
|
|
36
|
+
}
|
|
37
|
+
/** A pinch keeps the frame, viewport and range it began with, so it is measured against where the fingers started. */
|
|
38
|
+
interface PinchState {
|
|
39
|
+
ids: [number, number];
|
|
40
|
+
frame: ChartFrame;
|
|
41
|
+
viewport: ChartViewport;
|
|
42
|
+
range: PriceSpan;
|
|
43
|
+
mid: ChartPoint;
|
|
44
|
+
dx: number;
|
|
45
|
+
dy: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ChartGestureState {
|
|
48
|
+
/** Fingers down now, in the order they landed. */
|
|
49
|
+
touches: ReadonlyArray<{
|
|
50
|
+
id: number;
|
|
51
|
+
} & ChartPoint>;
|
|
52
|
+
drag: DragState | null;
|
|
53
|
+
pinch: PinchState | null;
|
|
54
|
+
lastTap: {
|
|
55
|
+
time: number;
|
|
56
|
+
x: number;
|
|
57
|
+
y: number;
|
|
58
|
+
} | null;
|
|
59
|
+
}
|
|
60
|
+
export declare const INITIAL_CHART_GESTURE: ChartGestureState;
|
|
61
|
+
export type ChartGestureEvent =
|
|
62
|
+
/** A primary-button press. `touch` is whether the pointer is a finger. */
|
|
63
|
+
{
|
|
64
|
+
type: 'down';
|
|
65
|
+
pointerId: number;
|
|
66
|
+
touch: boolean;
|
|
67
|
+
point: ChartPoint;
|
|
68
|
+
view: ChartGestureView;
|
|
69
|
+
} | {
|
|
70
|
+
type: 'move';
|
|
71
|
+
pointerId: number;
|
|
72
|
+
point: ChartPoint;
|
|
73
|
+
view: ChartGestureView;
|
|
74
|
+
}
|
|
75
|
+
/** `time` is the event timestamp in milliseconds; it times the double tap. */
|
|
76
|
+
| {
|
|
77
|
+
type: 'up';
|
|
78
|
+
pointerId: number;
|
|
79
|
+
time: number;
|
|
80
|
+
} | {
|
|
81
|
+
type: 'cancel';
|
|
82
|
+
pointerId: number;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'wheel';
|
|
85
|
+
point: ChartPoint;
|
|
86
|
+
deltaY: number;
|
|
87
|
+
view: ChartGestureView;
|
|
88
|
+
};
|
|
89
|
+
export interface ChartGestureResult {
|
|
90
|
+
state: ChartGestureState;
|
|
91
|
+
/** A new viewport to apply, if any. */
|
|
92
|
+
viewport?: ChartViewport;
|
|
93
|
+
/** A price range to fix, or null to give the price back to the bars; absent means leave it. */
|
|
94
|
+
priceRange?: PriceSpan | null;
|
|
95
|
+
/** The pointer is hovering the plot: the bar under it (absent leaves the crosshair bar) and its price (null when not over the price pane). */
|
|
96
|
+
hover?: {
|
|
97
|
+
bar?: number;
|
|
98
|
+
price: number | null;
|
|
99
|
+
};
|
|
100
|
+
/** Take the pointer, so the drag continues outside the chart. */
|
|
101
|
+
capture?: boolean;
|
|
102
|
+
/** The host should stop the browser's own handling (page scroll). */
|
|
103
|
+
preventDefault?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/** Whether a one-pointer drag (a pan or a price-label drag) is in progress. A two-finger pinch is separate: read `state.pinch`. The Plot keeps the hover price while a drag runs, as before the reducer moved to core. */
|
|
106
|
+
export declare function isChartGestureActive(state: ChartGestureState): boolean;
|
|
107
|
+
export declare function reduceChartGesture(event: ChartGestureEvent, state: ChartGestureState): ChartGestureResult;
|
|
108
|
+
export {};
|
|
109
|
+
//# sourceMappingURL=chart-gesture.d.ts.map
|