@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.
Files changed (31) hide show
  1. package/dist/chart/canvas/canvas-chart-html.js +1 -1
  2. package/dist/chart/chart.config.d.ts +7 -0
  3. package/dist/chart/chart.config.js +2 -0
  4. package/dist/chart/components/chart/chart-base.model.d.ts +2 -0
  5. package/dist/chart/components/chart/chart-base.model.js +2 -1
  6. package/dist/chart/components/chart/chart.model.d.ts +2 -0
  7. package/dist/chart/components/chart/chart.model.js +20 -8
  8. package/dist/chart/components/cross_tool/cross-tool.model.d.ts +1 -0
  9. package/dist/chart/components/cross_tool/cross-tool.model.js +15 -1
  10. package/dist/chart/components/dynamic-objects/dynamic-objects.model.d.ts +7 -0
  11. package/dist/chart/components/dynamic-objects/dynamic-objects.model.js +60 -7
  12. package/dist/chart/components/highlights/highlights.drawer.d.ts +4 -0
  13. package/dist/chart/components/highlights/highlights.drawer.js +27 -5
  14. package/dist/chart/components/y_axis/price_labels/price-label.drawer.js +1 -1
  15. package/dist/chart/components/y_axis/price_labels/y-axis-labels.model.js +6 -3
  16. package/dist/chart/components/y_axis/y-axis-labels.drawer.d.ts +8 -1
  17. package/dist/chart/components/y_axis/y-axis-labels.drawer.js +1 -1
  18. package/dist/chart/inputhandlers/hover-producer.component.d.ts +8 -2
  19. package/dist/chart/inputhandlers/hover-producer.component.js +86 -22
  20. package/dist/chart/model/scale.model.d.ts +2 -6
  21. package/dist/chart/model/scaling/viewport.model.d.ts +2 -0
  22. package/dist/chart/utils/__tests__/canvas-lower-hover-strip.utils.test.d.ts +6 -0
  23. package/dist/chart/utils/__tests__/canvas-lower-hover-strip.utils.test.js +34 -0
  24. package/dist/chart/utils/candles.utils.d.ts +4 -0
  25. package/dist/chart/utils/candles.utils.js +80 -1
  26. package/dist/chart/utils/canvas/canvas-drawing-functions.utils.d.ts +1 -1
  27. package/dist/chart/utils/canvas/canvas-drawing-functions.utils.js +4 -1
  28. package/dist/chart/utils/canvas-lower-hover-strip.utils.d.ts +17 -0
  29. package/dist/chart/utils/canvas-lower-hover-strip.utils.js +20 -0
  30. package/dist/dxchart.min.js +5 -5
  31. 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
- if (lastCandle.candle.timestamp <= this.hover.timestamp) {
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.paneManager.chartPanComponent.setChartPanningOptions(true, true);
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
- // previous fixed goes to temporary to prevent crosstool jumps outside the candle on candle update tick
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: this.crossEventProducer.crossToolTouchInfo.fixed.x,
157
- y: this.crossEventProducer.crossToolTouchInfo.fixed.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.crossEventProducer.crossToolTouchInfo.isSet = true;
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 = (_b = (_a = this.crossEventProducer.crossToolHover) === null || _a === void 0 ? void 0 : _a.paneId) !== null && _b !== void 0 ? _b : 'CHART';
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
- * Update current hover using a VisualCandle and fires it.
236
- * @param {VisualCandle} candle - The VisualCandle object to create the hover from.
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 (this.hover && updatedHover) {
241
- const hover = Object.assign(Object.assign({}, updatedHover), { x: this.hover.x, y: this.hover.y });
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
- export const searchCandleIndex = (rawTimestamp, options = {}, candles, periodMs = 1000) => {
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>): void;
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
+ }