@funnest/charts 0.4.0 → 0.5.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/dist/chart.d.ts +28 -0
- package/dist/index.js +84 -7
- package/dist/render/renderer.d.ts +2 -1
- package/dist/settings.d.ts +2 -0
- package/package.json +1 -1
package/dist/chart.d.ts
CHANGED
|
@@ -141,6 +141,8 @@ export declare class Chart {
|
|
|
141
141
|
private priceAxisWidth;
|
|
142
142
|
private frame;
|
|
143
143
|
private crosshair;
|
|
144
|
+
/** Time (UTC ms) of a crosshair mirrored from a linked chart; see setSyncedCrosshair(). */
|
|
145
|
+
private syncedTime;
|
|
144
146
|
private lastCrosshairKey;
|
|
145
147
|
private lastRangeKey;
|
|
146
148
|
private lastLayoutKey;
|
|
@@ -193,6 +195,30 @@ export declare class Chart {
|
|
|
193
195
|
scrollBars(bars: number): void;
|
|
194
196
|
/** True when the newest bar is visible. */
|
|
195
197
|
isAtRealtime(): boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Mirrors another chart's crosshair: a vertical line at `time` (UTC ms) on the bar that
|
|
200
|
+
* contains it, with the time badge but no price line. `crosshairMove` fires with
|
|
201
|
+
* `source: 'sync'`, so legends follow along. A pointer on this chart takes precedence.
|
|
202
|
+
* Pass null to clear.
|
|
203
|
+
*/
|
|
204
|
+
setSyncedCrosshair(time: number | null): void;
|
|
205
|
+
/**
|
|
206
|
+
* The time span (UTC ms) between the plot's left and right edges. Edges past the data
|
|
207
|
+
* (the empty space right of the last bar) are extrapolated by the bar interval.
|
|
208
|
+
*/
|
|
209
|
+
getVisibleTimeRange(): {
|
|
210
|
+
from: number;
|
|
211
|
+
to: number;
|
|
212
|
+
} | null;
|
|
213
|
+
/** Scrolls and zooms so the plot spans `from`…`to` (UTC ms), e.g. to match a linked chart. */
|
|
214
|
+
setVisibleTimeRange(range: {
|
|
215
|
+
from: number;
|
|
216
|
+
to: number;
|
|
217
|
+
}): void;
|
|
218
|
+
/** Fractional bar index → time, interpolating between bars and extrapolating past either end. */
|
|
219
|
+
private indexToTime;
|
|
220
|
+
/** Inverse of indexToTime(). */
|
|
221
|
+
private timeToIndex;
|
|
196
222
|
/** Snapshot of the chart (without the crosshair) at device resolution. */
|
|
197
223
|
takeScreenshot(): HTMLCanvasElement;
|
|
198
224
|
/**
|
|
@@ -286,6 +312,8 @@ export declare class Chart {
|
|
|
286
312
|
private afterFrame;
|
|
287
313
|
private requestHistory;
|
|
288
314
|
private resolveCrosshair;
|
|
315
|
+
/** The bar containing `time`, as a crosshair without a price (nothing when it's off the data). */
|
|
316
|
+
private resolveSyncedCrosshair;
|
|
289
317
|
private paneAt;
|
|
290
318
|
private setTooltip;
|
|
291
319
|
private executionTooltip;
|
package/dist/index.js
CHANGED
|
@@ -1681,6 +1681,7 @@ var DEFAULT_CHART_SETTINGS = {
|
|
|
1681
1681
|
gridHorizontal: true,
|
|
1682
1682
|
gridVertical: true,
|
|
1683
1683
|
volume: true,
|
|
1684
|
+
navigationButtons: true,
|
|
1684
1685
|
upColor: null,
|
|
1685
1686
|
downColor: null
|
|
1686
1687
|
};
|
|
@@ -2239,6 +2240,17 @@ function drawOverlay(layer, m, ch, tooltip = null) {
|
|
|
2239
2240
|
if (tooltip) drawTooltip(p, f, tooltip);
|
|
2240
2241
|
if (!ch) return;
|
|
2241
2242
|
p.vLine(ch.x, 0, f.panesHeight, pal.crosshair, DASH);
|
|
2243
|
+
if (ch.source !== "sync") drawCrosshairValue(p, m, ch);
|
|
2244
|
+
const timeLabel = m.formatCrosshairTime(ch.time);
|
|
2245
|
+
const w = p.measure(timeLabel) + 16;
|
|
2246
|
+
const cx = Math.min(f.plotWidth - w / 2, Math.max(w / 2, ch.x));
|
|
2247
|
+
p.roundRect(cx - w / 2, f.panesHeight + 4, w, f.timeAxisHeight - 8, 4, pal.text);
|
|
2248
|
+
p.text(timeLabel, cx, f.panesHeight + f.timeAxisHeight / 2, pal.background, "center");
|
|
2249
|
+
}
|
|
2250
|
+
function drawCrosshairValue(p, m, ch) {
|
|
2251
|
+
const f = m.frame;
|
|
2252
|
+
const pal = f.palette;
|
|
2253
|
+
const ctx = p.ctx;
|
|
2242
2254
|
p.hLine(ch.y, 0, f.plotWidth, pal.crosshair, DASH);
|
|
2243
2255
|
if (ch.source === "touch") {
|
|
2244
2256
|
ctx.beginPath();
|
|
@@ -2255,11 +2267,6 @@ function drawOverlay(layer, m, ch, tooltip = null) {
|
|
|
2255
2267
|
const by = Math.min(f.panesHeight - BADGE_HEIGHT / 2, Math.max(BADGE_HEIGHT / 2, ch.y));
|
|
2256
2268
|
p.roundRect(f.plotWidth + 2, by - BADGE_HEIGHT / 2, f.width - f.plotWidth - 4, BADGE_HEIGHT, 4, pal.text);
|
|
2257
2269
|
p.text(valueLabel, f.plotWidth + 8, by, pal.background);
|
|
2258
|
-
const timeLabel = m.formatCrosshairTime(ch.time);
|
|
2259
|
-
const w = p.measure(timeLabel) + 16;
|
|
2260
|
-
const cx = Math.min(f.plotWidth - w / 2, Math.max(w / 2, ch.x));
|
|
2261
|
-
p.roundRect(cx - w / 2, f.panesHeight + 4, w, f.timeAxisHeight - 8, 4, pal.text);
|
|
2262
|
-
p.text(timeLabel, cx, f.panesHeight + f.timeAxisHeight / 2, pal.background, "center");
|
|
2263
2270
|
}
|
|
2264
2271
|
|
|
2265
2272
|
// src/resolution.ts
|
|
@@ -2385,6 +2392,8 @@ var Chart = class {
|
|
|
2385
2392
|
priceAxisWidth = MIN_PRICE_AXIS_WIDTH;
|
|
2386
2393
|
frame = null;
|
|
2387
2394
|
crosshair = null;
|
|
2395
|
+
/** Time (UTC ms) of a crosshair mirrored from a linked chart; see setSyncedCrosshair(). */
|
|
2396
|
+
syncedTime = null;
|
|
2388
2397
|
lastCrosshairKey = "";
|
|
2389
2398
|
lastRangeKey = "";
|
|
2390
2399
|
lastLayoutKey = "";
|
|
@@ -2611,6 +2620,61 @@ var Chart = class {
|
|
|
2611
2620
|
isAtRealtime() {
|
|
2612
2621
|
return this.store.length === 0 || this.timeScale.visibleRange().to >= this.store.length - 1;
|
|
2613
2622
|
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Mirrors another chart's crosshair: a vertical line at `time` (UTC ms) on the bar that
|
|
2625
|
+
* contains it, with the time badge but no price line. `crosshairMove` fires with
|
|
2626
|
+
* `source: 'sync'`, so legends follow along. A pointer on this chart takes precedence.
|
|
2627
|
+
* Pass null to clear.
|
|
2628
|
+
*/
|
|
2629
|
+
setSyncedCrosshair(time) {
|
|
2630
|
+
if (time === this.syncedTime) return;
|
|
2631
|
+
this.syncedTime = time;
|
|
2632
|
+
this.invalidate(false);
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* The time span (UTC ms) between the plot's left and right edges. Edges past the data
|
|
2636
|
+
* (the empty space right of the last bar) are extrapolated by the bar interval.
|
|
2637
|
+
*/
|
|
2638
|
+
getVisibleTimeRange() {
|
|
2639
|
+
const ts = this.timeScale;
|
|
2640
|
+
if (this.store.length === 0 || ts.width <= 0) return null;
|
|
2641
|
+
return { from: this.indexToTime(ts.xToIndexFloat(0)), to: this.indexToTime(ts.xToIndexFloat(ts.width)) };
|
|
2642
|
+
}
|
|
2643
|
+
/** Scrolls and zooms so the plot spans `from`…`to` (UTC ms), e.g. to match a linked chart. */
|
|
2644
|
+
setVisibleTimeRange(range) {
|
|
2645
|
+
const ts = this.timeScale;
|
|
2646
|
+
if (this.store.length === 0 || ts.width <= 0 || !(range.to > range.from)) return;
|
|
2647
|
+
const from = this.timeToIndex(range.from);
|
|
2648
|
+
const to = this.timeToIndex(range.to);
|
|
2649
|
+
if (!(to > from)) return;
|
|
2650
|
+
ts.barSpacing = ts.width / (to - from);
|
|
2651
|
+
ts.rightOffset = to - 0.5 - (this.store.length - 1);
|
|
2652
|
+
ts.clamp();
|
|
2653
|
+
this.invalidate();
|
|
2654
|
+
}
|
|
2655
|
+
/** Fractional bar index → time, interpolating between bars and extrapolating past either end. */
|
|
2656
|
+
indexToTime(index) {
|
|
2657
|
+
const bars = this.store.bars;
|
|
2658
|
+
const last = bars.length - 1;
|
|
2659
|
+
const step = this.barInterval();
|
|
2660
|
+
if (index <= 0) return bars[0].time + index * step;
|
|
2661
|
+
if (index >= last) return bars[last].time + (index - last) * step;
|
|
2662
|
+
const i = Math.floor(index);
|
|
2663
|
+
const a = bars[i].time;
|
|
2664
|
+
return a + (bars[i + 1].time - a) * (index - i);
|
|
2665
|
+
}
|
|
2666
|
+
/** Inverse of indexToTime(). */
|
|
2667
|
+
timeToIndex(time) {
|
|
2668
|
+
const bars = this.store.bars;
|
|
2669
|
+
const last = bars.length - 1;
|
|
2670
|
+
const step = this.barInterval();
|
|
2671
|
+
if (time <= bars[0].time) return (time - bars[0].time) / step;
|
|
2672
|
+
if (time >= bars[last].time) return last + (time - bars[last].time) / step;
|
|
2673
|
+
const i = this.store.indexAtOrBefore(time);
|
|
2674
|
+
const a = bars[i].time;
|
|
2675
|
+
const b = bars[i + 1].time;
|
|
2676
|
+
return i + (b > a ? (time - a) / (b - a) : 0);
|
|
2677
|
+
}
|
|
2614
2678
|
/** Snapshot of the chart (without the crosshair) at device resolution. */
|
|
2615
2679
|
takeScreenshot() {
|
|
2616
2680
|
if (this.rafId || this.mainDirty) this.render();
|
|
@@ -3232,8 +3296,9 @@ var Chart = class {
|
|
|
3232
3296
|
resolveCrosshair(frame) {
|
|
3233
3297
|
const ch = this.crosshair;
|
|
3234
3298
|
const bars = this.store.bars;
|
|
3235
|
-
if (
|
|
3236
|
-
|
|
3299
|
+
if (bars.length === 0) return null;
|
|
3300
|
+
const outside = ch?.source === "mouse" && (ch.x < 0 || ch.x > frame.plotWidth || ch.y < 0 || ch.y > frame.panesHeight);
|
|
3301
|
+
if (!ch || outside) return this.syncedTime === null ? null : this.resolveSyncedCrosshair(this.syncedTime);
|
|
3237
3302
|
const index = Math.min(bars.length - 1, Math.max(0, this.timeScale.xToIndex(ch.x)));
|
|
3238
3303
|
const bar = bars[index];
|
|
3239
3304
|
const x = this.timeScale.indexToX(index);
|
|
@@ -3243,6 +3308,18 @@ var Chart = class {
|
|
|
3243
3308
|
const value = pane.kind === "main" ? roundToTick(raw, this.tickSize) : raw;
|
|
3244
3309
|
return { index, time: bar.time, bar, value, pane: pane.id, x, y, source: ch.source };
|
|
3245
3310
|
}
|
|
3311
|
+
/** The bar containing `time`, as a crosshair without a price (nothing when it's off the data). */
|
|
3312
|
+
resolveSyncedCrosshair(time) {
|
|
3313
|
+
const bars = this.store.bars;
|
|
3314
|
+
const last = bars[bars.length - 1];
|
|
3315
|
+
if (time < bars[0].time || time >= last.time + this.barInterval()) return null;
|
|
3316
|
+
const index = this.store.indexAtOrBefore(time);
|
|
3317
|
+
const bar = bars[index];
|
|
3318
|
+
if (!bar) return null;
|
|
3319
|
+
const x = this.timeScale.indexToX(index);
|
|
3320
|
+
if (x < 0 || x > this.timeScale.width) return null;
|
|
3321
|
+
return { index, time: bar.time, bar, value: bar.close, pane: "main", x, y: -1, source: "sync" };
|
|
3322
|
+
}
|
|
3246
3323
|
paneAt(frame, y) {
|
|
3247
3324
|
return frame.panes.find((p) => y >= p.top && y < p.top + p.height) ?? frame.panes[frame.panes.length - 1];
|
|
3248
3325
|
}
|
|
@@ -67,7 +67,8 @@ export interface CrosshairInfo {
|
|
|
67
67
|
pane: PaneId;
|
|
68
68
|
x: number;
|
|
69
69
|
y: number;
|
|
70
|
-
|
|
70
|
+
/** 'sync': mirrored from a linked chart (setSyncedCrosshair) — time only, `y` is -1. */
|
|
71
|
+
source: 'mouse' | 'touch' | 'sync';
|
|
71
72
|
}
|
|
72
73
|
/** Fill details shown next to an execution marker. */
|
|
73
74
|
export interface Tooltip {
|
package/dist/settings.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ChartSettings {
|
|
|
12
12
|
gridVertical: boolean;
|
|
13
13
|
/** Volume pane under the price pane. */
|
|
14
14
|
volume: boolean;
|
|
15
|
+
/** Zoom/scroll buttons at the bottom center of the chart (@funnest/charts-ui). */
|
|
16
|
+
navigationButtons: boolean;
|
|
15
17
|
/** Up/down candle colors; null follows the theme. */
|
|
16
18
|
upColor: string | null;
|
|
17
19
|
downColor: string | null;
|