@zuttora/charts 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +48 -2
- package/dist/chart.d.ts +8 -0
- package/dist/chart.js +214 -0
- package/dist/chartState.d.ts +41 -0
- package/dist/chartState.js +59 -0
- package/dist/core/emitter.d.ts +8 -0
- package/dist/core/emitter.js +26 -0
- package/dist/core/format.d.ts +9 -0
- package/dist/core/format.js +35 -0
- package/dist/core/heikinAshi.d.ts +2 -0
- package/dist/core/heikinAshi.js +14 -0
- package/dist/core/priceScale.d.ts +22 -0
- package/dist/core/priceScale.js +92 -0
- package/dist/core/series.d.ts +41 -0
- package/dist/core/series.js +221 -0
- package/dist/core/time.d.ts +25 -0
- package/dist/core/time.js +44 -0
- package/dist/core/timeLabels.d.ts +11 -0
- package/dist/core/timeLabels.js +52 -0
- package/dist/core/types.d.ts +113 -0
- package/dist/core/types.js +1 -0
- package/dist/core/viewport.d.ts +41 -0
- package/dist/core/viewport.js +116 -0
- package/dist/core/warn.d.ts +1 -0
- package/dist/core/warn.js +3 -0
- package/dist/createChart.d.ts +2 -0
- package/dist/createChart.js +7 -0
- package/dist/dom/environment.d.ts +11 -0
- package/dist/dom/environment.js +49 -0
- package/dist/dom/legend.d.ts +14 -0
- package/dist/dom/legend.js +53 -0
- package/dist/dom/legendSync.d.ts +5 -0
- package/dist/dom/legendSync.js +23 -0
- package/dist/dom/root.d.ts +9 -0
- package/dist/dom/root.js +19 -0
- package/dist/host.d.ts +32 -0
- package/dist/host.js +62 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/interact/attach.d.ts +2 -0
- package/dist/interact/attach.js +127 -0
- package/dist/interact/gestures.d.ts +47 -0
- package/dist/interact/gestures.js +179 -0
- package/dist/interact/keyboard.d.ts +19 -0
- package/dist/interact/keyboard.js +34 -0
- package/dist/interact/wheel.d.ts +15 -0
- package/dist/interact/wheel.js +18 -0
- package/dist/render/bars.d.ts +17 -0
- package/dist/render/bars.js +55 -0
- package/dist/render/canvas.d.ts +2 -0
- package/dist/render/canvas.js +13 -0
- package/dist/render/drawAxes.d.ts +2 -0
- package/dist/render/drawAxes.js +34 -0
- package/dist/render/drawCandles.d.ts +3 -0
- package/dist/render/drawCandles.js +68 -0
- package/dist/render/drawCrosshair.d.ts +7 -0
- package/dist/render/drawCrosshair.js +39 -0
- package/dist/render/drawGrid.d.ts +2 -0
- package/dist/render/drawGrid.js +22 -0
- package/dist/render/drawLastPrice.d.ts +10 -0
- package/dist/render/drawLastPrice.js +53 -0
- package/dist/render/drawLine.d.ts +3 -0
- package/dist/render/drawLine.js +43 -0
- package/dist/render/drawMain.d.ts +2 -0
- package/dist/render/drawMain.js +30 -0
- package/dist/render/drawMarkers.d.ts +2 -0
- package/dist/render/drawMarkers.js +39 -0
- package/dist/render/drawPriceLines.d.ts +4 -0
- package/dist/render/drawPriceLines.js +60 -0
- package/dist/render/frame.d.ts +53 -0
- package/dist/render/frame.js +5 -0
- package/dist/render/frameBuilder.d.ts +12 -0
- package/dist/render/frameBuilder.js +119 -0
- package/dist/render/layout.d.ts +4 -0
- package/dist/render/layout.js +26 -0
- package/dist/render/paintFrame.d.ts +11 -0
- package/dist/render/paintFrame.js +30 -0
- package/dist/render/scheduler.d.ts +17 -0
- package/dist/render/scheduler.js +38 -0
- package/dist/render/tween.d.ts +12 -0
- package/dist/render/tween.js +38 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +16 -0
- package/package.json +37 -4
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { bucketStart } from "./time.js";
|
|
2
|
+
import { warn } from "./warn.js";
|
|
3
|
+
export const MAX_CLOSED = 5000;
|
|
4
|
+
function isValid(k) {
|
|
5
|
+
return (Number.isFinite(k.t) &&
|
|
6
|
+
Number.isFinite(k.o) &&
|
|
7
|
+
Number.isFinite(k.h) &&
|
|
8
|
+
Number.isFinite(k.l) &&
|
|
9
|
+
Number.isFinite(k.c) &&
|
|
10
|
+
k.l <= Math.min(k.o, k.c) &&
|
|
11
|
+
k.h >= Math.max(k.o, k.c));
|
|
12
|
+
}
|
|
13
|
+
export class CandleSeries {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.t = [];
|
|
16
|
+
this.o = [];
|
|
17
|
+
this.h = [];
|
|
18
|
+
this.l = [];
|
|
19
|
+
this.c = [];
|
|
20
|
+
this.forming = null;
|
|
21
|
+
this.hasMore = false;
|
|
22
|
+
}
|
|
23
|
+
get closedCount() {
|
|
24
|
+
return this.t.length;
|
|
25
|
+
}
|
|
26
|
+
get count() {
|
|
27
|
+
return this.t.length + (this.forming ? 1 : 0);
|
|
28
|
+
}
|
|
29
|
+
lastIndex() {
|
|
30
|
+
return this.count - 1;
|
|
31
|
+
}
|
|
32
|
+
candleAt(i) {
|
|
33
|
+
if (i === this.t.length && this.forming)
|
|
34
|
+
return { ...this.forming };
|
|
35
|
+
return { t: this.t[i], o: this.o[i], h: this.h[i], l: this.l[i], c: this.c[i] };
|
|
36
|
+
}
|
|
37
|
+
timeAt(i) {
|
|
38
|
+
return i === this.t.length && this.forming ? this.forming.t : this.t[i];
|
|
39
|
+
}
|
|
40
|
+
closeAt(i) {
|
|
41
|
+
return i === this.t.length && this.forming ? this.forming.c : this.c[i];
|
|
42
|
+
}
|
|
43
|
+
toArray() {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (let i = 0; i < this.count; i++)
|
|
46
|
+
out.push(this.candleAt(i));
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
setData(closed, forming, hasMore) {
|
|
50
|
+
this.t = [];
|
|
51
|
+
this.o = [];
|
|
52
|
+
this.h = [];
|
|
53
|
+
this.l = [];
|
|
54
|
+
this.c = [];
|
|
55
|
+
let dropped = 0;
|
|
56
|
+
let prevT = -Infinity;
|
|
57
|
+
for (const k of closed) {
|
|
58
|
+
if (!isValid(k) || k.t <= prevT) {
|
|
59
|
+
dropped++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
this.push(k);
|
|
63
|
+
prevT = k.t;
|
|
64
|
+
}
|
|
65
|
+
if (forming && (!isValid(forming) || forming.t <= prevT)) {
|
|
66
|
+
dropped++;
|
|
67
|
+
forming = null;
|
|
68
|
+
}
|
|
69
|
+
this.forming = forming ? { t: forming.t, o: forming.o, h: forming.h, l: forming.l, c: forming.c } : null;
|
|
70
|
+
this.hasMore = hasMore;
|
|
71
|
+
if (this.t.length > MAX_CLOSED) {
|
|
72
|
+
this.dropOldest(this.t.length - MAX_CLOSED);
|
|
73
|
+
this.hasMore = false;
|
|
74
|
+
}
|
|
75
|
+
if (dropped > 0)
|
|
76
|
+
warn(`dropped ${dropped} invalid candle(s)`);
|
|
77
|
+
}
|
|
78
|
+
prependData(older, hasMore) {
|
|
79
|
+
const oldest = this.count > 0 ? this.timeAt(0) : Infinity;
|
|
80
|
+
const keep = [];
|
|
81
|
+
let dropped = 0;
|
|
82
|
+
let prevT = -Infinity;
|
|
83
|
+
for (const k of older) {
|
|
84
|
+
if (!isValid(k) || k.t >= oldest || k.t <= prevT) {
|
|
85
|
+
dropped++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
keep.push(k);
|
|
89
|
+
prevT = k.t;
|
|
90
|
+
}
|
|
91
|
+
if (dropped > 0)
|
|
92
|
+
warn(`dropped ${dropped} invalid older candle(s)`);
|
|
93
|
+
this.hasMore = hasMore;
|
|
94
|
+
let room = MAX_CLOSED - this.t.length;
|
|
95
|
+
if (room < 0)
|
|
96
|
+
room = 0;
|
|
97
|
+
let added = keep;
|
|
98
|
+
if (added.length > room) {
|
|
99
|
+
added = added.slice(added.length - room);
|
|
100
|
+
this.hasMore = false;
|
|
101
|
+
}
|
|
102
|
+
if (added.length === 0)
|
|
103
|
+
return 0;
|
|
104
|
+
this.t = added.map((k) => k.t).concat(this.t);
|
|
105
|
+
this.o = added.map((k) => k.o).concat(this.o);
|
|
106
|
+
this.h = added.map((k) => k.h).concat(this.h);
|
|
107
|
+
this.l = added.map((k) => k.l).concat(this.l);
|
|
108
|
+
this.c = added.map((k) => k.c).concat(this.c);
|
|
109
|
+
return added.length;
|
|
110
|
+
}
|
|
111
|
+
replaceClosed(candles) {
|
|
112
|
+
let changed = 0;
|
|
113
|
+
for (const k of candles) {
|
|
114
|
+
if (!isValid(k))
|
|
115
|
+
continue;
|
|
116
|
+
const i = this.closedIndexOf(k.t);
|
|
117
|
+
if (i < 0)
|
|
118
|
+
continue;
|
|
119
|
+
this.o[i] = k.o;
|
|
120
|
+
this.h[i] = k.h;
|
|
121
|
+
this.l[i] = k.l;
|
|
122
|
+
this.c[i] = k.c;
|
|
123
|
+
changed++;
|
|
124
|
+
}
|
|
125
|
+
return changed;
|
|
126
|
+
}
|
|
127
|
+
applyTick(price, time, intervalMs, offsetMs) {
|
|
128
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
129
|
+
return { kind: 'ignored' };
|
|
130
|
+
const b = bucketStart(time, intervalMs, offsetMs);
|
|
131
|
+
const f = this.forming;
|
|
132
|
+
if (f === null) {
|
|
133
|
+
const lastClosed = this.t.length > 0 ? this.t[this.t.length - 1] : -Infinity;
|
|
134
|
+
if (b <= lastClosed)
|
|
135
|
+
return { kind: 'ignored' };
|
|
136
|
+
this.forming = { t: b, o: price, h: price, l: price, c: price };
|
|
137
|
+
return { kind: 'started' };
|
|
138
|
+
}
|
|
139
|
+
if (b > f.t) {
|
|
140
|
+
const closed = { t: f.t, o: f.o, h: f.h, l: f.l, c: f.c };
|
|
141
|
+
this.push(closed);
|
|
142
|
+
this.forming = { t: b, o: price, h: price, l: price, c: price };
|
|
143
|
+
const over = this.t.length - MAX_CLOSED;
|
|
144
|
+
const shifted = over > 0 ? this.dropOldest(over) : 0;
|
|
145
|
+
if (shifted > 0)
|
|
146
|
+
this.hasMore = false;
|
|
147
|
+
return { kind: 'closed', closed, shifted };
|
|
148
|
+
}
|
|
149
|
+
if (b === f.t) {
|
|
150
|
+
if (price > f.h)
|
|
151
|
+
f.h = price;
|
|
152
|
+
if (price < f.l)
|
|
153
|
+
f.l = price;
|
|
154
|
+
f.c = price;
|
|
155
|
+
return { kind: 'updated' };
|
|
156
|
+
}
|
|
157
|
+
return { kind: 'ignored' };
|
|
158
|
+
}
|
|
159
|
+
visibleExtremes(from, to) {
|
|
160
|
+
const a = Math.max(0, Math.floor(from));
|
|
161
|
+
const z = Math.min(this.lastIndex(), Math.ceil(to));
|
|
162
|
+
if (a > z)
|
|
163
|
+
return null;
|
|
164
|
+
let min = Infinity;
|
|
165
|
+
let max = -Infinity;
|
|
166
|
+
const n = this.t.length;
|
|
167
|
+
for (let i = a; i <= z; i++) {
|
|
168
|
+
const lo = i === n && this.forming ? this.forming.l : this.l[i];
|
|
169
|
+
const hi = i === n && this.forming ? this.forming.h : this.h[i];
|
|
170
|
+
if (lo < min)
|
|
171
|
+
min = lo;
|
|
172
|
+
if (hi > max)
|
|
173
|
+
max = hi;
|
|
174
|
+
}
|
|
175
|
+
return { min, max };
|
|
176
|
+
}
|
|
177
|
+
indexAtTime(time) {
|
|
178
|
+
if (this.count === 0 || time < this.timeAt(0))
|
|
179
|
+
return -1;
|
|
180
|
+
let lo = 0;
|
|
181
|
+
let hi = this.count - 1;
|
|
182
|
+
while (lo < hi) {
|
|
183
|
+
const mid = (lo + hi + 1) >> 1;
|
|
184
|
+
if (this.timeAt(mid) <= time)
|
|
185
|
+
lo = mid;
|
|
186
|
+
else
|
|
187
|
+
hi = mid - 1;
|
|
188
|
+
}
|
|
189
|
+
return lo;
|
|
190
|
+
}
|
|
191
|
+
closedIndexOf(time) {
|
|
192
|
+
let lo = 0;
|
|
193
|
+
let hi = this.t.length - 1;
|
|
194
|
+
while (lo <= hi) {
|
|
195
|
+
const mid = (lo + hi) >> 1;
|
|
196
|
+
const v = this.t[mid];
|
|
197
|
+
if (v === time)
|
|
198
|
+
return mid;
|
|
199
|
+
if (v < time)
|
|
200
|
+
lo = mid + 1;
|
|
201
|
+
else
|
|
202
|
+
hi = mid - 1;
|
|
203
|
+
}
|
|
204
|
+
return -1;
|
|
205
|
+
}
|
|
206
|
+
push(k) {
|
|
207
|
+
this.t.push(k.t);
|
|
208
|
+
this.o.push(k.o);
|
|
209
|
+
this.h.push(k.h);
|
|
210
|
+
this.l.push(k.l);
|
|
211
|
+
this.c.push(k.c);
|
|
212
|
+
}
|
|
213
|
+
dropOldest(n) {
|
|
214
|
+
this.t.splice(0, n);
|
|
215
|
+
this.o.splice(0, n);
|
|
216
|
+
this.h.splice(0, n);
|
|
217
|
+
this.l.splice(0, n);
|
|
218
|
+
this.c.splice(0, n);
|
|
219
|
+
return n;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const IST_OFFSET_MS = 19800000;
|
|
2
|
+
export declare const INTERVAL_MS: {
|
|
3
|
+
readonly '1m': 60000;
|
|
4
|
+
readonly '5m': 300000;
|
|
5
|
+
readonly '10m': 600000;
|
|
6
|
+
readonly '1h': 3600000;
|
|
7
|
+
readonly '3h': 10800000;
|
|
8
|
+
readonly '6h': 21600000;
|
|
9
|
+
readonly '1D': 86400000;
|
|
10
|
+
};
|
|
11
|
+
export type IntervalId = keyof typeof INTERVAL_MS;
|
|
12
|
+
export declare function bucketStart(timeMs: number, intervalMs: number, offsetMs?: number): number;
|
|
13
|
+
export declare function slotEnd(slotStart: number, intervalMs: number): number;
|
|
14
|
+
export interface ClockParts {
|
|
15
|
+
year: number;
|
|
16
|
+
month: number;
|
|
17
|
+
day: number;
|
|
18
|
+
hour: number;
|
|
19
|
+
minute: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function clockParts(timeMs: number, offsetMs?: number): ClockParts;
|
|
22
|
+
export declare function monthName(month: number): string;
|
|
23
|
+
export declare function formatDate(timeMs: number, offsetMs?: number): string;
|
|
24
|
+
export declare function formatClock(timeMs: number, offsetMs?: number): string;
|
|
25
|
+
export declare function formatSlotTime(slotStart: number, intervalMs: number, offsetMs?: number): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const IST_OFFSET_MS = 19800000;
|
|
2
|
+
export const INTERVAL_MS = {
|
|
3
|
+
'1m': 60000,
|
|
4
|
+
'5m': 300000,
|
|
5
|
+
'10m': 600000,
|
|
6
|
+
'1h': 3600000,
|
|
7
|
+
'3h': 10800000,
|
|
8
|
+
'6h': 21600000,
|
|
9
|
+
'1D': 86400000,
|
|
10
|
+
};
|
|
11
|
+
export function bucketStart(timeMs, intervalMs, offsetMs = IST_OFFSET_MS) {
|
|
12
|
+
return Math.floor((timeMs + offsetMs) / intervalMs) * intervalMs - offsetMs;
|
|
13
|
+
}
|
|
14
|
+
export function slotEnd(slotStart, intervalMs) {
|
|
15
|
+
return slotStart + intervalMs;
|
|
16
|
+
}
|
|
17
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
18
|
+
export function clockParts(timeMs, offsetMs = IST_OFFSET_MS) {
|
|
19
|
+
const d = new Date(timeMs + offsetMs);
|
|
20
|
+
return {
|
|
21
|
+
year: d.getUTCFullYear(),
|
|
22
|
+
month: d.getUTCMonth(),
|
|
23
|
+
day: d.getUTCDate(),
|
|
24
|
+
hour: d.getUTCHours(),
|
|
25
|
+
minute: d.getUTCMinutes(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const two = (n) => (n < 10 ? `0${n}` : String(n));
|
|
29
|
+
export function monthName(month) {
|
|
30
|
+
return MONTHS[month] ?? '';
|
|
31
|
+
}
|
|
32
|
+
export function formatDate(timeMs, offsetMs = IST_OFFSET_MS) {
|
|
33
|
+
const p = clockParts(timeMs, offsetMs);
|
|
34
|
+
return `${p.day} ${monthName(p.month)}`;
|
|
35
|
+
}
|
|
36
|
+
export function formatClock(timeMs, offsetMs = IST_OFFSET_MS) {
|
|
37
|
+
const p = clockParts(timeMs, offsetMs);
|
|
38
|
+
return `${two(p.hour)}:${two(p.minute)}`;
|
|
39
|
+
}
|
|
40
|
+
export function formatSlotTime(slotStart, intervalMs, offsetMs = IST_OFFSET_MS) {
|
|
41
|
+
const p = clockParts(slotStart, offsetMs);
|
|
42
|
+
const date = `${p.day} ${monthName(p.month)}`;
|
|
43
|
+
return intervalMs >= INTERVAL_MS['1D'] ? `${date} ${p.year}` : `${date} ${two(p.hour)}:${two(p.minute)}`;
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const MIN_LABEL_GAP = 72;
|
|
2
|
+
export interface LabelBar {
|
|
3
|
+
t: number;
|
|
4
|
+
x: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TimeLabel {
|
|
7
|
+
x: number;
|
|
8
|
+
text: string;
|
|
9
|
+
weight: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function pickTimeLabels(bars: readonly LabelBar[], prevT: number | null, intervalMs: number, offsetMs: number, minGap?: number): TimeLabel[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { INTERVAL_MS, clockParts, monthName } from "./time.js";
|
|
2
|
+
export const MIN_LABEL_GAP = 72;
|
|
3
|
+
const two = (n) => (n < 10 ? `0${n}` : String(n));
|
|
4
|
+
export function pickTimeLabels(bars, prevT, intervalMs, offsetMs, minGap = MIN_LABEL_GAP) {
|
|
5
|
+
const intraday = intervalMs < INTERVAL_MS['1D'];
|
|
6
|
+
const all = [];
|
|
7
|
+
let prev = prevT === null ? null : clockParts(prevT, offsetMs);
|
|
8
|
+
for (const bar of bars) {
|
|
9
|
+
const p = clockParts(bar.t, offsetMs);
|
|
10
|
+
let weight = 1;
|
|
11
|
+
let text = `${two(p.hour)}:${two(p.minute)}`;
|
|
12
|
+
if (prev === null) {
|
|
13
|
+
weight = 3;
|
|
14
|
+
text = `${p.day} ${monthName(p.month)}`;
|
|
15
|
+
}
|
|
16
|
+
else if (p.year !== prev.year) {
|
|
17
|
+
weight = 5;
|
|
18
|
+
text = String(p.year);
|
|
19
|
+
}
|
|
20
|
+
else if (p.month !== prev.month) {
|
|
21
|
+
weight = 4;
|
|
22
|
+
text = monthName(p.month);
|
|
23
|
+
}
|
|
24
|
+
else if (p.day !== prev.day) {
|
|
25
|
+
weight = 3;
|
|
26
|
+
text = `${p.day} ${monthName(p.month)}`;
|
|
27
|
+
}
|
|
28
|
+
else if (intraday && p.hour !== prev.hour) {
|
|
29
|
+
weight = 2;
|
|
30
|
+
text = `${two(p.hour)}:00`;
|
|
31
|
+
}
|
|
32
|
+
all.push({ x: bar.x, text, weight });
|
|
33
|
+
prev = p;
|
|
34
|
+
}
|
|
35
|
+
const placed = [];
|
|
36
|
+
for (let w = 5; w >= 1; w--) {
|
|
37
|
+
for (const label of all) {
|
|
38
|
+
if (label.weight !== w)
|
|
39
|
+
continue;
|
|
40
|
+
let clear = true;
|
|
41
|
+
for (const q of placed) {
|
|
42
|
+
if (Math.abs(q.x - label.x) < minGap) {
|
|
43
|
+
clear = false;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (clear)
|
|
48
|
+
placed.push(label);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return placed.sort((a, b) => a.x - b.x);
|
|
52
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type Time = number;
|
|
2
|
+
export interface Candle {
|
|
3
|
+
t: Time;
|
|
4
|
+
o: number;
|
|
5
|
+
h: number;
|
|
6
|
+
l: number;
|
|
7
|
+
c: number;
|
|
8
|
+
}
|
|
9
|
+
export type ChartType = 'candles' | 'line' | 'area' | 'heikinAshi';
|
|
10
|
+
export type CandleStyle = 'hollowUp' | 'filled';
|
|
11
|
+
export interface ChartTheme {
|
|
12
|
+
background: string;
|
|
13
|
+
text: string;
|
|
14
|
+
textMuted: string;
|
|
15
|
+
grid: string;
|
|
16
|
+
up: string;
|
|
17
|
+
down: string;
|
|
18
|
+
line: string;
|
|
19
|
+
areaTop: string;
|
|
20
|
+
areaBottom: string;
|
|
21
|
+
crosshair: string;
|
|
22
|
+
accent: string;
|
|
23
|
+
accentText: string;
|
|
24
|
+
fontFamily: string;
|
|
25
|
+
fontSize: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ChartLabels {
|
|
28
|
+
open: string;
|
|
29
|
+
high: string;
|
|
30
|
+
low: string;
|
|
31
|
+
close: string;
|
|
32
|
+
change: string;
|
|
33
|
+
noData: string;
|
|
34
|
+
heikinAshi: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ChartOptions {
|
|
37
|
+
intervalMs: number;
|
|
38
|
+
tzOffsetMs: number;
|
|
39
|
+
priceDecimals: number;
|
|
40
|
+
minMove?: number;
|
|
41
|
+
type?: ChartType;
|
|
42
|
+
candleStyle?: CandleStyle;
|
|
43
|
+
theme: ChartTheme;
|
|
44
|
+
labels?: Partial<ChartLabels>;
|
|
45
|
+
locale?: string;
|
|
46
|
+
now?: () => Time;
|
|
47
|
+
reducedMotion?: boolean;
|
|
48
|
+
ariaLabel?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface PriceLine {
|
|
51
|
+
id: string;
|
|
52
|
+
price: number;
|
|
53
|
+
label: string;
|
|
54
|
+
color: string;
|
|
55
|
+
style?: 'solid' | 'dashed' | 'dotted';
|
|
56
|
+
}
|
|
57
|
+
export interface Marker {
|
|
58
|
+
id: string;
|
|
59
|
+
t: Time;
|
|
60
|
+
price: number;
|
|
61
|
+
kind: 'buy' | 'sell';
|
|
62
|
+
label?: string;
|
|
63
|
+
tooltip?: string;
|
|
64
|
+
}
|
|
65
|
+
export type IndicatorSpec = {
|
|
66
|
+
id: string;
|
|
67
|
+
type: 'sma';
|
|
68
|
+
period: number;
|
|
69
|
+
color: string;
|
|
70
|
+
} | {
|
|
71
|
+
id: string;
|
|
72
|
+
type: 'ema';
|
|
73
|
+
period: number;
|
|
74
|
+
color: string;
|
|
75
|
+
} | {
|
|
76
|
+
id: string;
|
|
77
|
+
type: 'bollinger';
|
|
78
|
+
period: number;
|
|
79
|
+
stdDev: number;
|
|
80
|
+
color: string;
|
|
81
|
+
} | {
|
|
82
|
+
id: string;
|
|
83
|
+
type: 'rsi';
|
|
84
|
+
period: number;
|
|
85
|
+
color: string;
|
|
86
|
+
} | {
|
|
87
|
+
id: string;
|
|
88
|
+
type: 'macd';
|
|
89
|
+
fast: number;
|
|
90
|
+
slow: number;
|
|
91
|
+
signal: number;
|
|
92
|
+
color: string;
|
|
93
|
+
signalColor: string;
|
|
94
|
+
};
|
|
95
|
+
export interface ChartEvents {
|
|
96
|
+
needMore: (oldest: Time) => void;
|
|
97
|
+
candleClosed: (candle: Candle) => void;
|
|
98
|
+
liveChange: (atLive: boolean) => void;
|
|
99
|
+
crosshair: (candle: Candle | null) => void;
|
|
100
|
+
}
|
|
101
|
+
export interface Chart {
|
|
102
|
+
setData(closed: readonly Candle[], forming: Candle | null, hasMore: boolean): void;
|
|
103
|
+
prependData(older: readonly Candle[], hasMore: boolean): void;
|
|
104
|
+
replaceClosed(candles: readonly Candle[]): void;
|
|
105
|
+
applyTick(price: number, time: Time): void;
|
|
106
|
+
setOptions(patch: Partial<ChartOptions>): void;
|
|
107
|
+
setPriceLines(lines: readonly PriceLine[]): void;
|
|
108
|
+
setMarkers(markers: readonly Marker[]): void;
|
|
109
|
+
scrollToLive(): void;
|
|
110
|
+
fitAll(): void;
|
|
111
|
+
on<E extends keyof ChartEvents>(event: E, listener: ChartEvents[E]): () => void;
|
|
112
|
+
destroy(): void;
|
|
113
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare const LIVE_MARGIN = 3;
|
|
2
|
+
export declare const MIN_BAR_SPACING = 2;
|
|
3
|
+
export declare const MAX_BAR_SPACING = 48;
|
|
4
|
+
export declare const DEFAULT_BAR_SPACING = 8;
|
|
5
|
+
export declare const NEED_MORE_BARS = 50;
|
|
6
|
+
export declare class Viewport {
|
|
7
|
+
barSpacing: number;
|
|
8
|
+
rightIndex: number;
|
|
9
|
+
plotLeft: number;
|
|
10
|
+
plotRight: number;
|
|
11
|
+
private last;
|
|
12
|
+
get plotWidth(): number;
|
|
13
|
+
get lastIndex(): number;
|
|
14
|
+
setPlot(left: number, right: number): void;
|
|
15
|
+
setLastIndex(lastIndex: number): void;
|
|
16
|
+
x(i: number): number;
|
|
17
|
+
indexAt(x: number): number;
|
|
18
|
+
visibleRange(): {
|
|
19
|
+
from: number;
|
|
20
|
+
to: number;
|
|
21
|
+
} | null;
|
|
22
|
+
liveRightIndex(): number;
|
|
23
|
+
atLive(): boolean;
|
|
24
|
+
panBy(dxPx: number): void;
|
|
25
|
+
setRightIndex(v: number): void;
|
|
26
|
+
zoomAt(x: number, f: number): void;
|
|
27
|
+
setBarSpacing(v: number): void;
|
|
28
|
+
onAppend(newLastIndex: number): void;
|
|
29
|
+
onPrepend(k: number): void;
|
|
30
|
+
onShift(k: number): void;
|
|
31
|
+
goLive(): void;
|
|
32
|
+
reset(): void;
|
|
33
|
+
fitAll(): void;
|
|
34
|
+
leftIndex(): number;
|
|
35
|
+
private clampRight;
|
|
36
|
+
}
|
|
37
|
+
export declare class MoreGate {
|
|
38
|
+
private pending;
|
|
39
|
+
check(leftIndex: number, hasMore: boolean): boolean;
|
|
40
|
+
clear(): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const LIVE_MARGIN = 3;
|
|
2
|
+
export const MIN_BAR_SPACING = 2;
|
|
3
|
+
export const MAX_BAR_SPACING = 48;
|
|
4
|
+
export const DEFAULT_BAR_SPACING = 8;
|
|
5
|
+
export const NEED_MORE_BARS = 50;
|
|
6
|
+
const clampSpacing = (v) => Math.min(MAX_BAR_SPACING, Math.max(MIN_BAR_SPACING, v));
|
|
7
|
+
export class Viewport {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.barSpacing = DEFAULT_BAR_SPACING;
|
|
10
|
+
this.rightIndex = 0;
|
|
11
|
+
this.plotLeft = 0;
|
|
12
|
+
this.plotRight = 0;
|
|
13
|
+
this.last = -1;
|
|
14
|
+
}
|
|
15
|
+
get plotWidth() {
|
|
16
|
+
return this.plotRight - this.plotLeft;
|
|
17
|
+
}
|
|
18
|
+
get lastIndex() {
|
|
19
|
+
return this.last;
|
|
20
|
+
}
|
|
21
|
+
setPlot(left, right) {
|
|
22
|
+
this.plotLeft = left;
|
|
23
|
+
this.plotRight = Math.max(left, right);
|
|
24
|
+
this.rightIndex = this.clampRight(this.rightIndex);
|
|
25
|
+
}
|
|
26
|
+
setLastIndex(lastIndex) {
|
|
27
|
+
this.last = lastIndex;
|
|
28
|
+
}
|
|
29
|
+
x(i) {
|
|
30
|
+
return this.plotRight - (this.rightIndex - i) * this.barSpacing;
|
|
31
|
+
}
|
|
32
|
+
indexAt(x) {
|
|
33
|
+
return this.rightIndex - (this.plotRight - x) / this.barSpacing;
|
|
34
|
+
}
|
|
35
|
+
visibleRange() {
|
|
36
|
+
if (this.last < 0)
|
|
37
|
+
return null;
|
|
38
|
+
const from = Math.max(0, Math.floor(this.indexAt(this.plotLeft)) - 1);
|
|
39
|
+
const to = Math.min(this.last, Math.ceil(this.rightIndex));
|
|
40
|
+
return from > to ? null : { from, to };
|
|
41
|
+
}
|
|
42
|
+
liveRightIndex() {
|
|
43
|
+
return this.last + LIVE_MARGIN;
|
|
44
|
+
}
|
|
45
|
+
atLive() {
|
|
46
|
+
return this.rightIndex >= this.last + LIVE_MARGIN - 0.5;
|
|
47
|
+
}
|
|
48
|
+
panBy(dxPx) {
|
|
49
|
+
this.rightIndex = this.clampRight(this.rightIndex - dxPx / this.barSpacing);
|
|
50
|
+
}
|
|
51
|
+
setRightIndex(v) {
|
|
52
|
+
this.rightIndex = this.clampRight(v);
|
|
53
|
+
}
|
|
54
|
+
zoomAt(x, f) {
|
|
55
|
+
const wasLive = this.atLive();
|
|
56
|
+
const a = this.indexAt(x);
|
|
57
|
+
this.barSpacing = clampSpacing(this.barSpacing * f);
|
|
58
|
+
this.rightIndex = wasLive ? this.liveRightIndex() : a + (this.plotRight - x) / this.barSpacing;
|
|
59
|
+
this.rightIndex = this.clampRight(this.rightIndex);
|
|
60
|
+
}
|
|
61
|
+
setBarSpacing(v) {
|
|
62
|
+
this.barSpacing = clampSpacing(v);
|
|
63
|
+
this.rightIndex = this.clampRight(this.rightIndex);
|
|
64
|
+
}
|
|
65
|
+
onAppend(newLastIndex) {
|
|
66
|
+
const wasLive = this.atLive();
|
|
67
|
+
const grew = newLastIndex - this.last;
|
|
68
|
+
this.last = newLastIndex;
|
|
69
|
+
if (wasLive && grew > 0)
|
|
70
|
+
this.rightIndex += grew;
|
|
71
|
+
}
|
|
72
|
+
onPrepend(k) {
|
|
73
|
+
this.last += k;
|
|
74
|
+
this.rightIndex += k;
|
|
75
|
+
}
|
|
76
|
+
onShift(k) {
|
|
77
|
+
this.last -= k;
|
|
78
|
+
this.rightIndex -= k;
|
|
79
|
+
}
|
|
80
|
+
goLive() {
|
|
81
|
+
this.rightIndex = this.clampRight(this.liveRightIndex());
|
|
82
|
+
}
|
|
83
|
+
reset() {
|
|
84
|
+
this.barSpacing = DEFAULT_BAR_SPACING;
|
|
85
|
+
this.goLive();
|
|
86
|
+
}
|
|
87
|
+
fitAll() {
|
|
88
|
+
this.barSpacing = clampSpacing(this.plotWidth / (this.last + 1 + LIVE_MARGIN));
|
|
89
|
+
this.goLive();
|
|
90
|
+
}
|
|
91
|
+
leftIndex() {
|
|
92
|
+
return this.indexAt(this.plotLeft);
|
|
93
|
+
}
|
|
94
|
+
clampRight(v) {
|
|
95
|
+
if (this.last < 0)
|
|
96
|
+
return 0;
|
|
97
|
+
const min = Math.min(4, this.last);
|
|
98
|
+
const futureBars = Math.max(LIVE_MARGIN, this.plotWidth / this.barSpacing / 2);
|
|
99
|
+
const max = this.last + futureBars;
|
|
100
|
+
return Math.min(max, Math.max(min, v));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class MoreGate {
|
|
104
|
+
constructor() {
|
|
105
|
+
this.pending = false;
|
|
106
|
+
}
|
|
107
|
+
check(leftIndex, hasMore) {
|
|
108
|
+
if (!hasMore || this.pending || leftIndex > NEED_MORE_BARS)
|
|
109
|
+
return false;
|
|
110
|
+
this.pending = true;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
clear() {
|
|
114
|
+
this.pending = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function warn(message: string): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface EnvironmentHooks {
|
|
2
|
+
resize(): void;
|
|
3
|
+
densityChanged(): void;
|
|
4
|
+
second(): void;
|
|
5
|
+
visible(): void;
|
|
6
|
+
}
|
|
7
|
+
export interface Environment {
|
|
8
|
+
dpr(): number;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function watchEnvironment(container: HTMLElement, doc: Document, hooks: EnvironmentHooks): Environment;
|