diff --git a/package.json b/package.json index d9e610e..4465419 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devexperts/dxcharts-lite", - "version": "2.6.1", + "version": "2.5.8", "description": "DXCharts Lite", "author": "Devexperts Solutions IE Limited", "license": "MPL 2.0", diff --git a/src/chart/bootstrap.ts b/src/chart/bootstrap.ts index 9b57194..a87b94e 100755 --- a/src/chart/bootstrap.ts +++ b/src/chart/bootstrap.ts @@ -506,7 +506,6 @@ export default class ChartBootstrap { this.canvasBoundsContainer, this.paneManager, timeZoneModel, - chartPanComponent.mainCanvasTouchHandler, formatterFactory, ); this.chartComponents.push(this.hoverProducer); diff --git a/src/chart/canvas/canvas-bounds-container.ts b/src/chart/canvas/canvas-bounds-container.ts index 7ab2269..f68d13f 100644 --- a/src/chart/canvas/canvas-bounds-container.ts +++ b/src/chart/canvas/canvas-bounds-container.ts @@ -60,9 +60,6 @@ const N_MAP_BUTTON_W = 15; const KNOTS_W_MOBILE_MULTIPLIER = 1.5; const N_MAP_KNOT_W = isMobile() ? 8 * KNOTS_W_MOBILE_MULTIPLIER : 8; -// additional x axis height padding for mobiles, used for better usability on mobile devices -export const X_AXIS_MOBILE_PADDING = isMobile() ? 5 : 0; - /** * we need to check that: heightRatios - 1 < 0.000001 after calculations between decimals */ @@ -469,8 +466,7 @@ export class CanvasBoundsContainer { this.xAxisHeight = fontHeight + (this.config.components.xAxis.padding.top ?? 0) + - (this.config.components.xAxis.padding.bottom ?? 0) + - X_AXIS_MOBILE_PADDING; + (this.config.components.xAxis.padding.bottom ?? 0); } return this.xAxisHeight; } diff --git a/src/chart/chart.config.ts b/src/chart/chart.config.ts index 98d03e4..b2e311b 100644 --- a/src/chart/chart.config.ts +++ b/src/chart/chart.config.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2019 - 2024 Devexperts Solutions IE Limited - * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. - * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ /* * Copyright (C) 2019 - 2024 Devexperts Solutions IE Limited * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. @@ -115,7 +110,6 @@ export const getDefaultConfig = (): FullChartConfig => ({ histogram: { barCapSize: 1, }, - maxYAxisScalesAmount: 10, sortCandles: defaultSortCandles, }, yAxis: { @@ -237,7 +231,7 @@ export const getDefaultConfig = (): FullChartConfig => ({ logoWidth: 20, logoHeight: 20, }, - highLow: { visible: false, font: '12px sans-serif', prefix: { high: 'H: ', low: 'L: ' } }, + highLow: { visible: false, font: '12px sans-serif' }, highlights: { visible: false, fontFamily: 'Open Sans', @@ -985,10 +979,6 @@ export interface ChartConfigComponentsChart { selectedWidth: number; minCandlesOffset: number; histogram: ChartConfigComponentsHistogram; - /** - * The maximum amount of Y axis scales on a single chart - */ - maxYAxisScalesAmount: number; // optional because backward compability sortCandles?: (candles: Candle[]) => Candle[]; } @@ -1158,7 +1148,6 @@ export interface ChartConfigComponentsHighLow { * Font config of high/low labels. */ font: string; - prefix: { high: string; low: string }; } export interface ChartConfigComponentsCrossTool { /** diff --git a/src/chart/components/cross_tool/cross-tool.component.ts b/src/chart/components/cross_tool/cross-tool.component.ts index 9b2884b..7b75ed6 100644 --- a/src/chart/components/cross_tool/cross-tool.component.ts +++ b/src/chart/components/cross_tool/cross-tool.component.ts @@ -41,17 +41,11 @@ export class CrossToolComponent extends ChartBaseElement { this.crossToolCanvasModel, crossEventProducer, hoverProducer, - this.canvasBoundsContainer, ); this.addChildEntity(this.model); const clearCanvasDrawer = new ClearCanvasDrawer(this.crossToolCanvasModel); this.registerDefaultDrawerTypes(); - const crossToolDrawer = new CrossToolDrawer( - this.model, - this.config, - this.crossToolCanvasModel, - this.crossToolTypeDrawers, - ); + const crossToolDrawer = new CrossToolDrawer(this.model, this.crossToolCanvasModel, this.crossToolTypeDrawers); const compositeDrawer = new CompositeDrawer(); compositeDrawer.addDrawer(clearCanvasDrawer, 'CLEAR_CANVAS'); compositeDrawer.addDrawer(crossToolDrawer, 'CROSS_TOOL_DRAWER'); diff --git a/src/chart/components/cross_tool/cross-tool.drawer.ts b/src/chart/components/cross_tool/cross-tool.drawer.ts index 36b2cd7..ac8018c 100644 --- a/src/chart/components/cross_tool/cross-tool.drawer.ts +++ b/src/chart/components/cross_tool/cross-tool.drawer.ts @@ -6,7 +6,6 @@ import { CanvasModel } from '../../model/canvas.model'; import { Drawer } from '../../drawers/drawing-manager'; import { CrossToolHover, CrossToolModel, CrossToolType } from './cross-tool.model'; -import { FullChartConfig } from '../../chart.config'; export interface CrossToolTypeDrawer { draw: (ctx: CanvasRenderingContext2D, hover: CrossToolHover) => void; @@ -15,7 +14,6 @@ export interface CrossToolTypeDrawer { export class CrossToolDrawer implements Drawer { constructor( private model: CrossToolModel, - private config: FullChartConfig, private crossToolCanvasModel: CanvasModel, private readonly crossToolTypeDrawers: Record, ) {} @@ -29,13 +27,11 @@ export class CrossToolDrawer implements Drawer { * @returns {void} */ draw() { - const drawer = this.crossToolTypeDrawers[this.config.components.crossTool.type]; + const drawer = this.crossToolTypeDrawers[this.model.type]; if (drawer) { this.model.currentHover && drawer.draw(this.crossToolCanvasModel.ctx, this.model.currentHover); } else { - console.error( - `No cross tool drawer type registered for drawer type ${this.config.components.crossTool.type}`, - ); + console.error(`No cross tool drawer type registered for drawer type ${this.model.type}`); } } diff --git a/src/chart/components/cross_tool/cross-tool.model.ts b/src/chart/components/cross_tool/cross-tool.model.ts index adc2869..3206f6f 100644 --- a/src/chart/components/cross_tool/cross-tool.model.ts +++ b/src/chart/components/cross_tool/cross-tool.model.ts @@ -9,8 +9,7 @@ import { CrossEventProducerComponent } from '../../inputhandlers/cross-event-pro import { Hover, HoverProducerComponent } from '../../inputhandlers/hover-producer.component'; import { CanvasModel } from '../../model/canvas.model'; import { ChartBaseElement } from '../../model/chart-base-element'; -import { CanvasBoundsContainer, CanvasElement, CHART_UUID } from '../../canvas/canvas-bounds-container'; -import { isMobile } from '../../utils/device/browser.utils'; +import { CHART_UUID } from '../../canvas/canvas-bounds-container'; export type CrossToolType = 'cross-and-labels' | 'only-labels' | 'none' | string; @@ -30,15 +29,16 @@ export class CrossToolModel extends ChartBaseElement { set currentHover(value: CrossToolHover | null) { this.currentHoverSubject.next(value); } + type: CrossToolType = 'cross-and-labels'; constructor( private config: Required, private crossToolCanvasModel: CanvasModel, private crossEventProducer: CrossEventProducerComponent, private hoverProducer: HoverProducerComponent, - private canvasBoundsContainer: CanvasBoundsContainer, ) { super(); + this.type = config.type; } /** @@ -48,19 +48,21 @@ export class CrossToolModel extends ChartBaseElement { * @returns {void} */ public setType(type: CrossToolType) { - this.config.type = type; + this.type = type; } /** * Method to activate the cross tool. - * It subscribes to the hoverProducer's hover event and updates the crosstool. + * It subscribes to the canvasInputListener's mouse move event and fires the draw event. + * It also subscribes to the eventBus's hover and close hover events and updates the hover and fires the draw event accordingly. + * It also subscribes to the chartModel's candlesSetSubject and timeZoneModel's observeTimeZoneChanged events and recalculates the cross tool X formatter. */ protected doActivate() { super.doActivate(); this.addRxSubscription( this.hoverProducer.hoverSubject.subscribe(hover => { if (this.crossEventProducer.crossSubject.getValue() !== null && hover !== null) { - isMobile() ? this.updateCrossToolMobile(hover) : this.updateCrossTool(hover); + this.updateHover(hover); } else { this.currentHover = null; } @@ -74,7 +76,7 @@ export class CrossToolModel extends ChartBaseElement { * @private */ private fireDraw() { - if (this.config.type !== 'none') { + if (this.type !== 'none') { this.crossToolCanvasModel.fireDraw(); } } @@ -93,7 +95,7 @@ export class CrossToolModel extends ChartBaseElement { * @param {number} hover.candleHover.closestOHLCY - The y coordinate of the closest OHLC price of the candle. * @returns {void} */ - public updateCrossTool(hover: Hover, magnetTarget = this.config.magnetTarget) { + public updateHover(hover: Hover, magnetTarget = this.config.magnetTarget) { if (this.currentHover === null) { this.currentHover = { x: hover.x, y: 0, time: hover.timeFormatted, paneId: hover.paneId }; } else { @@ -127,49 +129,4 @@ export class CrossToolModel extends ChartBaseElement { this.currentHover.paneId = hover.paneId; this.currentHoverSubject.next(this.currentHover); } - - private updateCrossToolMobile(hover: Hover) { - // mobile crosstool works only after long touch event - if (!this.hoverProducer.longTouchActivatedSubject.getValue()) { - return; - } - - const { fixed, temp, isSet } = this.crossEventProducer.crossToolTouchInfo; - - // long touch makes crosstool fixed and the further moving will place crosstool under the current hover coordinates (ordinary logic) - // if crosstool is already set (long touch end event happened) the moving of chart will move crosstool according to it's initial (fixed position) - if (!isSet) { - this.updateCrossTool(hover); - return; - } - - // additional crosstool move logic - const paneBounds = this.canvasBoundsContainer.getBounds(CanvasElement.PANE_UUID(hover.paneId)); - const offset = 5; - - // take a difference inbetween current hover and temporary crosstool coordinates - const xDiff = hover.x - temp.x; - const yDiff = hover.y - temp.y; - - // apply the difference to the fixed coordinates - const rawX = fixed.x + xDiff; - const rawY = fixed.y + yDiff; - - const paneYStart = paneBounds.y + offset; - const paneYEnd = paneBounds.y + paneBounds.height - offset; - - // check for chart bounds and don't move crosstool outside of it - const x = rawX < offset ? offset : rawX > paneBounds.width - offset ? paneBounds.width - offset : rawX; - const y = rawY < paneYStart ? paneYStart : rawY > paneYEnd ? paneYEnd : rawY; - const crossToolHover = this.hoverProducer.createHover(x, y, hover.paneId) ?? hover; - - const updatedHover = { - ...crossToolHover, - x, - y, - }; - - this.crossEventProducer.crossToolHover = updatedHover; - this.updateCrossTool(updatedHover); - } } diff --git a/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts b/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts index e756f30..dc3639c 100644 --- a/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts +++ b/src/chart/components/cross_tool/types/cross-and-labels.drawer.ts @@ -3,7 +3,7 @@ * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { CanvasBoundsContainer, CanvasElement, X_AXIS_MOBILE_PADDING } from '../../../canvas/canvas-bounds-container'; +import { CanvasBoundsContainer, CanvasElement } from '../../../canvas/canvas-bounds-container'; import { FullChartConfig } from '../../../chart.config'; import { avoidAntialiasing, drawRoundedRect } from '../../../utils/canvas/canvas-drawing-functions.utils'; import { PaneManager } from '../../pane/pane-manager.component'; @@ -112,12 +112,12 @@ export class CrossAndLabelsDrawerType implements CrossToolTypeDrawer { const boxWidth = width + xLabelPaddingLeft + xLabelPaddingRight; const boxHeight = fontHeight + xLabelPaddingTop + xLabelPaddingBottom; const xBoxPos = Math.max(x - boxWidth / 2, 0); - const yBoxPos = xAxis.y + xLabelMarginTop + X_AXIS_MOBILE_PADDING / 2; + const yBoxPos = xAxis.y + xLabelMarginTop; drawRoundedRect(ctx, xBoxPos, yBoxPos, boxWidth, boxHeight); // label ctx.fillStyle = crossToolColors.labelTextColor; const xTextPos = Math.max(x - width / 2, xLabelPaddingLeft); - const yTextPos = xAxis.y + xLabelMarginTop + fontHeight + X_AXIS_MOBILE_PADDING / 2 + xLabelPaddingTop - 1; // -1 for vertical adjustment + const yTextPos = xAxis.y + xLabelMarginTop + fontHeight + xLabelPaddingTop - 1; // -1 for vertical adjustment ctx.fillText(labelText, xTextPos, yTextPos); ctx.restore(); } diff --git a/src/chart/components/high_low/high-low.drawer.ts b/src/chart/components/high_low/high-low.drawer.ts index dfe8187..81a3a69 100644 --- a/src/chart/components/high_low/high-low.drawer.ts +++ b/src/chart/components/high_low/high-low.drawer.ts @@ -1,8 +1,3 @@ -/* - * Copyright (C) 2019 - 2024 Devexperts Solutions IE Limited - * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. - * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ /* * Copyright (C) 2019 - 2024 Devexperts Solutions IE Limited * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. @@ -89,8 +84,7 @@ export class HighLowDrawer implements Drawer { */ private getMarkerText(yValue: number, type: MarkerType): string { const formattedValue = this.chartModel.pane.regularFormatter(yValue); - const prefix = - type === 'high' ? this.config.components.highLow.prefix.high : this.config.components.highLow.prefix.low; + const prefix = type === 'high' ? 'H:' : 'L:'; return `${prefix} ${formattedValue}`; } diff --git a/src/chart/components/pane/pane-manager.component.ts b/src/chart/components/pane/pane-manager.component.ts index 14fb3cd..a443f64 100644 --- a/src/chart/components/pane/pane-manager.component.ts +++ b/src/chart/components/pane/pane-manager.component.ts @@ -34,8 +34,6 @@ import { HitTestCanvasModel } from '../../model/hit-test-canvas.model'; import { firstOf, flatMap, lastOf } from '../../utils/array.utils'; import { ChartResizeHandler } from '../../inputhandlers/chart-resize.handler'; -export type MoveDataSeriesToPaneDirection = 'above' | 'below'; - export class PaneManager extends ChartBaseElement { public panes: Record = {}; public paneRemovedSubject: Subject = new Subject(); @@ -288,35 +286,6 @@ export class PaneManager extends ChartBaseElement { this.recalculateState(); } - /** - * Move data series to a certain pane, or create a new one if no pane is found - */ - public moveDataSeriesToPane( - dataSeries: DataSeriesModel[], - initialPane: PaneComponent, - initialExtent: YExtentComponent, - paneUUID?: string, - extent?: YExtentComponent, - direction?: MoveDataSeriesToPaneDirection, - ) { - const pane = paneUUID && this.panes[paneUUID]; - - if (!pane) { - const order = direction && direction === 'above' ? 0 : this.panesOrder.length; - const newPane = this.createPane(undefined, { order }); - newPane.moveDataSeriesToExistingExtentComponent(dataSeries, initialPane, initialExtent, newPane.mainExtent); - initialPane.yExtentComponents.length === 0 && this.removePane(initialPane.uuid); - return; - } - - if (extent) { - pane.moveDataSeriesToExistingExtentComponent(dataSeries, initialPane, initialExtent, extent); - } else { - pane.moveDataSeriesToNewExtentComponent(dataSeries, initialPane, initialExtent); - } - initialPane.yExtentComponents.length === 0 && this.removePane(initialPane.uuid); - } - /** * Adds cursors to the chart elements based on the provided uuid and cursor type. * @private diff --git a/src/chart/components/pane/pane.component.ts b/src/chart/components/pane/pane.component.ts index c964f4e..37a5dca 100644 --- a/src/chart/components/pane/pane.component.ts +++ b/src/chart/components/pane/pane.component.ts @@ -13,7 +13,7 @@ import { HitBoundsTest, } from '../../canvas/canvas-bounds-container'; import { CursorHandler } from '../../canvas/cursor.handler'; -import { FullChartConfig, YAxisAlign, YAxisConfig } from '../../chart.config'; +import { FullChartConfig, YAxisConfig } from '../../chart.config'; import { DrawingManager } from '../../drawers/drawing-manager'; import EventBus from '../../events/event-bus'; import { CanvasInputListenerComponent } from '../../inputlisteners/canvas-input-listener.component'; @@ -267,34 +267,6 @@ export class PaneComponent extends ChartBaseElement { this.canvasBoundsContainer.updateYAxisWidths(); } - /** - * Create new pane extent and attach data series to it - */ - public moveDataSeriesToNewExtentComponent( - dataSeries: DataSeriesModel[], - initialPane: PaneComponent, - initialExtent: YExtentComponent, - align: YAxisAlign = 'right', - ) { - const extent = this.createExtentComponent(); - extent.yAxis.setYAxisAlign(align); - dataSeries.forEach(series => series.moveToExtent(extent)); - initialExtent.dataSeries.size === 0 && initialPane.removeExtentComponent(initialExtent); - } - - /** - * Attach data series to existing y axis extent - */ - public moveDataSeriesToExistingExtentComponent( - dataSeries: DataSeriesModel[], - initialPane: PaneComponent, - initialExtent: YExtentComponent, - extentComponent: YExtentComponent, - ) { - dataSeries.forEach(series => series.moveToExtent(extentComponent)); - initialExtent.dataSeries.size === 0 && initialPane.removeExtentComponent(initialExtent); - } - /** * This method updates the view by calling the doAutoScale method of the scaleModel and firing the Draw event using the eventBus. * @private diff --git a/src/chart/components/x_axis/x-axis-draw.functions.ts b/src/chart/components/x_axis/x-axis-draw.functions.ts index ff996d8..623c824 100644 --- a/src/chart/components/x_axis/x-axis-draw.functions.ts +++ b/src/chart/components/x_axis/x-axis-draw.functions.ts @@ -3,7 +3,7 @@ * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { CanvasBoundsContainer, CanvasElement, X_AXIS_MOBILE_PADDING } from '../../canvas/canvas-bounds-container'; +import { CanvasBoundsContainer, CanvasElement } from '../../canvas/canvas-bounds-container'; import { FullChartConfig } from '../../chart.config'; import { XAxisLabel } from './x-axis-labels.model'; @@ -63,7 +63,7 @@ export function drawXAxisLabel( ctx.fillStyle = label.color; const xTextPos = boxStart + DEFAULT_X_LABEL_PADDING.x; - const yTextPos = xAxisBounds.y + offsetTop + fontSize + X_AXIS_MOBILE_PADDING / 2; + const yTextPos = xAxisBounds.y + offsetTop + fontSize; // -2 for vertical adjustment ctx.fillText(label.text, xTextPos, yTextPos); ctx.restore(); } diff --git a/src/chart/components/x_axis/x-axis-scale.handler.ts b/src/chart/components/x_axis/x-axis-scale.handler.ts index d70943f..90f8c74 100644 --- a/src/chart/components/x_axis/x-axis-scale.handler.ts +++ b/src/chart/components/x_axis/x-axis-scale.handler.ts @@ -14,16 +14,12 @@ import { DragNDropXComponent } from '../dran-n-drop_helper/drag-n-drop-x.compone import { ChartPanComponent } from '../pan/chart-pan.component'; import { HitTestCanvasModel } from '../../model/hit-test-canvas.model'; -// if you drag full X width from left to right - you will have x3 zoom, and vice-versa -const FULL_X_WIDTH_ZOOM_FACTOR = 3; - /** * Handles the mouse drag on X axis - to zoom the viewport horizontally. * @doc-tags scaling,zoom,viewport */ export class XAxisScaleHandler extends ChartBaseElement { lastXStart: Unit = 0; - lastXEnd: Unit = 0; lastXWidth: Unit = 0; lastXPxWidth: Pixel = 0; @@ -101,7 +97,6 @@ export class XAxisScaleHandler extends ChartBaseElement { private onXDragStart = () => { this.lastXStart = this.scale.xStart; - this.lastXEnd = this.scale.xEnd; this.lastXWidth = this.scale.xEnd - this.scale.xStart; const bounds = this.canvasBoundsContainer.getBounds(CanvasElement.X_AXIS); this.lastXPxWidth = bounds.width - this.canvasInputListener.currentPoint.x; @@ -110,44 +105,16 @@ export class XAxisScaleHandler extends ChartBaseElement { }; private onXDragTick = (dragInfo: DragInfo) => { - let { delta: absoluteXDelta } = dragInfo; - - // mouse click or single tap drag - if (!this.touches || this.touches.length === 1) { - const newPxWidth = this.lastXPxWidth - absoluteXDelta; - // cursor goes beyond x-axis - maximum scale is reached - if (newPxWidth < 0) { - return; - } - const xZoomMult = this.lastXPxWidth / newPxWidth; - const newWidth = this.lastXWidth * xZoomMult; - const newXStart = this.lastXStart + (this.lastXWidth - newWidth); - this.scale.setXScale(newXStart, this.scale.xEnd); + const { delta: absoluteXDelta } = dragInfo; + const newPxWidth = this.lastXPxWidth - absoluteXDelta; + // cursor goes beyond x-axis - maximum scale is reached + if (newPxWidth < 0) { return; } - - // if multitouch - take the first two touch events and apply delta the following way: - // the touch on the left keeps the initial delta because left => right gesture - // the touch on the right has the reversed delta because the gesture is right => left - if (this.touches.length > 1) { - const left = Math.min(this.touches[0].clientX, this.touches[1].clientX); - absoluteXDelta = left === this.touches[0].clientX ? absoluteXDelta : -absoluteXDelta; - - let xZoomMult; - if (absoluteXDelta < 0) { - xZoomMult = 1 / (1 + (-absoluteXDelta / this.lastXPxWidth) * (FULL_X_WIDTH_ZOOM_FACTOR - 1)); - } else { - xZoomMult = 1 + (absoluteXDelta / this.lastXPxWidth) * (FULL_X_WIDTH_ZOOM_FACTOR - 1); - } - - const newXWidth = this.lastXWidth * xZoomMult; - const delta = (newXWidth - this.lastXWidth) / 2; - const newXStart = this.lastXStart - delta; - const newXEnd = this.lastXEnd + delta; - if (this.lastXStart !== newXStart || this.lastXEnd !== newXEnd) { - this.scale.setXScale(newXStart, newXEnd); - } - } + const xZoomMult = this.lastXPxWidth / newPxWidth; + const newWidth = this.lastXWidth * xZoomMult; + const newXStart = this.lastXStart + (this.lastXWidth - newWidth); + this.scale.setXScale(newXStart, this.scale.xEnd); }; private onXDragEnd = () => { diff --git a/src/chart/components/x_axis/x-axis-time-labels.drawer.ts b/src/chart/components/x_axis/x-axis-time-labels.drawer.ts index 4084ed6..37ac945 100644 --- a/src/chart/components/x_axis/x-axis-time-labels.drawer.ts +++ b/src/chart/components/x_axis/x-axis-time-labels.drawer.ts @@ -6,7 +6,7 @@ import { NumericAxisLabel } from '../labels_generator/numeric-axis-labels.generator'; import { Bounds } from '../../model/bounds.model'; import { FullChartConfig } from '../../chart.config'; -import { CanvasBoundsContainer, CanvasElement, X_AXIS_MOBILE_PADDING } from '../../canvas/canvas-bounds-container'; +import { CanvasBoundsContainer, CanvasElement } from '../../canvas/canvas-bounds-container'; import { CanvasModel } from '../../model/canvas.model'; import { Drawer } from '../../drawers/drawing-manager'; import { ViewportModel } from '../../model/scaling/viewport.model'; @@ -46,7 +46,7 @@ export class XAxisTimeLabelsDrawer implements Drawer { const color = this.config.colors.xAxis.labelTextColor; const labels = this.labelsProvider(); - this.drawLabels(ctx, labels, bounds, color, fontHeight, fontFamily, offsetTop + X_AXIS_MOBILE_PADDING / 2); + this.drawLabels(ctx, labels, bounds, color, fontHeight, fontFamily, offsetTop); ctx.restore(); } } diff --git a/src/chart/inputhandlers/cross-event-producer.component.ts b/src/chart/inputhandlers/cross-event-producer.component.ts index bf093d0..d126740 100644 --- a/src/chart/inputhandlers/cross-event-producer.component.ts +++ b/src/chart/inputhandlers/cross-event-producer.component.ts @@ -9,47 +9,14 @@ import { CanvasBoundsContainer, HitBoundsTest, HitBoundsTestOptionsPartial } fro import { CanvasInputListenerComponent } from '../inputlisteners/canvas-input-listener.component'; import { ChartBaseElement } from '../model/chart-base-element'; import { Unsubscriber } from '../utils/function.utils'; -import { Pixel } from '../model/scaling/viewport.model'; -import { Hover } from './hover-producer.component'; -import { isMobile } from '../utils/device/browser.utils'; /** * [x, y, uuid - Unique identifier for the subscription] */ export type CrossEvent = [number, number, string]; - -interface CrossToolTouchInfo { - // the placement of crosstool when it's set after long touch and further moving happens - fixed: { - x: Pixel; - y: Pixel; - }; - // // the placement of crosstool after touchMove and touchEnd happened - temp: { - x: Pixel; - y: Pixel; - }; - // true after longTouch event, initial movement and touchEnd happen - isSet: boolean; - /** - * additional flag to determine ordinary and long taps - * crosstool shouldn't be hidden after longtouch event even if coordinates are the same - * becomes true after touchStart and false after longTouchStart - */ - isCommonTap: boolean; -} - export class CrossEventProducerComponent extends ChartBaseElement { panesSubscriptions: Partial> = {}; public crossSubject: BehaviorSubject = new BehaviorSubject(null); - // mobile specific crosstool hover and touch info - public crossToolHover: Hover | null = null; - public crossToolTouchInfo: CrossToolTouchInfo = { - fixed: { x: 0, y: 0 }, - temp: { x: 0, y: 0 }, - isSet: false, - isCommonTap: false, - }; constructor( private canvasInputListener: CanvasInputListenerComponent, private canvasBoundsContainer: CanvasBoundsContainer, @@ -113,9 +80,7 @@ export class CrossEventProducerComponent extends ChartBaseElement { const cross: CrossEvent = [point.x, point.y, uuid]; this.crossSubject.next(cross); closeHoverFired = false; - } - // crosstool should be hidden if hovering nonpane only on desktop - if (!enter && !isMobile()) { + } else { this.crossSubject.next(null); closeHoverFired = true; } diff --git a/src/chart/inputhandlers/hover-producer.component.ts b/src/chart/inputhandlers/hover-producer.component.ts index ecd724c..7ce5cbe 100644 --- a/src/chart/inputhandlers/hover-producer.component.ts +++ b/src/chart/inputhandlers/hover-producer.component.ts @@ -19,7 +19,6 @@ import VisualCandle from '../model/visual-candle'; import { isMobile } from '../utils/device/browser.utils'; import { CrossEvent, CrossEventProducerComponent } from './cross-event-producer.component'; import { TimeZoneModel } from '../model/time-zone.model'; -import { checkChartIsMoving, MainCanvasTouchHandler } from './main-canvas-touch.handler'; export interface BaseHover { readonly x: number; @@ -61,9 +60,8 @@ export class HoverProducerComponent extends ChartBaseElement { get hover(): Hover | null { return this.hoverSubject.getValue(); } - public longTouchActivatedSubject: BehaviorSubject = new BehaviorSubject(false); + private longTouchActivatedSubject: BehaviorSubject = new BehaviorSubject(false); private hoverProducerParts: HoverProducerParts; - xFormatter: DateTimeFormatter = () => ''; constructor( private crossEventProducer: CrossEventProducerComponent, @@ -74,7 +72,6 @@ export class HoverProducerComponent extends ChartBaseElement { private canvasBoundsContainer: CanvasBoundsContainer, private paneManager: PaneManager, private timeZoneModel: TimeZoneModel, - private mainCanvasTouchHandler: MainCanvasTouchHandler, private formatterFactory: (format: string) => (timestamp: number) => string, ) { super(); @@ -130,106 +127,46 @@ export class HoverProducerComponent extends ChartBaseElement { }), ); this.addRxSubscription(this.scale.xChanged.subscribe(() => this.fireLastCross())); - this.addRxSubscription( - merge(this.chartModel.candlesSetSubject, this.timeZoneModel.observeTimeZoneChanged()).subscribe(() => - this.recalculateCrossToolXFormatter(), - ), - ); - //#region crosstool touch events, special handling for mobile this.addRxSubscription( this.canvasInputListener.observeTouchStart().subscribe(event => { - this.crossEventProducer.crossToolTouchInfo.isCommonTap = true; - const { clientX, clientY } = event.touches[0]; - - // if common tap - fire hover - if (!this.longTouchActivatedSubject.getValue()) { - const paneId = this.paneManager.getPaneIfHit({ x: clientX, y: clientY })?.uuid || ''; - this.createAndFireHover([clientX, clientY, paneId]); - } else { - // update crosstool placement coordinates - this.crossEventProducer.crossToolTouchInfo.temp = { - x: clientX - this.canvasBoundsContainer.canvasOnPageLocation.x, - y: clientY - this.canvasBoundsContainer.canvasOnPageLocation.y, - }; + const x = event.touches[0].clientX - this.canvasBoundsContainer.canvasOnPageLocation.x; + const y = event.touches[0].clientY - this.canvasBoundsContainer.canvasOnPageLocation.y; + const candle = this.chartModel.candleFromX(x, true); + const paneId = this.paneManager.getPaneIfHit({ x, y })?.uuid || ''; + if (candle) { + this.createAndFireHover([x, y, paneId]); } }), ); + + // special handling for mobile // on long touch - disable panning and show cross tool const hitTest = this.canvasBoundsContainer.getBoundsHitTest(CanvasElement.ALL_PANES); this.addRxSubscription( this.canvasInputListener.observeLongTouchStart(hitTest).subscribe(event => { - this.crossEventProducer.crossToolHover = null; - this.crossEventProducer.crossToolTouchInfo.isCommonTap = false; - // don't lock chart and show crosshair if chart is being moved, crosstool is not enabled or we do pinch event - const longTouchCrosshairPredicate = - this.mainCanvasTouchHandler.canvasTouchInfo.isMoving || - this.chartModel.config.components.crossTool.type === 'none' || - event.touches.length > 1; - - if (longTouchCrosshairPredicate) { - return; - } - + this.paneManager.chartPanComponent.deactivatePanHandlers(); this.longTouchActivatedSubject.next(true); - this.crossEventProducer.crossToolTouchInfo.isSet = false; - const x = event.touches[0].clientX - this.canvasBoundsContainer.canvasOnPageLocation.x; const y = event.touches[0].clientY - this.canvasBoundsContainer.canvasOnPageLocation.y; - - this.crossEventProducer.crossToolTouchInfo.fixed = { - x, - y, - }; - const paneId = this.paneManager.getPaneIfHit({ x, y })?.uuid || ''; this.createAndFireHover([x, y, paneId]); this.crossEventProducer.crossSubject.next([x, y, paneId]); - this.paneManager.chartPanComponent.setChartPanningOptions(false, false); }), ); this.addRxSubscription( - this.canvasInputListener.observeTouchEndDocument().subscribe(event => { - const { clientX, clientY } = event.changedTouches[0]; - const { fixed, temp } = this.crossEventProducer.crossToolTouchInfo; - - const x = clientX - this.canvasBoundsContainer.canvasOnPageLocation.x; - const y = clientY - this.canvasBoundsContainer.canvasOnPageLocation.y; - - // common tap without moving, hide crosstool - if ( - this.crossEventProducer.crossToolTouchInfo.isCommonTap && - !checkChartIsMoving(x, temp.x, y, temp.y) - ) { - this.paneManager.chartPanComponent.setChartPanningOptions(true, true); + this.canvasInputListener.observeTouchEndDocument().subscribe(() => { + this.paneManager.chartPanComponent.activateChartPanHandlers(); + if (this.longTouchActivatedSubject.getValue()) { this.longTouchActivatedSubject.next(false); this.crossEventProducer.fireCrossClose(); - this.crossEventProducer.crossToolHover = null; - } - - if (!this.crossEventProducer.crossToolTouchInfo.isSet) { - this.crossEventProducer.crossToolTouchInfo.isSet = true; - this.crossEventProducer.crossToolTouchInfo.fixed = { - x, - y, - }; - this.crossEventProducer.crossToolTouchInfo.isSet = true; - } else { - const pane = this.crossEventProducer.crossToolHover?.paneId ?? 'CHART'; - const paneBounds = this.canvasBoundsContainer.getBounds(CanvasElement.PANE_UUID(pane)); - - const paneYStart = paneBounds.y + 5; - const paneYEnd = paneBounds.y + paneBounds.height - 5; - - const xDiff = x - temp.x; - const yDiff = y - temp.y; - - const newX = fixed.x < 0 ? 0 : fixed.x > paneBounds.width ? paneBounds.width : (fixed.x += xDiff); - const newY = fixed.y < paneYStart ? paneYStart : fixed.y > paneYEnd ? paneYEnd : (fixed.y += yDiff); - this.crossEventProducer.crossToolTouchInfo.fixed = { x: newX, y: newY }; } }), ); - //#endregion + this.addRxSubscription( + merge(this.chartModel.candlesSetSubject, this.timeZoneModel.observeTimeZoneChanged()).subscribe(() => + this.recalculateCrossToolXFormatter(), + ), + ); } /** @@ -261,7 +198,7 @@ export class HoverProducerComponent extends ChartBaseElement { * @returns {Hover | undefined} - The hover object or undefined if there are no candles in the chart model. * @todo Check if uuid is still useful here. */ - public createHover(x: number, y: number, uuid: string): Hover | undefined { + private createHover(x: number, y: number, uuid: string): Hover | undefined { if (this.chartModel.getCandles().length === 0) { return; } @@ -338,12 +275,9 @@ export class HoverProducerComponent extends ChartBaseElement { private fireHover(hover?: Hover) { if (hover) { // special handling for mobile - // set active candle + show cross tool only when crosstool is active + // set active candle + show cross tool only when long tap if (isMobile() && this.config.components.crossTool.type !== 'none') { - const crossToolHover = this.crossEventProducer.crossToolHover; - const candle = crossToolHover - ? crossToolHover.candleHover?.visualCandle.candle - : hover.candleHover?.visualCandle.candle; + const candle = hover.candleHover?.visualCandle.candle; candle && this.chartModel.mainCandleSeries.setActiveCandle(candle); } this.hoverSubject.next(hover); diff --git a/src/chart/inputhandlers/main-canvas-touch.handler.ts b/src/chart/inputhandlers/main-canvas-touch.handler.ts index 70818c6..ed2d8d1 100644 --- a/src/chart/inputhandlers/main-canvas-touch.handler.ts +++ b/src/chart/inputhandlers/main-canvas-touch.handler.ts @@ -8,9 +8,6 @@ import { CanvasInputListenerComponent } from '../inputlisteners/canvas-input-lis import { ScaleModel } from '../model/scale.model'; import { ChartAreaPanHandler } from '../components/chart/chart-area-pan.handler'; import { HitBoundsTest } from '../canvas/canvas-bounds-container'; -import { Pixel } from '../model/scaling/viewport.model'; - -export const PIXELS_FOR_MOVE = 2; /** * Handles chart touch events. @@ -18,15 +15,6 @@ export const PIXELS_FOR_MOVE = 2; export class MainCanvasTouchHandler extends ChartBaseElement { // 2 candles indexes touched by 2 fingers when pinching private touchedCandleIndexes: [number, number] = [0, 0]; - // stores the information about touch events - public canvasTouchInfo: { - touchStart: { x: Pixel; y: Pixel }; - isMoving?: boolean; - } = { - touchStart: { x: 0, y: 0 }, - // uses touch start to determine if chart is being moved - isMoving: false, - }; constructor( private chartAreaPanHandler: ChartAreaPanHandler, private scale: ScaleModel, @@ -60,12 +48,6 @@ export class MainCanvasTouchHandler extends ChartBaseElement { * @returns {void} */ private handleTouchStartEvent(e: TouchEvent) { - const { clientX, clientY } = e.touches[0]; - - if (e.touches.length === 1) { - this.canvasTouchInfo.touchStart = { x: clientX, y: clientY }; - } - if (e.touches.length === 2) { this.chartAreaPanHandler.deactivate(); // @ts-ignore @@ -80,13 +62,6 @@ export class MainCanvasTouchHandler extends ChartBaseElement { * @returns {void} */ private handleTouchMoveEvent(e: TouchEvent): void { - const { clientX, clientY } = e.touches[0]; - const { touchStart } = this.canvasTouchInfo; - - if (e.touches.length === 1) { - this.canvasTouchInfo.isMoving = checkChartIsMoving(clientX, touchStart.x, clientY, touchStart.y); - } - if (e.touches.length === 2) { this.pinchHandler(this.touchedCandleIndexes, this.getXPositions(e)); } @@ -96,7 +71,6 @@ export class MainCanvasTouchHandler extends ChartBaseElement { * @returns {void} */ private handleTouchEndEvent(e: TouchEvent): void { - this.canvasTouchInfo.isMoving = false; // zero touches means the user stopped resizing completely (both fingers are up) if (e.touches.length === 0) { this.chartAreaPanHandler.activate(); @@ -141,6 +115,3 @@ export class MainCanvasTouchHandler extends ChartBaseElement { this.scale.setXScale(first, last); } } - -export const checkChartIsMoving = (x1: Pixel, x2: Pixel, y1: Pixel, y2: Pixel, pixelsToMove: Pixel = PIXELS_FOR_MOVE) => - Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) > pixelsToMove; diff --git a/src/chart/inputlisteners/canvas-input-listener.component.ts b/src/chart/inputlisteners/canvas-input-listener.component.ts index 6f78035..b1d8fb8 100644 --- a/src/chart/inputlisteners/canvas-input-listener.component.ts +++ b/src/chart/inputlisteners/canvas-input-listener.component.ts @@ -248,8 +248,10 @@ export class CanvasInputListenerComponent extends ChartBaseElement { this.addSubscription(subscribeListener(this.element, doubleTapListenerProducer, 'touchend')); // workaround to handle long touch start/end for iOS - const longTouchListeners = (e: TouchEvent, delay = 200) => { + const longTouchListeners = (e: TouchEvent, delay = 700, pixelsForMoveReset = 3) => { e.preventDefault(); + const initialCoords = { x: e.touches[0].clientX, y: e.touches[0].clientY }; + let coords = { x: 0, y: 0 }; let longTouchStart = false; let timerLongTouchStart: ReturnType | null = null; @@ -261,8 +263,13 @@ export class CanvasInputListenerComponent extends ChartBaseElement { const touchMoveHandler = (e: TouchEvent) => { e.preventDefault(); + coords = { x: e.touches[0].clientX, y: e.touches[0].clientY }; // clear long touch timeout if move area is bigger than pixelsForMove - if (e.touches.length > 1) { + if ( + Math.sqrt(Math.pow(coords.x - initialCoords.x, 2) + Math.pow(coords.y - initialCoords.y, 2)) > + pixelsForMoveReset || + e.touches.length > 1 + ) { timerLongTouchStart && clearTimeout(timerLongTouchStart); } }; diff --git a/src/chart/model/candle-series.model.ts b/src/chart/model/candle-series.model.ts index 47d89c9..caca538 100644 --- a/src/chart/model/candle-series.model.ts +++ b/src/chart/model/candle-series.model.ts @@ -162,7 +162,7 @@ export class CandleSeriesModel extends DataSeriesModel { */ recalculateZippedHighLow(): HighLowWithIndex { return (this.zippedHighLow = calculateCandlesHighLow( - this.visualPoints.slice(this.dataIdxStart, this.dataIdxEnd + 1), + this.visualPoints.slice(this.dataIdxStart, this.dataIdxEnd), )); } diff --git a/src/chart/utils/math.utils.ts b/src/chart/utils/math.utils.ts index 8470903..1bafde7 100644 --- a/src/chart/utils/math.utils.ts +++ b/src/chart/utils/math.utils.ts @@ -1,10 +1,10 @@ +import { StringTMap } from './object.utils'; + /* * Copyright (C) 2019 - 2024 Devexperts Solutions IE Limited * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { StringTMap } from './object.utils'; - const MAX_DECIMAL_DIGITS = 14; // Array of powers of 10. Used in roundDecimal to walk through mantissa.