@devexperts/dxcharts-lite 2.7.29 → 2.7.31
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/canvas/canvas-chart-html.js +1 -1
- package/dist/chart/chart.config.d.ts +7 -0
- package/dist/chart/chart.config.js +2 -0
- package/dist/chart/components/chart/chart-base.model.d.ts +2 -0
- package/dist/chart/components/chart/chart-base.model.js +2 -1
- package/dist/chart/components/chart/chart.model.d.ts +2 -0
- package/dist/chart/components/chart/chart.model.js +20 -8
- package/dist/chart/components/cross_tool/cross-tool.model.d.ts +1 -0
- package/dist/chart/components/cross_tool/cross-tool.model.js +15 -1
- package/dist/chart/components/dynamic-objects/dynamic-objects.model.d.ts +7 -0
- package/dist/chart/components/dynamic-objects/dynamic-objects.model.js +60 -7
- package/dist/chart/components/highlights/highlights.drawer.d.ts +4 -0
- package/dist/chart/components/highlights/highlights.drawer.js +27 -5
- package/dist/chart/components/y_axis/price_labels/price-label.drawer.js +1 -1
- package/dist/chart/components/y_axis/price_labels/y-axis-labels.model.js +6 -3
- package/dist/chart/components/y_axis/y-axis-labels.drawer.d.ts +8 -1
- package/dist/chart/components/y_axis/y-axis-labels.drawer.js +1 -1
- package/dist/chart/inputhandlers/hover-producer.component.d.ts +8 -2
- package/dist/chart/inputhandlers/hover-producer.component.js +86 -22
- package/dist/chart/model/scale.model.d.ts +2 -6
- package/dist/chart/model/scaling/viewport.model.d.ts +2 -0
- package/dist/chart/utils/__tests__/canvas-lower-hover-strip.utils.test.d.ts +6 -0
- package/dist/chart/utils/__tests__/canvas-lower-hover-strip.utils.test.js +34 -0
- package/dist/chart/utils/candles.utils.d.ts +4 -0
- package/dist/chart/utils/candles.utils.js +80 -1
- package/dist/chart/utils/canvas/canvas-drawing-functions.utils.d.ts +1 -1
- package/dist/chart/utils/canvas/canvas-drawing-functions.utils.js +4 -1
- package/dist/chart/utils/canvas-lower-hover-strip.utils.d.ts +17 -0
- package/dist/chart/utils/canvas-lower-hover-strip.utils.js +20 -0
- package/dist/dxchart.min.js +5 -5
- package/package.json +1 -1
|
@@ -38,6 +38,7 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
38
38
|
* When true, mobile long-touch (e.g. 200ms) does not activate crosshair / disable pan.
|
|
39
39
|
*/
|
|
40
40
|
this.longTouchCrosshairSuppressed = false;
|
|
41
|
+
this.hoverOverLastCandle = false;
|
|
41
42
|
this.xFormatter = () => '';
|
|
42
43
|
const candleHoverProducerPart = new CandleHoverProducerPart(this.chartModel);
|
|
43
44
|
const compareSeriesHoverProducerPart = new CompareSeriesHoverProducerPart(this.chartModel);
|
|
@@ -68,16 +69,14 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
68
69
|
lastCandle && this.createAndFireHoverFromCandle(lastCandle);
|
|
69
70
|
}));
|
|
70
71
|
this.addRxSubscription(this.chartModel.candlesUpdatedSubject.subscribe(() => {
|
|
71
|
-
// update hover if its timestamp is equal or greater than last candle's one
|
|
72
72
|
const lastCandle = this.chartModel.getLastVisualCandle();
|
|
73
|
-
if (this.hover !== null && lastCandle !== undefined) {
|
|
74
|
-
|
|
75
|
-
this.updateHover(lastCandle);
|
|
76
|
-
}
|
|
73
|
+
if (this.hover !== null && lastCandle !== undefined && this.hoverOverLastCandle) {
|
|
74
|
+
this.updateHover(lastCandle);
|
|
77
75
|
}
|
|
78
76
|
}));
|
|
79
77
|
this.addRxSubscription(this.crossEventProducer.crossSubject.subscribe((cross) => {
|
|
80
78
|
if (cross === null) {
|
|
79
|
+
this.hoverOverLastCandle = false;
|
|
81
80
|
this.hoverSubject.next(null);
|
|
82
81
|
}
|
|
83
82
|
else {
|
|
@@ -134,7 +133,7 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
134
133
|
this.paneManager.chartPanComponent.setChartPanningOptions(false, false);
|
|
135
134
|
}));
|
|
136
135
|
this.addRxSubscription(this.canvasInputListener.observeTouchEndDocument().subscribe(event => {
|
|
137
|
-
var _a, _b;
|
|
136
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
138
137
|
const { clientX, clientY } = event.changedTouches[0];
|
|
139
138
|
const { fixed, temp } = this.crossEventProducer.crossToolTouchInfo;
|
|
140
139
|
const x = clientX - this.canvasBoundsContainer.canvasOnPageLocation.x;
|
|
@@ -142,28 +141,32 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
142
141
|
// common tap without moving, hide crosstool
|
|
143
142
|
if (this.crossEventProducer.crossToolTouchInfo.isCommonTap &&
|
|
144
143
|
!checkChartIsMoving(x, temp.x, y, temp.y)) {
|
|
145
|
-
this.
|
|
146
|
-
this.longTouchActivatedSubject.next(false);
|
|
147
|
-
this.crossEventProducer.fireCrossClose();
|
|
148
|
-
this.crossEventProducer.crossToolHover = null;
|
|
149
|
-
this.crossEventProducer.crossToolTouchInfo.isSet = false;
|
|
144
|
+
this.resetCrossTool();
|
|
150
145
|
return;
|
|
151
146
|
}
|
|
152
147
|
if (!this.crossEventProducer.crossToolTouchInfo.isSet) {
|
|
153
148
|
this.crossEventProducer.crossToolTouchInfo.isSet = true;
|
|
154
|
-
|
|
149
|
+
const crossToolHover = this.crossEventProducer.crossToolHover;
|
|
150
|
+
const anchorX = (_a = crossToolHover === null || crossToolHover === void 0 ? void 0 : crossToolHover.x) !== null && _a !== void 0 ? _a : x;
|
|
151
|
+
const anchorY = (_b = crossToolHover === null || crossToolHover === void 0 ? void 0 : crossToolHover.y) !== null && _b !== void 0 ? _b : y;
|
|
155
152
|
this.crossEventProducer.crossToolTouchInfo.temp = {
|
|
156
|
-
x:
|
|
157
|
-
y:
|
|
153
|
+
x: anchorX,
|
|
154
|
+
y: anchorY,
|
|
158
155
|
};
|
|
159
156
|
this.crossEventProducer.crossToolTouchInfo.fixed = {
|
|
160
|
-
x,
|
|
161
|
-
y,
|
|
157
|
+
x: anchorX,
|
|
158
|
+
y: anchorY,
|
|
162
159
|
};
|
|
163
|
-
this.
|
|
160
|
+
const paneId = (_e = (_c = crossToolHover === null || crossToolHover === void 0 ? void 0 : crossToolHover.paneId) !== null && _c !== void 0 ? _c : (_d = this.paneManager.getPaneIfHit({ x: anchorX, y: anchorY })) === null || _d === void 0 ? void 0 : _d.uuid) !== null && _e !== void 0 ? _e : '';
|
|
161
|
+
const hover = crossToolHover !== null && crossToolHover !== void 0 ? crossToolHover : this.hover;
|
|
162
|
+
if (!hover) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.crossEventProducer.crossToolHover = Object.assign(Object.assign({}, hover), { x: anchorX, y: anchorY, paneId });
|
|
166
|
+
this.crossEventProducer.crossSubject.next([anchorX, anchorY, paneId]);
|
|
164
167
|
}
|
|
165
168
|
else {
|
|
166
|
-
const pane = (
|
|
169
|
+
const pane = (_g = (_f = this.crossEventProducer.crossToolHover) === null || _f === void 0 ? void 0 : _f.paneId) !== null && _g !== void 0 ? _g : 'CHART';
|
|
167
170
|
const paneBounds = this.canvasBoundsContainer.getBounds(CanvasElement.PANE_UUID(pane));
|
|
168
171
|
const paneYStart = paneBounds.y + 5;
|
|
169
172
|
const paneYEnd = paneBounds.y + paneBounds.height - 5;
|
|
@@ -172,6 +175,13 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
172
175
|
const newX = fixed.x < 0 ? 0 : fixed.x > paneBounds.width ? paneBounds.width : (fixed.x += xDiff);
|
|
173
176
|
const newY = fixed.y < paneYStart ? paneYStart : fixed.y > paneYEnd ? paneYEnd : (fixed.y += yDiff);
|
|
174
177
|
this.crossEventProducer.crossToolTouchInfo.fixed = { x: newX, y: newY };
|
|
178
|
+
this.crossEventProducer.crossToolTouchInfo.temp = { x: newX, y: newY };
|
|
179
|
+
const hover = (_h = this.crossEventProducer.crossToolHover) !== null && _h !== void 0 ? _h : this.hover;
|
|
180
|
+
if (!hover) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.crossEventProducer.crossToolHover = Object.assign(Object.assign({}, hover), { x: newX, y: newY, paneId: pane });
|
|
184
|
+
this.crossEventProducer.crossSubject.next([newX, newY, pane]);
|
|
175
185
|
}
|
|
176
186
|
}));
|
|
177
187
|
//#endregion
|
|
@@ -232,16 +242,53 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
232
242
|
this.fireHover(hover);
|
|
233
243
|
}
|
|
234
244
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
245
|
+
* Refreshes hover data (candles, studies, etc.) for the current pointer position.
|
|
246
|
+
* Used when the last candle updates while the legend must stay aligned to crosshair X — not to the last candle center.
|
|
237
247
|
*/
|
|
238
248
|
updateHover(candle) {
|
|
249
|
+
var _a, _b;
|
|
250
|
+
if (!this.hover) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const shouldKeepSetTouchCrosshair = isMobile() &&
|
|
254
|
+
this.longTouchActivatedSubject.getValue() &&
|
|
255
|
+
this.crossEventProducer.crossSubject.getValue() !== null;
|
|
256
|
+
const crossToolHover = this.crossEventProducer.crossToolHover;
|
|
257
|
+
const hoverX = shouldKeepSetTouchCrosshair ? ((_a = crossToolHover === null || crossToolHover === void 0 ? void 0 : crossToolHover.x) !== null && _a !== void 0 ? _a : this.hover.x) : this.hover.x;
|
|
258
|
+
const hoverY = shouldKeepSetTouchCrosshair ? ((_b = crossToolHover === null || crossToolHover === void 0 ? void 0 : crossToolHover.y) !== null && _b !== void 0 ? _b : this.hover.y) : this.hover.y;
|
|
259
|
+
const hoveredCandle = this.chartModel.candleFromX(hoverX, true);
|
|
260
|
+
const lastIdx = candle.candle.idx;
|
|
261
|
+
const hoveredIdx = hoveredCandle.idx;
|
|
262
|
+
if (lastIdx !== undefined &&
|
|
263
|
+
hoveredIdx !== undefined &&
|
|
264
|
+
hoveredIdx < lastIdx &&
|
|
265
|
+
hoverX < candle.xStart(this.scale)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
239
268
|
const updatedHover = this.createHoverFromCandle(candle);
|
|
240
|
-
if (
|
|
241
|
-
const hover = Object.assign(Object.assign({}, updatedHover), { x:
|
|
269
|
+
if (updatedHover) {
|
|
270
|
+
const hover = Object.assign(Object.assign({}, updatedHover), { x: hoverX, y: hoverY });
|
|
242
271
|
this.fireHover(hover);
|
|
243
272
|
}
|
|
244
273
|
}
|
|
274
|
+
updateHoverLastCandle(hover) {
|
|
275
|
+
const lastCandle = this.chartModel.getLastVisualCandle();
|
|
276
|
+
if (!lastCandle) {
|
|
277
|
+
this.hoverOverLastCandle = false;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const hoveredCandle = this.chartModel.candleFromX(hover.x, true);
|
|
281
|
+
const lastIdx = lastCandle.candle.idx;
|
|
282
|
+
const hoveredIdx = hoveredCandle.idx;
|
|
283
|
+
const isHistoricalCandleHover = lastIdx !== undefined &&
|
|
284
|
+
hoveredIdx !== undefined &&
|
|
285
|
+
hoveredIdx < lastIdx &&
|
|
286
|
+
hover.x < lastCandle.xStart(this.scale);
|
|
287
|
+
this.hoverOverLastCandle =
|
|
288
|
+
!isHistoricalCandleHover &&
|
|
289
|
+
(lastCandle.candle.timestamp <= hover.timestamp ||
|
|
290
|
+
(lastIdx !== undefined && hoveredIdx !== undefined && hoveredIdx === lastIdx));
|
|
291
|
+
}
|
|
245
292
|
/**
|
|
246
293
|
* Creates a hover element at the specified coordinates and fires it with the option to show the cross tool
|
|
247
294
|
* @param {CrossEvent} [x,y] - The coordinates where the hover element will be created
|
|
@@ -274,9 +321,11 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
274
321
|
: (_b = hover.candleHover) === null || _b === void 0 ? void 0 : _b.visualCandle.candle;
|
|
275
322
|
candle && this.chartModel.mainCandleSeries.setActiveCandle(candle);
|
|
276
323
|
}
|
|
324
|
+
this.updateHoverLastCandle(hover);
|
|
277
325
|
this.hoverSubject.next(hover);
|
|
278
326
|
}
|
|
279
327
|
else {
|
|
328
|
+
this.hoverOverLastCandle = false;
|
|
280
329
|
this.crossEventProducer.fireCrossClose();
|
|
281
330
|
}
|
|
282
331
|
}
|
|
@@ -293,6 +342,21 @@ export class HoverProducerComponent extends ChartBaseElement {
|
|
|
293
342
|
setLongTouchCrosshairSuppressed(value) {
|
|
294
343
|
this.longTouchCrosshairSuppressed = value;
|
|
295
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Resets the current crosshair/touch state back to the default mobile idle state.
|
|
347
|
+
*/
|
|
348
|
+
resetCrossTool(clearMobile = false) {
|
|
349
|
+
this.paneManager.chartPanComponent.setChartPanningOptions(true, true);
|
|
350
|
+
this.longTouchActivatedSubject.next(false);
|
|
351
|
+
this.crossEventProducer.fireCrossClose();
|
|
352
|
+
this.crossEventProducer.crossToolHover = null;
|
|
353
|
+
this.crossEventProducer.crossToolTouchInfo.isSet = false;
|
|
354
|
+
if (clearMobile) {
|
|
355
|
+
this.crossEventProducer.crossToolTouchInfo.isCommonTap = false;
|
|
356
|
+
this.crossEventProducer.crossToolTouchInfo.fixed = { x: 0, y: 0 };
|
|
357
|
+
this.crossEventProducer.crossToolTouchInfo.temp = { x: 0, y: 0 };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
296
360
|
/**
|
|
297
361
|
* Registers a hover producer part with the given id.
|
|
298
362
|
*
|
|
@@ -7,7 +7,7 @@ import { Subject } from 'rxjs';
|
|
|
7
7
|
import { ChartConfigComponentsOffsets, ChartScale, FullChartConfig } from '../chart.config';
|
|
8
8
|
import { CanvasAnimation } from '../animation/canvas-animation';
|
|
9
9
|
import { AutoScaleViewportSubModel } from './scaling/auto-scale.model';
|
|
10
|
-
import { Price, Unit, ViewportModel, ViewportModelState, Zoom } from './scaling/viewport.model';
|
|
10
|
+
import { Price, Unit, ViewportModel, ViewportModelState, XScale, YScale, Zoom } from './scaling/viewport.model';
|
|
11
11
|
import { BoundsProvider } from './bounds.model';
|
|
12
12
|
import VisualCandle from './visual-candle';
|
|
13
13
|
export interface HighLowWithIndex {
|
|
@@ -16,11 +16,7 @@ export interface HighLowWithIndex {
|
|
|
16
16
|
highIdx: number;
|
|
17
17
|
lowIdx: number;
|
|
18
18
|
}
|
|
19
|
-
export interface ScaleHistoryItem {
|
|
20
|
-
xStart: Unit;
|
|
21
|
-
xEnd: Unit;
|
|
22
|
-
yStart: Unit;
|
|
23
|
-
yEnd: Unit;
|
|
19
|
+
export interface ScaleHistoryItem extends XScale, YScale {
|
|
24
20
|
}
|
|
25
21
|
export declare const getDefaultHighLowWithIndex: () => HighLowWithIndex;
|
|
26
22
|
export type ViewportPercent = number;
|
|
@@ -57,6 +57,8 @@ export interface ViewportModelState {
|
|
|
57
57
|
zoomY: Zoom;
|
|
58
58
|
inverseY: boolean;
|
|
59
59
|
}
|
|
60
|
+
export type XScale = Pick<ViewportModelState, 'xStart' | 'xEnd'>;
|
|
61
|
+
export type YScale = Pick<ViewportModelState, 'yStart' | 'yEnd'>;
|
|
60
62
|
/**
|
|
61
63
|
* Abstract viewport model.
|
|
62
64
|
* Viewport has 4 coordinates: xStart, xEnd, yStart and yEnd - all stored in {@link Unit}.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2019 - 2026 Devexperts Solutions IE Limited
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2019 - 2026 Devexperts Solutions IE Limited
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
import { isCanvasPointInLowerHoverStrip } from '../canvas-lower-hover-strip.utils';
|
|
7
|
+
const baseBounds = () => ({
|
|
8
|
+
x: 0,
|
|
9
|
+
y: 0,
|
|
10
|
+
pageX: 0,
|
|
11
|
+
pageY: 0,
|
|
12
|
+
width: 1000,
|
|
13
|
+
height: 1000,
|
|
14
|
+
});
|
|
15
|
+
describe('canvas-lower-hover-strip.utils', () => {
|
|
16
|
+
it('returns true for a point in the lower strip (bottom offset 0)', () => {
|
|
17
|
+
const b = baseBounds();
|
|
18
|
+
expect(isCanvasPointInLowerHoverStrip({ x: 500, y: 900 }, b, 0)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('returns false for a point in the upper area', () => {
|
|
21
|
+
const b = baseBounds();
|
|
22
|
+
expect(isCanvasPointInLowerHoverStrip({ x: 500, y: 100 }, b, 0)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
it('shifts the strip when bottomOffsetPx is non-zero', () => {
|
|
25
|
+
const b = baseBounds();
|
|
26
|
+
const yAtStripStartNoOffset = b.height * 0.85 + 10;
|
|
27
|
+
expect(isCanvasPointInLowerHoverStrip({ x: 1, y: yAtStripStartNoOffset - 1 }, b, 0)).toBe(false);
|
|
28
|
+
expect(isCanvasPointInLowerHoverStrip({ x: 1, y: yAtStripStartNoOffset }, b, 0)).toBe(true);
|
|
29
|
+
const bottomOffset = 50;
|
|
30
|
+
const yAtStripStartWithOffset = b.height * 0.85 - bottomOffset + 10;
|
|
31
|
+
expect(isCanvasPointInLowerHoverStrip({ x: 1, y: yAtStripStartWithOffset - 1 }, b, bottomOffset)).toBe(false);
|
|
32
|
+
expect(isCanvasPointInLowerHoverStrip({ x: 1, y: yAtStripStartWithOffset }, b, bottomOffset)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
4
|
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
*/
|
|
6
|
+
import { CandleTimestampAnchor } from '../chart.config';
|
|
6
7
|
import { Candle } from '../model/candle.model';
|
|
7
8
|
import { DataSeriesPoint } from '../model/data-series.model';
|
|
8
9
|
import { BinarySearchResult } from './array.utils';
|
|
@@ -10,7 +11,10 @@ export declare const getDaysOnlyTimestampFn: (isDaysPeriod: boolean) => (timesta
|
|
|
10
11
|
export declare const searchCandleIndex: (rawTimestamp: number, options: {
|
|
11
12
|
extrapolate?: boolean;
|
|
12
13
|
isDaysPeriod?: boolean;
|
|
14
|
+
candleTimestampAnchor?: CandleTimestampAnchor;
|
|
13
15
|
} | undefined, candles: DataSeriesPoint[], periodMs?: number) => BinarySearchResult;
|
|
16
|
+
export declare const getCandleStart: (candles: DataSeriesPoint[], index: number, periodMs: number, anchor: CandleTimestampAnchor) => number;
|
|
17
|
+
export declare const getCandleEnd: (candle: DataSeriesPoint, periodMs: number, anchor: CandleTimestampAnchor) => number;
|
|
14
18
|
/**
|
|
15
19
|
* checks if the first or the last candle or both have implied volatility data provided
|
|
16
20
|
* @param candles
|
|
@@ -11,7 +11,7 @@ export const getDaysOnlyTimestampFn = (isDaysPeriod) => (timestamp) => {
|
|
|
11
11
|
}
|
|
12
12
|
return timestamp;
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
const searchOpenTimeCandleIndex = (rawTimestamp, options = {}, candles, periodMs = 1000) => {
|
|
15
15
|
var _a, _b, _c, _d;
|
|
16
16
|
const { extrapolate, isDaysPeriod } = options;
|
|
17
17
|
const shouldExtrapolate = Boolean(extrapolate);
|
|
@@ -55,6 +55,85 @@ export const searchCandleIndex = (rawTimestamp, options = {}, candles, periodMs
|
|
|
55
55
|
return binarySearch(candles, timestamp, candle => getDaysOnlyTimestamp(candle.timestamp));
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
+
const searchCloseTimeCandleIndex = (rawTimestamp, options = {}, candles, periodMs = 1000) => {
|
|
59
|
+
var _a, _b, _c, _d, _e, _f;
|
|
60
|
+
const { extrapolate, isDaysPeriod } = options;
|
|
61
|
+
const shouldExtrapolate = Boolean(extrapolate);
|
|
62
|
+
const getDaysOnlyTimestamp = getDaysOnlyTimestampFn(Boolean(isDaysPeriod));
|
|
63
|
+
const timestamp = getDaysOnlyTimestamp(rawTimestamp);
|
|
64
|
+
if (!candles.length) {
|
|
65
|
+
return {
|
|
66
|
+
index: -1,
|
|
67
|
+
exact: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const firstTimestamp = getDaysOnlyTimestamp((_b = (_a = firstOf(candles)) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : 0);
|
|
71
|
+
const lastTimestamp = getDaysOnlyTimestamp((_d = (_c = lastOf(candles)) === null || _c === void 0 ? void 0 : _c.timestamp) !== null && _d !== void 0 ? _d : 0);
|
|
72
|
+
if (timestamp > lastTimestamp) {
|
|
73
|
+
if (shouldExtrapolate) {
|
|
74
|
+
return {
|
|
75
|
+
index: candles.length - 1 + Math.ceil((timestamp - lastTimestamp) / periodMs),
|
|
76
|
+
exact: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
index: candles.length - 1,
|
|
81
|
+
exact: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (timestamp <= firstTimestamp) {
|
|
85
|
+
const previousClose = getDaysOnlyTimestamp((_f = (_e = candles[0]) === null || _e === void 0 ? void 0 : _e.timestamp) !== null && _f !== void 0 ? _f : firstTimestamp);
|
|
86
|
+
const firstCandleStart = previousClose - periodMs;
|
|
87
|
+
if (timestamp <= firstCandleStart) {
|
|
88
|
+
if (shouldExtrapolate) {
|
|
89
|
+
return {
|
|
90
|
+
index: floor((timestamp - firstTimestamp) / periodMs),
|
|
91
|
+
exact: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
index: -1,
|
|
96
|
+
exact: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
index: 0,
|
|
101
|
+
exact: timestamp === firstTimestamp,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
let lo = 0;
|
|
105
|
+
let hi = candles.length;
|
|
106
|
+
while (lo < hi) {
|
|
107
|
+
const mid = floor((lo + hi) / 2);
|
|
108
|
+
if (getDaysOnlyTimestamp(candles[mid].timestamp) < timestamp) {
|
|
109
|
+
lo = mid + 1;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
hi = mid;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const index = lo;
|
|
116
|
+
return {
|
|
117
|
+
index: index >= candles.length ? candles.length - 1 : index,
|
|
118
|
+
exact: index < candles.length && getDaysOnlyTimestamp(candles[index].timestamp) === timestamp,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
export const searchCandleIndex = (rawTimestamp, options = {}, candles, periodMs = 1000) => {
|
|
122
|
+
var _a;
|
|
123
|
+
const anchor = (_a = options.candleTimestampAnchor) !== null && _a !== void 0 ? _a : 'open';
|
|
124
|
+
if (anchor === 'close') {
|
|
125
|
+
return searchCloseTimeCandleIndex(rawTimestamp, options, candles, periodMs);
|
|
126
|
+
}
|
|
127
|
+
return searchOpenTimeCandleIndex(rawTimestamp, options, candles, periodMs);
|
|
128
|
+
};
|
|
129
|
+
export const getCandleStart = (candles, index, periodMs, anchor) => {
|
|
130
|
+
var _a, _b;
|
|
131
|
+
if (anchor === 'close') {
|
|
132
|
+
return (_b = (_a = candles[index - 1]) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : candles[index].timestamp - periodMs;
|
|
133
|
+
}
|
|
134
|
+
return candles[index].timestamp;
|
|
135
|
+
};
|
|
136
|
+
export const getCandleEnd = (candle, periodMs, anchor) => anchor === 'close' ? candle.timestamp : candle.timestamp + periodMs;
|
|
58
137
|
/**
|
|
59
138
|
* checks if the first or the last candle or both have implied volatility data provided
|
|
60
139
|
* @param candles
|
|
@@ -46,7 +46,7 @@ export declare function drawPriceLabel(ctx: CanvasRenderingContext2D, x0: number
|
|
|
46
46
|
*
|
|
47
47
|
* @returns {void}
|
|
48
48
|
*/
|
|
49
|
-
export declare function drawLine(ctx: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number, thickness?: number, lineDash?: Array<number
|
|
49
|
+
export declare function drawLine(ctx: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number, thickness?: number, lineDash?: Array<number>, lineCap?: CanvasLineCap): void;
|
|
50
50
|
/**
|
|
51
51
|
* Try to avoid anti-aliasing
|
|
52
52
|
*/
|
|
@@ -109,10 +109,13 @@ export function drawPriceLabel(ctx, x0, y0, x1, y1, x2, y2, _width, rounded, ali
|
|
|
109
109
|
*
|
|
110
110
|
* @returns {void}
|
|
111
111
|
*/
|
|
112
|
-
export function drawLine(ctx, x0, y0, x1, y1, thickness = 1, lineDash = []) {
|
|
112
|
+
export function drawLine(ctx, x0, y0, x1, y1, thickness = 1, lineDash = [], lineCap) {
|
|
113
113
|
ctx.save();
|
|
114
114
|
ctx.setLineDash(lineDash);
|
|
115
115
|
ctx.lineWidth = thickness;
|
|
116
|
+
if (lineCap !== undefined) {
|
|
117
|
+
ctx.lineCap = lineCap;
|
|
118
|
+
}
|
|
116
119
|
ctx.beginPath();
|
|
117
120
|
ctx.moveTo(x0, y0);
|
|
118
121
|
ctx.lineTo(x1, y1);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2019 - 2026 Devexperts Solutions IE Limited
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
import { Bounds } from '../model/bounds.model';
|
|
7
|
+
export interface CanvasPointerPoint {
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Canvas-space hit test for the lower band (time scale / volume strip).
|
|
13
|
+
* Reusable for any UI that should react when the pointer is near the bottom of the chart.
|
|
14
|
+
*
|
|
15
|
+
* Rectangle matches the Figma hover region (full width, lower ~15% of canvas height plus offsets).
|
|
16
|
+
*/
|
|
17
|
+
export declare function isCanvasPointInLowerHoverStrip(point: CanvasPointerPoint, canvasBounds: Bounds, bottomOffsetPx: number): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2019 - 2026 Devexperts Solutions IE Limited
|
|
3
|
+
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
|
|
4
|
+
* If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Canvas-space hit test for the lower band (time scale / volume strip).
|
|
8
|
+
* Reusable for any UI that should react when the pointer is near the bottom of the chart.
|
|
9
|
+
*
|
|
10
|
+
* Rectangle matches the Figma hover region (full width, lower ~15% of canvas height plus offsets).
|
|
11
|
+
*/
|
|
12
|
+
export function isCanvasPointInLowerHoverStrip(point, canvasBounds, bottomOffsetPx) {
|
|
13
|
+
const rectX = 0;
|
|
14
|
+
const rectY = canvasBounds.height * 0.85 - bottomOffsetPx + 10;
|
|
15
|
+
const rectWidth = canvasBounds.width;
|
|
16
|
+
const rectHeight = canvasBounds.height * 0.15 + bottomOffsetPx + 10;
|
|
17
|
+
const dx = point.x - rectX;
|
|
18
|
+
const dy = point.y - rectY;
|
|
19
|
+
return dx >= 0 && dx < rectWidth && dy >= 0 && dy < rectHeight;
|
|
20
|
+
}
|