diff --git a/src/Chart.ts b/src/Chart.ts index 43986b533..10c8d0c94 100644 --- a/src/Chart.ts +++ b/src/Chart.ts @@ -30,17 +30,17 @@ import VisibleRange from './common/VisibleRange' import { createId } from './common/utils/id' import { createDom } from './common/utils/dom' import { getPixelRatio } from './common/utils/canvas' -import { isString, isArray, isValid } from './common/utils/typeChecks' +import { isString, isArray, isValid, merge } from './common/utils/typeChecks' import { logWarn } from './common/utils/logger' import { formatValue } from './common/utils/format' import { binarySearchNearest } from './common/utils/number' import ChartStore from './store/ChartStore' -import Pane, { PaneOptions, PANE_DEFAULT_HEIGHT, PaneIdConstants } from './pane/Pane' import CandlePane from './pane/CandlePane' import IndicatorPane from './pane/IndicatorPane' import XAxisPane from './pane/XAxisPane' +import { PaneOptions, PanePosition, PANE_DEFAULT_HEIGHT, PaneIdConstants, PaneNameConstants } from './pane/types' import Axis from './component/Axis' @@ -52,7 +52,9 @@ import { getStyles as getExtensionStyles } from './extension/styles/index' import Event from './Event' -import { CustomApi, Options } from './Options' +import { CustomApi, LayoutChildType, Options } from './Options' +import DrawPane from './pane/DrawPane' +import SeparatorPane from './pane/SeparatorPane' export enum DomPosition { Root = 'root', @@ -126,15 +128,15 @@ export default class ChartImp implements Chart { private _chartContainer: HTMLElement private readonly _chartEvent: Event private readonly _chartStore: ChartStore - private readonly _xAxisPane: XAxisPane - private readonly _panes: Map = new Map() + private readonly _drawPanes: Map = new Map() + private readonly _elementPanes: Map = new Map() + private readonly _separatorPanes: Map = new Map() constructor (container: HTMLElement, options?: Options) { this._initContainer(container) this._chartEvent = new Event(this._chartContainer, this) this._chartStore = new ChartStore(this, options) - this._xAxisPane = new XAxisPane(this._chartContainer, this, PaneIdConstants.XAXIS) - this._panes.set(PaneIdConstants.CANDLE, new CandlePane(this._chartContainer, this, PaneIdConstants.CANDLE)) + this._initPanes(options) this.adjustPaneViewport(true, true, true) } @@ -158,16 +160,127 @@ export default class ChartImp implements Chart { container.appendChild(this._chartContainer) } + private _initPanes (options?: Options): void { + const layout = options?.layout ?? [{ type: LayoutChildType.Candle }] + let candlePaneInitialized = false + let xAxisPaneInitialized = false + + const createXAxisPane: ((ops?: PaneOptions) => void) = (ops?: PaneOptions) => { + if (!xAxisPaneInitialized) { + const pane = new XAxisPane( + this._chartContainer, + this._getInsertElement(ops?.position), + this, + PaneIdConstants.X_AXIS + ) + pane.setOptions(ops ?? {}) + this._drawPanes.set(PaneIdConstants.X_AXIS, pane) + this._elementPanes.set(pane.getContainer(), pane) + xAxisPaneInitialized = true + } + } + + layout.forEach(child => { + switch (child.type) { + case LayoutChildType.Candle: { + if (!candlePaneInitialized) { + const paneOptions = child.options ?? {} + merge(paneOptions, { id: PaneIdConstants.CANDLE }) + const pane = new CandlePane( + this._chartContainer, + this._getInsertElement(paneOptions.position), + this, + PaneIdConstants.CANDLE + ) + this._createSeparatorPane(pane) + this._drawPanes.set(PaneIdConstants.CANDLE, pane) + this._elementPanes.set(pane.getContainer(), pane) + const content = child.content ?? [] + content.forEach(v => { + this.createIndicator(v, true, paneOptions) + }) + candlePaneInitialized = true + } + break + } + case LayoutChildType.Indicator: { + const content = child.content ?? [] + if (content.length > 0) { + let paneId: Nullable + content.forEach(v => { + if (isValid(paneId)) { + this.createIndicator(v, true, { id: paneId }) + } else { + paneId = this.createIndicator(v, true, child.options) + } + }) + } + break + } + case LayoutChildType.XAxis: { + createXAxisPane(child.options) + } + } + }) + createXAxisPane({ position: PanePosition.Bottom }) + } + + private _createSeparatorPane (bottomPane: DrawPane): void { + const panes = Array.from(this._drawPanes.values()) + let topPane = panes[panes.length - 1] + if (isValid(topPane)) { + if (topPane.getName() === PaneNameConstants.X_AXIS) { + topPane = panes[panes.length - 2] + } + } + if (isValid(topPane)) { + const pane = new SeparatorPane(this._chartContainer, bottomPane.getContainer(), this, '', topPane, bottomPane) + this._separatorPanes.set(bottomPane, pane) + } + } + + private _getInsertElement (position?: PanePosition): Nullable { + switch (position) { + case PanePosition.Top: { + const panes = Array.from(this._drawPanes.values()) + const firstPane = panes[0] + if (isValid(firstPane)) { + return firstPane.getContainer() + } + return null + } + case PanePosition.Bottom: { return null } + default: { + const children = this._chartContainer.children + for (let i = children.length - 1; i > -1; i--) { + const child = children[i] + const prevChild = children[i - 1] + const pane = this._elementPanes.get(child) + const prevPane = this._elementPanes.get(prevChild) + if ( + pane?.getOptions().position === PanePosition.Bottom && + prevPane?.getOptions().position !== PanePosition.Bottom + ) { + return pane.getContainer() + } + } + return null + } + } + } + private _measurePaneHeight (): void { const totalHeight = this._container.offsetHeight - const xAxisHeight = this._xAxisPane.getAxisComponent().getAutoSize() - let paneExcludeXAxisHeight = totalHeight - xAxisHeight + const separatorSize = this._chartStore.getStyles().separator.size + const xAxisHeight = this._drawPanes.get(PaneIdConstants.X_AXIS)?.getAxisComponent().getAutoSize() ?? 0 + let paneExcludeXAxisHeight = totalHeight - xAxisHeight - this._separatorPanes.size * separatorSize if (paneExcludeXAxisHeight < 0) { paneExcludeXAxisHeight = 0 } let indicatorPaneTotalHeight = 0 - this._panes.forEach(pane => { - if (pane.getId() !== PaneIdConstants.CANDLE) { + + this._drawPanes.forEach(pane => { + if (pane.getId() !== PaneIdConstants.CANDLE && pane.getId() !== PaneIdConstants.X_AXIS) { let paneHeight = pane.getBounding().height const paneMinHeight = pane.getOptions().minHeight if (paneHeight < paneMinHeight) { @@ -184,14 +297,24 @@ export default class ChartImp implements Chart { }) const candlePaneHeight = paneExcludeXAxisHeight - indicatorPaneTotalHeight - this._panes.get(PaneIdConstants.CANDLE)?.setBounding({ height: candlePaneHeight }) + this._drawPanes.get(PaneIdConstants.CANDLE)?.setBounding({ height: candlePaneHeight }) + this._drawPanes.get(PaneIdConstants.X_AXIS)?.setBounding({ height: xAxisHeight }) let top = 0 - this._panes.forEach(pane => { - pane.setBounding({ top }) - top += pane.getBounding().height - }) - this._xAxisPane.setBounding({ height: xAxisHeight, top }) + const children = this._chartContainer.children + + for (let i = 0; i < children.length; i++) { + const pane = this._elementPanes.get(children[i]) + if (isValid(pane)) { + const separatorPane = this._separatorPanes.get(pane) + if (isValid(separatorPane)) { + separatorPane.setBounding({ height: separatorSize, top }) + top += separatorSize + } + pane.setBounding({ top }) + top += pane.getBounding().height + } + } } private _measurePaneWidth (): void { @@ -204,8 +327,10 @@ export default class ChartImp implements Chart { let yAxisWidth = Number.MIN_SAFE_INTEGER let yAxisLeft = 0 let mainLeft = 0 - this._panes.forEach(pane => { - yAxisWidth = Math.max(yAxisWidth, pane.getAxisComponent().getAutoSize()) + this._drawPanes.forEach(pane => { + if (pane.getId() !== PaneIdConstants.X_AXIS) { + yAxisWidth = Math.max(yAxisWidth, pane.getAxisComponent().getAutoSize()) + } }) if (yAxisWidth > totalWidth) { yAxisWidth = totalWidth @@ -234,14 +359,14 @@ export default class ChartImp implements Chart { const paneBounding = { width: totalWidth } const mainBounding = { width: mainWidth, left: mainLeft } const yAxisBounding = { width: yAxisWidth, left: yAxisLeft } - this._panes.forEach((pane) => { + this._drawPanes.forEach((pane) => { + this._separatorPanes.get(pane)?.setBounding(paneBounding) pane.setBounding(paneBounding, mainBounding, yAxisBounding) }) - this._xAxisPane.setBounding(paneBounding, mainBounding, yAxisBounding) } private _setPaneOptions (options: PaneOptions, forceShouldAdjust: boolean): void { - const pane = this._panes.get(options?.id ?? '') + const pane = this._drawPanes.get(options?.id ?? '') let shouldMeasureHeight = false if (pane !== undefined) { let shouldAdjust = forceShouldAdjust @@ -263,7 +388,9 @@ export default class ChartImp implements Chart { getChartStore (): ChartStore { return this._chartStore } - getAllPanes (): Map { return this._panes } + getAllDrawPanes (): Map { return this._drawPanes } + + getAllSeparatorPanes (): Map { return this._separatorPanes } adjustPaneViewport ( shouldMeasureHeight: boolean, @@ -279,7 +406,7 @@ export default class ChartImp implements Chart { const adjustYAxis = shouldAdjustYAxis ?? false const forceAdjustYAxis = shouldForceAdjustYAxis ?? false if (adjustYAxis || forceAdjustYAxis) { - this._panes.forEach(pane => { + this._drawPanes.forEach(pane => { const adjust = pane.getAxisComponent().buildTicks(forceAdjustYAxis) if (!forceMeasureWidth) { forceMeasureWidth = adjust @@ -290,34 +417,33 @@ export default class ChartImp implements Chart { this._measurePaneWidth() } if (shouldUpdate ?? false) { - this._xAxisPane.getAxisComponent().buildTicks(true) + this._drawPanes.get(PaneIdConstants.X_AXIS)?.getAxisComponent().buildTicks(true) this.updatePane(UpdateLevel.All) } } updatePane (level: UpdateLevel, paneId?: string): void { if (paneId !== undefined) { - this.getPaneById(paneId)?.update(level) + this.getDrawPaneById(paneId)?.update(level) } else { - this._xAxisPane.update(level) - this._panes.forEach(pane => { + this._separatorPanes.forEach(pane => { + pane.update(level) + }) + this._drawPanes.forEach(pane => { pane.update(level) }) } } - getPaneById (paneId: string): Nullable> { - if (paneId === PaneIdConstants.XAXIS) { - return this._xAxisPane - } - return this._panes.get(paneId) ?? null + getDrawPaneById (paneId: string): Nullable> { + return this._drawPanes.get(paneId) ?? null } crosshairChange (crosshair: Crosshair): void { const actionStore = this._chartStore.getActionStore() if (actionStore.has(ActionType.OnCrosshairChange)) { const indicatorData = {} - this._panes.forEach((_, id) => { + this._drawPanes.forEach((_, id) => { const paneIndicatorData = {} const indicators = this._chartStore.getIndicatorStore().getInstances(id) indicators.forEach(indicator => { @@ -337,7 +463,7 @@ export default class ChartImp implements Chart { getDom (paneId?: string, position?: DomPosition): Nullable { if (paneId !== undefined) { - const pane = this.getPaneById(paneId) + const pane = this.getDrawPaneById(paneId) if (pane !== null) { const pos = position ?? DomPosition.Root switch (pos) { @@ -360,7 +486,7 @@ export default class ChartImp implements Chart { getSize (paneId?: string, position?: DomPosition): Nullable { if (paneId !== undefined) { - const pane = this.getPaneById(paneId) + const pane = this.getDrawPaneById(paneId) if (pane !== null) { const pos = position ?? DomPosition.Root switch (pos) { @@ -397,7 +523,7 @@ export default class ChartImp implements Chart { realStyles = styles } if (realStyles?.yAxis?.type !== undefined) { - this.getPaneById(PaneIdConstants.CANDLE)?.getAxisComponent().setAutoCalcTickFlag(true) + this.getDrawPaneById(PaneIdConstants.CANDLE)?.getAxisComponent().setAutoCalcTickFlag(true) } this.adjustPaneViewport(true, true, true, true, true) } @@ -430,8 +556,9 @@ export default class ChartImp implements Chart { setTimezone (timezone: string): void { this._chartStore.setOptions({ timezone }) - this._xAxisPane.getAxisComponent().buildTicks(true) - this._xAxisPane.update(UpdateLevel.Drawer) + const xAxisPane = this._drawPanes.get(PaneIdConstants.X_AXIS) + xAxisPane?.getAxisComponent().buildTicks(true) + xAxisPane?.update(UpdateLevel.Drawer) } getTimezone (): string { @@ -536,22 +663,27 @@ export default class ChartImp implements Chart { } let paneId: string - if (isValid(paneOptions) && isString(paneOptions?.id) && this._panes.has(paneOptions.id)) { + if (isValid(paneOptions) && isString(paneOptions?.id) && this._drawPanes.has(paneOptions.id)) { paneId = paneOptions.id this._chartStore.getIndicatorStore().addInstance(indicator, paneId, isStack ?? false).then(_ => { - this._setPaneOptions(paneOptions, this._panes.get(paneId)?.getAxisComponent().buildTicks(true) ?? false) + this._setPaneOptions(paneOptions, this._drawPanes.get(paneId)?.getAxisComponent().buildTicks(true) ?? false) }).catch(_ => {}) } else { paneId = paneOptions?.id ?? createId(PaneIdConstants.INDICATOR) - const topPane = Array.from(this._panes.values()).pop() as unknown as Pane - const pane = new IndicatorPane(this._chartContainer, this, paneId, topPane) - topPane.setBottomPane(pane) + const pane = new IndicatorPane( + this._chartContainer, + this._getInsertElement(paneOptions?.position), + this, + paneId + ) + this._createSeparatorPane(pane) const height = paneOptions?.height ?? PANE_DEFAULT_HEIGHT pane.setBounding({ height }) if (isValid(paneOptions)) { pane.setOptions(paneOptions) } - this._panes.set(paneId, pane) + this._drawPanes.set(paneId, pane) + this._elementPanes.set(pane.getContainer(), pane) this._chartStore.getIndicatorStore().addInstance(indicator, paneId, isStack ?? false).finally(() => { this.adjustPaneViewport(true, true, true, true, true) callback?.() @@ -582,15 +714,24 @@ export default class ChartImp implements Chart { let shouldMeasureHeight = false if (paneId !== PaneIdConstants.CANDLE) { if (!indicatorStore.hasInstances(paneId)) { - const deletePane = this._panes.get(paneId) - if (deletePane !== undefined) { + const pane = this._drawPanes.get(paneId) + if (isValid(pane)) { shouldMeasureHeight = true - const deleteTopPane = deletePane.getTopPane() - const deleteBottomPane = deletePane.getBottomPane() - deleteBottomPane?.setTopPane(deleteTopPane) - deleteTopPane?.setBottomPane(deleteBottomPane) - deletePane?.destroy() - this._panes.delete(paneId) + const separatorPane = this._separatorPanes.get(pane) + if (isValid(separatorPane)) { + const topPane = separatorPane?.getTopPane() + for (const item of this._separatorPanes) { + if (item[1].getTopPane().getId() === pane.getId()) { + item[1].setTopPane(topPane) + break + } + } + separatorPane.destroy() + this._separatorPanes.delete(pane) + } + this._drawPanes.delete(paneId) + this._elementPanes.delete(pane.getContainer()) + pane.destroy() } } } @@ -614,7 +755,7 @@ export default class ChartImp implements Chart { overlays = [overlay] } let appointPaneFlag = true - if (paneId === undefined || this.getPaneById(paneId) === null) { + if (paneId === undefined || this.getDrawPaneById(paneId) === null) { paneId = PaneIdConstants.CANDLE appointPaneFlag = false } @@ -744,13 +885,13 @@ export default class ChartImp implements Chart { convertToPixel (points: Partial | Array>, finder: ConvertFinder): Partial | Array> { const { paneId = PaneIdConstants.CANDLE, absolute = false } = finder let coordinates: Array> = [] - if (paneId !== PaneIdConstants.XAXIS) { - const pane = this.getPaneById(paneId) + if (paneId !== PaneIdConstants.X_AXIS) { + const pane = this.getDrawPaneById(paneId) if (pane !== null) { const timeScaleStore = this._chartStore.getTimeScaleStore() const bounding = pane.getBounding() const ps = new Array>().concat(points) - const xAxis = this._xAxisPane.getAxisComponent() + const xAxis = this._drawPanes.get(PaneIdConstants.X_AXIS)?.getAxisComponent() const yAxis = pane.getAxisComponent() coordinates = ps.map(point => { const coordinate: Partial = {} @@ -775,18 +916,18 @@ export default class ChartImp implements Chart { convertFromPixel (coordinates: Array>, finder: ConvertFinder): Partial | Array> { const { paneId = PaneIdConstants.CANDLE, absolute = false } = finder let points: Array> = [] - if (paneId !== PaneIdConstants.XAXIS) { - const pane = this.getPaneById(paneId) + if (paneId !== PaneIdConstants.X_AXIS) { + const pane = this.getDrawPaneById(paneId) if (pane !== null) { const timeScaleStore = this._chartStore.getTimeScaleStore() const bounding = pane.getBounding() const cs = new Array>().concat(coordinates) - const xAxis = this._xAxisPane.getAxisComponent() + const xAxis = this._drawPanes.get(PaneIdConstants.X_AXIS)?.getAxisComponent() const yAxis = pane.getAxisComponent() points = cs.map(coordinate => { const point: Partial = {} if (coordinate.x !== undefined) { - const dataIndex = xAxis.convertFromPixel(coordinate.x) + const dataIndex = xAxis?.convertFromPixel(coordinate.x) ?? -1 point.dataIndex = dataIndex point.timestamp = timeScaleStore.dataIndexToTimestamp(dataIndex) ?? undefined } @@ -837,18 +978,13 @@ export default class ChartImp implements Chart { ctx.fillStyle = backgroundColor ?? '#FFFFFF' ctx.fillRect(0, 0, width, height) const overlayFlag = includeOverlay ?? false - this._panes.forEach(pane => { + this._drawPanes.forEach(pane => { const bounding = pane.getBounding() ctx.drawImage( pane.getImage(overlayFlag), 0, bounding.top, width, bounding.height ) }) - const xAxisBounding = this._xAxisPane.getBounding() - ctx.drawImage( - this._xAxisPane.getImage(overlayFlag), - 0, xAxisBounding.top, width, xAxisBounding.height - ) return canvas.toDataURL(`image/${type ?? 'jpeg'}`) } @@ -858,11 +994,11 @@ export default class ChartImp implements Chart { destroy (): void { this._chartEvent.destroy() - this._panes.forEach(pane => { + this._drawPanes.forEach(pane => { pane.destroy() }) - this._panes.clear() - this._xAxisPane.destroy() + this._drawPanes.clear() + this._elementPanes.clear() this._container.removeChild(this._chartContainer) } } diff --git a/src/Event.ts b/src/Event.ts index 7d0c9a287..2bc3588e1 100644 --- a/src/Event.ts +++ b/src/Event.ts @@ -22,11 +22,14 @@ import { requestAnimationFrame, cancelAnimationFrame } from './common/utils/comp import { AxisExtremum } from './component/Axis' import YAxis from './component/YAxis' +import XAxis from './component/XAxis' import Chart from './Chart' -import Pane, { PaneIdConstants } from './pane/Pane' -import Widget, { WidgetNameConstants } from './widget/Widget' -import { REAL_SEPARATOR_HEIGHT } from './widget/SeparatorWidget' +import Pane from './pane/Pane' +import { PaneIdConstants } from './pane/types' +import Widget from './widget/Widget' +import { WidgetNameConstants, REAL_SEPARATOR_HEIGHT } from './widget/types' +import DrawPane from './pane/DrawPane' interface EventTriggerWidgetInfo { pane: Nullable @@ -113,7 +116,7 @@ export default class Event implements EventHandler { pinchEvent (e: MouseTouchEvent, scale: number): boolean { const { pane, widget } = this._findWidgetByEvent(e) - if (pane?.getId() !== PaneIdConstants.XAXIS && widget?.getName() === WidgetNameConstants.MAIN) { + if (pane?.getId() !== PaneIdConstants.X_AXIS && widget?.getName() === WidgetNameConstants.MAIN) { const event = this._makeWidgetEvent(e, widget) const zoomScale = (scale - this._pinchScale) * 5 this._pinchScale = scale @@ -137,10 +140,10 @@ export default class Event implements EventHandler { let zoomCoordinate: Nullable = null const name = widget?.getName() if (isTouch) { - if (name === WidgetNameConstants.MAIN || name === WidgetNameConstants.XAXIS) { + if (name === WidgetNameConstants.MAIN || name === WidgetNameConstants.X_AXIS) { zoomCoordinate = { x: event.x, y: event.y } } else { - const bounding = this._chart.getPaneById(PaneIdConstants.CANDLE)?.getBounding() as Bounding + const bounding = this._chart.getDrawPaneById(PaneIdConstants.CANDLE)?.getBounding() as Bounding zoomCoordinate = { x: bounding.width / 2, y: bounding.height / 2 } } } else { @@ -166,13 +169,13 @@ export default class Event implements EventHandler { return widget.dispatchEvent('mouseDownEvent', event) } case WidgetNameConstants.MAIN: { - const extremum = pane?.getAxisComponent().getExtremum() ?? null + const extremum = (pane as DrawPane).getAxisComponent().getExtremum() ?? null this._prevYAxisExtremum = extremum === null ? extremum : { ...extremum } this._startScrollCoordinate = { x: event.x, y: event.y } this._chart.getChartStore().getTimeScaleStore().startScroll() return widget.dispatchEvent('mouseDownEvent', event) } - case WidgetNameConstants.XAXIS: { + case WidgetNameConstants.X_AXIS: { const consumed = widget.dispatchEvent('mouseDownEvent', event) if (consumed) { this._chart.updatePane(UpdateLevel.Overlay) @@ -181,12 +184,12 @@ export default class Event implements EventHandler { this._xAxisStartScaleDistance = event.pageX return consumed } - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.Y_AXIS: { const consumed = widget.dispatchEvent('mouseDownEvent', event) if (consumed) { this._chart.updatePane(UpdateLevel.Overlay) } - const extremum = pane?.getAxisComponent().getExtremum() ?? null + const extremum = (pane as DrawPane).getAxisComponent().getExtremum() ?? null this._prevYAxisExtremum = extremum === null ? extremum : { ...extremum } this._yAxisStartScaleDistance = event.pageY return consumed @@ -224,8 +227,8 @@ export default class Event implements EventHandler { return consumed } case WidgetNameConstants.SEPARATOR: - case WidgetNameConstants.XAXIS: - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.X_AXIS: + case WidgetNameConstants.Y_AXIS: { const consumed = widget.dispatchEvent('mouseMoveEvent', event) this._chart.getChartStore().getTooltipStore().setCrosshair() return consumed @@ -252,7 +255,7 @@ export default class Event implements EventHandler { const bounding = widget.getBounding() const consumed = widget.dispatchEvent('pressedMouseMoveEvent', event) if (!consumed && this._startScrollCoordinate !== null) { - const yAxis = pane?.getAxisComponent() as YAxis + const yAxis = (pane as DrawPane).getAxisComponent() if (this._prevYAxisExtremum !== null && !yAxis.getAutoCalcTickFlag() && yAxis.getScrollZoomEnabled()) { const { min, max, range } = this._prevYAxisExtremum let distance: number @@ -282,10 +285,10 @@ export default class Event implements EventHandler { this._chart.getChartStore().getTooltipStore().setCrosshair({ x: event.x, y: event.y, paneId: pane?.getId() }) return consumed } - case WidgetNameConstants.XAXIS: { + case WidgetNameConstants.X_AXIS: { const consumed = widget.dispatchEvent('pressedMouseMoveEvent', event) if (!consumed) { - const xAxis = pane?.getAxisComponent() + const xAxis = (pane as DrawPane).getAxisComponent() if (xAxis?.getScrollZoomEnabled() ?? true) { const scale = this._xAxisStartScaleDistance / event.pageX const zoomScale = (scale - this._xAxisScale) * 10 @@ -297,10 +300,10 @@ export default class Event implements EventHandler { } return consumed } - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.Y_AXIS: { const consumed = widget.dispatchEvent('pressedMouseMoveEvent', event) if (!consumed) { - const yAxis = pane?.getAxisComponent() as YAxis + const yAxis = (pane as DrawPane).getAxisComponent() if (this._prevYAxisExtremum !== null && yAxis.getScrollZoomEnabled()) { const { min, max, range } = this._prevYAxisExtremum const scale = event.pageY / this._yAxisStartScaleDistance @@ -308,7 +311,6 @@ export default class Event implements EventHandler { const difRange = (newRange - range) / 2 const newMin = min - difRange const newMax = max + difRange - const yAxis = pane?.getAxisComponent() as YAxis const newRealMin = yAxis.convertToRealValue(newMin) const newRealMax = yAxis.convertToRealValue(newMax) yAxis.setExtremum({ @@ -340,8 +342,8 @@ export default class Event implements EventHandler { switch (name) { case WidgetNameConstants.MAIN: case WidgetNameConstants.SEPARATOR: - case WidgetNameConstants.XAXIS: - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.X_AXIS: + case WidgetNameConstants.Y_AXIS: { consumed = widget.dispatchEvent('mouseUpEvent', event) break } @@ -377,8 +379,8 @@ export default class Event implements EventHandler { const name = widget.getName() switch (name) { case WidgetNameConstants.MAIN: - case WidgetNameConstants.XAXIS: - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.X_AXIS: + case WidgetNameConstants.Y_AXIS: { consumed = widget.dispatchEvent('mouseRightClickEvent', event) break } @@ -399,8 +401,8 @@ export default class Event implements EventHandler { const event = this._makeWidgetEvent(e, widget) return widget.dispatchEvent('mouseDoubleClickEvent', event) } - case WidgetNameConstants.YAXIS: { - const yAxis = pane?.getAxisComponent() as YAxis + case WidgetNameConstants.Y_AXIS: { + const yAxis = (pane as DrawPane).getAxisComponent() if (!yAxis.getAutoCalcTickFlag()) { yAxis.setAutoCalcTickFlag(true) this._chart.adjustPaneViewport(false, true, true, true) @@ -457,8 +459,8 @@ export default class Event implements EventHandler { } return true } - case WidgetNameConstants.XAXIS: - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.X_AXIS: + case WidgetNameConstants.Y_AXIS: { const consumed = widget.dispatchEvent('mouseDownEvent', event) if (consumed) { this._chart.updatePane(UpdateLevel.Overlay) @@ -499,8 +501,8 @@ export default class Event implements EventHandler { } return true } - case WidgetNameConstants.XAXIS: - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.X_AXIS: + case WidgetNameConstants.Y_AXIS: { const consumed = widget.dispatchEvent('pressedMouseMoveEvent', event) if (consumed) { event.preventDefault?.() @@ -547,8 +549,8 @@ export default class Event implements EventHandler { } return true } - case WidgetNameConstants.XAXIS: - case WidgetNameConstants.YAXIS: { + case WidgetNameConstants.X_AXIS: + case WidgetNameConstants.Y_AXIS: { const consumed = widget.dispatchEvent('mouseUpEvent', event) if (consumed) { this._chart.updatePane(UpdateLevel.Overlay) @@ -606,10 +608,24 @@ export default class Event implements EventHandler { } private _findWidgetByEvent (event: MouseTouchEvent): EventTriggerWidgetInfo { - const panes = this._chart.getAllPanes() const { x, y } = event - let pane: Nullable = null - for (const [, p] of panes) { + const separatorPanes = this._chart.getAllSeparatorPanes() + const separatorSize = this._chart.getChartStore().getStyles().separator.size + for (const [, pane] of separatorPanes) { + const bounding = pane.getBounding() + const top = bounding.top - Math.round((REAL_SEPARATOR_HEIGHT - separatorSize) / 2) + if ( + x >= bounding.left && x <= bounding.left + bounding.width && + y >= top && y <= top + REAL_SEPARATOR_HEIGHT + ) { + return { pane, widget: pane.getWidget() } + } + } + + const drawPanes = this._chart.getAllDrawPanes() + + let pane: Nullable = null + for (const [, p] of drawPanes) { const bounding = p.getBounding() if ( x >= bounding.left && x <= bounding.left + bounding.width && @@ -619,21 +635,8 @@ export default class Event implements EventHandler { break } } - if (pane === null) { - pane = this._chart.getPaneById(PaneIdConstants.XAXIS) - } let widget: Nullable = null if (pane !== null) { - const separatorWidget = pane.getSeparatorWidget() - if (separatorWidget !== null) { - const separatorBounding = separatorWidget.getBounding() - if ( - x >= separatorBounding.left && x <= separatorBounding.left + separatorBounding.width && - y >= separatorBounding.top && y <= (separatorBounding.top + REAL_SEPARATOR_HEIGHT) - ) { - widget = separatorWidget - } - } if (widget === null) { const mainWidget = pane.getMainWidget() const mainBounding = mainWidget.getBounding() diff --git a/src/Options.ts b/src/Options.ts index 27b29eaa3..81d043cd8 100644 --- a/src/Options.ts +++ b/src/Options.ts @@ -13,10 +13,12 @@ */ import DeepPartial from './common/DeepPartial' - import { Styles } from './common/Styles' import { formatDate, formatBigNumber } from './common/utils/format' +import { IndicatorCreate } from './component/Indicator' +import { PaneOptions } from './pane/types' + export enum FormatDateType { Tooltip, Crosshair, @@ -53,7 +55,20 @@ export interface Locales { [key: string]: string } +export const enum LayoutChildType { + Candle = 'candle', + Indicator = 'indicator', + XAxis = 'xAxis' +} + +export interface LayoutChild { + type: LayoutChildType + content?: Array + options?: PaneOptions +} + export interface Options { + layout?: LayoutChild[] locale?: string timezone?: string styles?: string | DeepPartial diff --git a/src/component/Axis.ts b/src/component/Axis.ts index e9efa6f6d..1d0812dd0 100644 --- a/src/component/Axis.ts +++ b/src/component/Axis.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import Pane from '../pane/Pane' +import DrawPane from '../pane/DrawPane' import { getPrecision, nice, round } from '../common/utils/number' @@ -37,7 +37,7 @@ export interface Axis { } export default abstract class AxisImp { - private readonly _parent: Pane + private readonly _parent: DrawPane private _extremum: AxisExtremum = { min: 0, max: 0, range: 0, realMin: 0, realMax: 0, realRange: 0 } private _prevExtremum: AxisExtremum = { min: 0, max: 0, range: 0, realMin: 0, realMax: 0, realRange: 0 } @@ -45,11 +45,11 @@ export default abstract class AxisImp { private _autoCalcTickFlag = true - constructor (parent: Pane) { + constructor (parent: DrawPane) { this._parent = parent } - getParent (): Pane { return this._parent } + getParent (): DrawPane { return this._parent } buildTicks (force: boolean): boolean { if (this._autoCalcTickFlag) { @@ -117,6 +117,8 @@ export default abstract class AxisImp { protected abstract optimalTicks (ticks: AxisTick[]): AxisTick[] + abstract getAutoSize (): number + abstract convertToPixel (value: number): number abstract convertFromPixel (px: number): number } diff --git a/src/component/XAxis.ts b/src/component/XAxis.ts index 6d1e6dfe1..4bd3f2495 100644 --- a/src/component/XAxis.ts +++ b/src/component/XAxis.ts @@ -106,7 +106,7 @@ export default class XAxisImp extends AxisImp { return null } - getAutoSize (): number { + override getAutoSize (): number { const styles = this.getParent().getChart().getStyles() const xAxisStyles = styles.xAxis const height = xAxisStyles.size diff --git a/src/component/YAxis.ts b/src/component/YAxis.ts index db9bdcb43..815c081f3 100644 --- a/src/component/YAxis.ts +++ b/src/component/YAxis.ts @@ -287,7 +287,7 @@ export default class YAxisImp extends AxisImp implements YAxis { return optimalTicks } - getAutoSize (): number { + override getAutoSize (): number { const pane = this.getParent() const chart = pane.getChart() const styles = chart.getStyles() diff --git a/src/pane/CandlePane.ts b/src/pane/CandlePane.ts index 21b448b65..6532d7f15 100644 --- a/src/pane/CandlePane.ts +++ b/src/pane/CandlePane.ts @@ -12,25 +12,21 @@ * limitations under the License. */ -import Nullable from '../common/Nullable' - import DrawWidget from '../widget/DrawWidget' import CandleWidget from '../widget/CandleWidget' -import SeparatorWidget from '../widget/SeparatorWidget' +import DrawPane from './DrawPane' import IndicatorPane from './IndicatorPane' import YAxis from '../component/YAxis' +import { PaneNameConstants } from './types' + export default class CandlePane extends IndicatorPane { override getName (): string { - return 'candle' + return PaneNameConstants.CANDLE } - override createMainWidget (container: HTMLElement): DrawWidget { + override createMainWidget (container: HTMLElement): DrawWidget> { return new CandleWidget(container, this) } - - override createSeparatorWidget (): Nullable { - return null - } } diff --git a/src/pane/DrawPane.ts b/src/pane/DrawPane.ts new file mode 100644 index 000000000..acdccb058 --- /dev/null +++ b/src/pane/DrawPane.ts @@ -0,0 +1,140 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import DeepRequired from '../common/DeepRequired' +import Nullable from '../common/Nullable' +import { UpdateLevel } from '../common/Updater' +import Bounding from '../common/Bounding' + +import { isValid, merge } from '../common/utils/typeChecks' + +import Axis from '../component/Axis' + +import DrawWidget from '../widget/DrawWidget' +import YAxisWidget from '../widget/YAxisWidget' + +import Pane from './Pane' +import { PaneOptions, PANE_MIN_HEIGHT, PaneIdConstants } from './types' + +import Chart from '../Chart' + +import { createDom } from '../common/utils/dom' +import { getPixelRatio } from '../common/utils/canvas' +import PickPartial from '../common/PickPartial' + +export default abstract class DrawPane extends Pane { + private readonly _mainWidget: DrawWidget> + private readonly _yAxisWidget: Nullable = null + + private readonly _axis: C = this.createAxisComponent() + + private readonly _options: PickPartial>, 'position'> = { minHeight: PANE_MIN_HEIGHT, dragEnabled: true, gap: { top: 0.2, bottom: 0.1 }, axisOptions: { scrollZoomEnabled: true } } + + constructor (rootContainer: HTMLElement, afterElement: Nullable, chart: Chart, id: string) { + super(rootContainer, afterElement, chart, id) + const container = this.getContainer() + this._mainWidget = this.createMainWidget(container) + this._yAxisWidget = this.createYAxisWidget(container) + } + + setOptions (options: Omit): DrawPane { + merge(this._options, options) + let container: HTMLElement + let cursor: string + if (this.getId() === PaneIdConstants.X_AXIS) { + container = this.getMainWidget().getContainer() + cursor = 'ew-resize' + } else { + container = this.getYAxisWidget()?.getContainer() as HTMLElement + cursor = 'ns-resize' + } + if (options.axisOptions?.scrollZoomEnabled ?? true) { + container.style.cursor = cursor + } else { + container.style.cursor = 'default' + } + return this + } + + getOptions (): PickPartial>, 'position'> { return this._options } + + getAxisComponent (): C { + return this._axis + } + + override setBounding (rootBounding: Partial, mainBounding?: Partial, yAxisBounding?: Partial): DrawPane { + merge(this.getBounding(), rootBounding) + const contentBounding: Partial = {} + if (isValid(rootBounding.height)) { + contentBounding.height = rootBounding.height + } + if (isValid(rootBounding.top)) { + contentBounding.top = rootBounding.top + } + this._mainWidget.setBounding(contentBounding) + this._yAxisWidget?.setBounding(contentBounding) + if (isValid(mainBounding)) { + this._mainWidget.setBounding(mainBounding) + } + if (isValid(yAxisBounding)) { + this._yAxisWidget?.setBounding(yAxisBounding) + } + return this + } + + getMainWidget (): DrawWidget> { return this._mainWidget } + + getYAxisWidget (): Nullable { return this._yAxisWidget } + + override updateImp (level: UpdateLevel): void { + this._mainWidget.update(level) + this._yAxisWidget?.update(level) + } + + override getImage (includeOverlay: boolean): HTMLCanvasElement { + const { width, height } = this.getBounding() + const canvas = createDom('canvas', { + width: `${width}px`, + height: `${height}px`, + boxSizing: 'border-box' + }) + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D + const pixelRatio = getPixelRatio(canvas) + canvas.width = width * pixelRatio + canvas.height = height * pixelRatio + ctx.scale(pixelRatio, pixelRatio) + + const mainBounding = this._mainWidget.getBounding() + ctx.drawImage( + this._mainWidget.getImage(includeOverlay), + mainBounding.left, 0, + mainBounding.width, mainBounding.height + ) + if (this._yAxisWidget !== null) { + const yAxisBounding = this._yAxisWidget.getBounding() + ctx.drawImage( + this._yAxisWidget.getImage(includeOverlay), + yAxisBounding.left, 0, + yAxisBounding.width, yAxisBounding.height + ) + } + return canvas + } + + protected abstract createAxisComponent (): C + + protected createYAxisWidget (_container: HTMLElement): Nullable { return null } + + protected abstract createMainWidget (container: HTMLElement): DrawWidget> +} diff --git a/src/pane/IndicatorPane.ts b/src/pane/IndicatorPane.ts index fcef974a9..cc0eb2b01 100644 --- a/src/pane/IndicatorPane.ts +++ b/src/pane/IndicatorPane.ts @@ -14,32 +14,29 @@ import Nullable from '../common/Nullable' -import SeparatorWidget from '../widget/SeparatorWidget' import DrawWidget from '../widget/DrawWidget' import IndicatorWidget from '../widget/IndicatorWidget' import YAxisWidget from '../widget/YAxisWidget' import YAxis from '../component/YAxis' -import Pane from './Pane' +import DrawPane from './DrawPane' -export default class IndicatorPane extends Pane { +import { PaneNameConstants } from './types' + +export default class IndicatorPane extends DrawPane { override getName (): string { - return 'indicator' + return PaneNameConstants.INDICATOR } override createAxisComponent (): YAxis { return new YAxis(this) } - override createMainWidget (container: HTMLElement): DrawWidget { + override createMainWidget (container: HTMLElement): DrawWidget> { return new IndicatorWidget(container, this) } - override createSeparatorWidget (container: HTMLElement): Nullable { - return new SeparatorWidget(container, this) - } - override createYAxisWidget (container: HTMLElement): Nullable { return new YAxisWidget(container, this) } diff --git a/src/pane/Pane.ts b/src/pane/Pane.ts index faed4557c..63b31326a 100644 --- a/src/pane/Pane.ts +++ b/src/pane/Pane.ts @@ -11,81 +11,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import DeepRequired from '../common/DeepRequired' import Nullable from '../common/Nullable' import Updater, { UpdateLevel } from '../common/Updater' import Bounding, { getDefaultBounding } from '../common/Bounding' -import { merge } from '../common/utils/typeChecks' - -import Axis from '../component/Axis' - -import DrawWidget from '../widget/DrawWidget' -import SeparatorWidget, { REAL_SEPARATOR_HEIGHT } from '../widget/SeparatorWidget' -import YAxisWidget from '../widget/YAxisWidget' - import Chart from '../Chart' import { createDom } from '../common/utils/dom' -import { getPixelRatio } from '../common/utils/canvas' - -export interface PaneGap { - top?: number - bottom?: number -} - -export interface PaneAxisOptions { - scrollZoomEnabled?: boolean -} - -export interface PaneOptions { - id?: string - height?: number - minHeight?: number - dragEnabled?: boolean - gap?: PaneGap - axisOptions?: PaneAxisOptions -} - -export const PANE_MIN_HEIGHT = 30 - -export const PANE_DEFAULT_HEIGHT = 100 - -export const PaneIdConstants = { - CANDLE: 'candle_pane', - INDICATOR: 'indicator_pane_', - XAXIS: 'xaxis_pane' -} - -export default abstract class Pane implements Updater { +export default abstract class Pane implements Updater { + private _rootContainer: HTMLElement private _container: HTMLElement - private _seriesContainer: HTMLElement private readonly _id: string private readonly _chart: Chart - private _mainWidget: DrawWidget - private _yAxisWidget: Nullable = null - private _separatorWidget: Nullable = null - private readonly _axis: C = this.createAxisComponent() private readonly _bounding: Bounding = getDefaultBounding() - private _topPane: Nullable - private _bottomPane: Nullable - - private readonly _options: DeepRequired> = { minHeight: PANE_MIN_HEIGHT, dragEnabled: true, gap: { top: 0.2, bottom: 0.1 }, axisOptions: { scrollZoomEnabled: true } } - - constructor (rootContainer: HTMLElement, chart: Chart, id: string, topPane?: Pane, bottomPane?: Pane) { + constructor (rootContainer: HTMLElement, afterElement: Nullable, chart: Chart, id: string) { this._chart = chart this._id = id - this._topPane = topPane ?? null - this._bottomPane = bottomPane ?? null - this._init(rootContainer) + this._init(rootContainer, afterElement) } - private _init (rootContainer: HTMLElement): void { - this._container = rootContainer - this._seriesContainer = createDom('div', { + private _init (rootContainer: HTMLElement, afterElement: Nullable): void { + this._rootContainer = rootContainer + this._container = createDom('div', { width: '100%', margin: '0', padding: '0', @@ -93,181 +42,45 @@ export default abstract class Pane implements Updater { overflow: 'hidden', boxSizing: 'border-box' }) - this._separatorWidget = this.createSeparatorWidget(rootContainer) - const lastElement = rootContainer.lastChild - if (lastElement !== null) { - rootContainer.insertBefore(this._seriesContainer, lastElement) + if (afterElement !== null) { + rootContainer.insertBefore(this._container, afterElement) } else { - rootContainer.appendChild(this._seriesContainer) + rootContainer.appendChild(this._container) } - this._mainWidget = this.createMainWidget(this._seriesContainer) - this._yAxisWidget = this.createYAxisWidget(this._seriesContainer) } getContainer (): HTMLElement { - return this._seriesContainer + return this._container } getId (): string { return this._id } - setOptions (options: Omit): Pane { - merge(this._options, options) - let container: HTMLElement - let cursor: string - if (this.getId() === PaneIdConstants.XAXIS) { - container = this.getMainWidget().getContainer() - cursor = 'ew-resize' - } else { - container = this.getYAxisWidget()?.getContainer() as HTMLElement - cursor = 'ns-resize' - } - if (options.axisOptions?.scrollZoomEnabled ?? true) { - container.style.cursor = cursor - } else { - container.style.cursor = 'default' - } - return this - } - - getOptions (): DeepRequired> { return this._options } - getChart (): Chart { return this._chart } - getAxisComponent (): C { - return this._axis - } - - setBounding (rootBounding: Partial, mainBounding?: Partial, yAxisBounding?: Partial): Pane { - merge(this._bounding, rootBounding) - let separatorSize = 0 - if (this._separatorWidget !== null) { - separatorSize = this._chart.getStyles().separator.size - const separatorBounding: Partial = { ...rootBounding, height: REAL_SEPARATOR_HEIGHT } - if (rootBounding.top !== undefined) { - separatorBounding.top = rootBounding.top - Math.floor((REAL_SEPARATOR_HEIGHT - separatorSize) / 2) - } - this._separatorWidget.setBounding(separatorBounding) - } - const contentBounding: Partial = {} - if (rootBounding.height !== undefined) { - contentBounding.height = rootBounding.height - separatorSize - } - if (rootBounding.top !== undefined) { - contentBounding.top = rootBounding.top + separatorSize - } - this._mainWidget.setBounding(contentBounding) - this._yAxisWidget?.setBounding(contentBounding) - if (mainBounding !== undefined) { - this._mainWidget.setBounding(mainBounding) - this._separatorWidget?.setBounding(mainBounding) - } - if (yAxisBounding !== undefined) { - this._yAxisWidget?.setBounding(yAxisBounding) - } - return this - } - - getTopPane (): Nullable { - return this._topPane - } - - setTopPane (pane: Nullable): Pane { - this._topPane = pane - return this - } - - getBottomPane (): Nullable { - return this._bottomPane - } - - setBottomPane (pane: Nullable): Pane { - this._bottomPane = pane - return this - } - getBounding (): Bounding { return this._bounding } - getMainWidget (): DrawWidget { return this._mainWidget } - - getYAxisWidget (): Nullable { return this._yAxisWidget } - - getSeparatorWidget (): Nullable { return this._separatorWidget } - update (level?: UpdateLevel): void { - if (this._bounding.width !== this._seriesContainer.offsetWidth) { - this._seriesContainer.style.width = `${this._bounding.width}px` - } - const seriesHeight = this._mainWidget.getBounding().height - if (seriesHeight !== this._seriesContainer.offsetHeight) { - this._seriesContainer.style.height = `${seriesHeight}px` - } - const l = level ?? UpdateLevel.Drawer - this._mainWidget.update(l) - this._yAxisWidget?.update(l) - this._separatorWidget?.update(l) - } - - getImage (includeOverlay: boolean): HTMLCanvasElement { - const { width, height } = this._bounding - const canvas = createDom('canvas', { - width: `${width}px`, - height: `${height}px`, - boxSizing: 'border-box' - }) - const ctx = canvas.getContext('2d') as CanvasRenderingContext2D - const pixelRatio = getPixelRatio(canvas) - canvas.width = width * pixelRatio - canvas.height = height * pixelRatio - ctx.scale(pixelRatio, pixelRatio) - - let top = 0 - if (this._separatorWidget != null) { - const separatorHeight = this.getChart().getStyles().separator.size - top = separatorHeight - ctx.drawImage( - this._separatorWidget.getImage(), - 0, 0, - width, separatorHeight - ) - } - - const mainBounding = this._mainWidget.getBounding() - ctx.drawImage( - this._mainWidget.getImage(includeOverlay), - mainBounding.left, top, - mainBounding.width, mainBounding.height - ) - if (this._yAxisWidget !== null) { - const yAxisBounding = this._yAxisWidget.getBounding() - ctx.drawImage( - this._yAxisWidget.getImage(includeOverlay), - yAxisBounding.left, top, - yAxisBounding.width, yAxisBounding.height - ) + if (this._bounding.height !== this._container.offsetHeight) { + this._container.style.height = `${this._bounding.height}px` } - return canvas + this.updateImp(level ?? UpdateLevel.Drawer, this._container, this._bounding) } destroy (): void { - this._container.removeChild(this._seriesContainer) - if (this._separatorWidget !== null) { - this._container.removeChild(this._separatorWidget.getContainer()) - } + this._rootContainer.removeChild(this._container) } - abstract getName (): string - - protected abstract createAxisComponent (): C + abstract setBounding (...bounding: Array>): Pane - protected createSeparatorWidget (_container: HTMLElement): Nullable { return null } + abstract getImage (includeOverlay: boolean): HTMLCanvasElement - protected createYAxisWidget (_container: HTMLElement): Nullable { return null } + abstract getName (): string - protected abstract createMainWidget (container: HTMLElement): DrawWidget + abstract updateImp (level: UpdateLevel, container: HTMLElement, bounding: Bounding): void } diff --git a/src/pane/SeparatorPane.ts b/src/pane/SeparatorPane.ts new file mode 100644 index 000000000..1b4f03385 --- /dev/null +++ b/src/pane/SeparatorPane.ts @@ -0,0 +1,86 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Nullable from '../common/Nullable' +import { UpdateLevel } from '../common/Updater' +import Bounding from '../common/Bounding' +import { merge } from '../common/utils/typeChecks' + +import Chart from '../Chart' + +import DrawPane from './DrawPane' +import Pane from './Pane' +import { PaneNameConstants } from './types' + +import SeparatorWidget from '../widget/SeparatorWidget' + +export default class SeparatorPane extends Pane { + private _topPane: DrawPane + private _bottomPane: DrawPane + + private readonly _separatorWidget: SeparatorWidget + + constructor (rootContainer: HTMLElement, afterElement: Nullable, chart: Chart, id: string, topPane: DrawPane, bottomPane: DrawPane) { + super(rootContainer, afterElement, chart, id) + this.getContainer().style.overflow = '' + this._topPane = topPane + this._bottomPane = bottomPane + this._separatorWidget = new SeparatorWidget(this.getContainer(), this) + } + + override setBounding (rootBounding: Partial): Pane { + merge(this.getBounding(), rootBounding) + return this + } + + getTopPane (): DrawPane { + return this._topPane + } + + setTopPane (pane: DrawPane): Pane { + this._topPane = pane + return this + } + + getBottomPane (): DrawPane { + return this._bottomPane + } + + setBottomPane (pane: DrawPane): Pane { + this._bottomPane = pane + return this + } + + getWidget (): SeparatorWidget { return this._separatorWidget } + + override getImage (_includeOverlay: boolean): HTMLCanvasElement { + throw new Error('Method not implemented.') + } + + override updateImp (level: UpdateLevel, container: HTMLElement, bounding: Bounding): void { + if (level === UpdateLevel.All || level === UpdateLevel.Separator) { + const styles = this.getChart().getStyles().separator + const fill = styles.fill + container.style.backgroundColor = styles.color + container.style.height = `${styles.size}px` + container.style.marginLeft = `${fill ? 0 : bounding.left}px` + container.style.width = fill ? '100%' : `${bounding.width}px` + this._separatorWidget.update(level) + } + } + + override getName (): string { + return PaneNameConstants.SEPARATOR + } +} diff --git a/src/pane/XAxisPane.ts b/src/pane/XAxisPane.ts index 38a09bbe4..3e263fa58 100644 --- a/src/pane/XAxisPane.ts +++ b/src/pane/XAxisPane.ts @@ -17,18 +17,19 @@ import XAxisWidget from '../widget/XAxisWidget' import XAxis from '../component/XAxis' -import Pane from './Pane' +import DrawPane from './DrawPane' +import { PaneNameConstants } from './types' -export default class XAxisPane extends Pane { +export default class XAxisPane extends DrawPane { override getName (): string { - return 'xAxis' + return PaneNameConstants.X_AXIS } override createAxisComponent (): XAxis { return new XAxis(this) } - override createMainWidget (container: HTMLElement): DrawWidget { + override createMainWidget (container: HTMLElement): DrawWidget> { return new XAxisWidget(container, this) } } diff --git a/src/pane/types.ts b/src/pane/types.ts new file mode 100644 index 000000000..bbe3dffb5 --- /dev/null +++ b/src/pane/types.ts @@ -0,0 +1,54 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface PaneGap { + top?: number + bottom?: number +} + +export interface PaneAxisOptions { + scrollZoomEnabled?: boolean +} + +export const enum PanePosition { + Top = 'top', + Bottom = 'bottom' +} + +export interface PaneOptions { + id?: string + height?: number + minHeight?: number + dragEnabled?: boolean + position?: PanePosition + gap?: PaneGap + axisOptions?: PaneAxisOptions +} + +export const PaneNameConstants = { + CANDLE: 'candle', + INDICATOR: 'indicator', + SEPARATOR: 'separator', + X_AXIS: 'xAxis' +} + +export const PANE_MIN_HEIGHT = 30 + +export const PANE_DEFAULT_HEIGHT = 100 + +export const PaneIdConstants = { + CANDLE: 'candle_pane', + INDICATOR: 'indicator_pane_', + X_AXIS: 'x_axis_pane' +} diff --git a/src/store/OverlayStore.ts b/src/store/OverlayStore.ts index 5bff86f75..8fbef09af 100644 --- a/src/store/OverlayStore.ts +++ b/src/store/OverlayStore.ts @@ -15,7 +15,7 @@ import Nullable from '../common/Nullable' import { UpdateLevel } from '../common/Updater' import { MouseTouchEvent } from '../common/SyntheticEvent' - +import { isFunction } from '../common/utils/typeChecks' import { createId } from '../common/utils/id' import OverlayImp, { OVERLAY_ID_PREFIX, OVERLAY_ACTIVE_Z_LEVEL, OverlayCreate, OverlayRemove } from '../component/Overlay' @@ -24,8 +24,7 @@ import { getOverlayInnerClass } from '../extension/overlay/index' import ChartStore from './ChartStore' -import { PaneIdConstants } from '../pane/Pane' -import { isFunction } from '../common/utils/typeChecks' +import { PaneIdConstants } from '../pane/types' export interface ProgressOverlayInfo { paneId: string @@ -407,7 +406,7 @@ export default class OverlayStore { updatePaneIds.forEach(paneId => { chart.updatePane(UpdateLevel.Overlay, paneId) }) - chart.updatePane(UpdateLevel.Overlay, PaneIdConstants.XAXIS) + chart.updatePane(UpdateLevel.Overlay, PaneIdConstants.X_AXIS) } } @@ -476,7 +475,7 @@ export default class OverlayStore { if (paneId !== info.paneId) { chart.updatePane(UpdateLevel.Overlay, paneId) } - chart.updatePane(UpdateLevel.Overlay, PaneIdConstants.XAXIS) + chart.updatePane(UpdateLevel.Overlay, PaneIdConstants.X_AXIS) } } } diff --git a/src/view/AxisView.ts b/src/view/AxisView.ts index 6d514e678..5491a8f62 100644 --- a/src/view/AxisView.ts +++ b/src/view/AxisView.ts @@ -22,7 +22,7 @@ import Axis, { AxisTick } from '../component/Axis' import View from './View' -export default abstract class AxisView extends View { +export default abstract class AxisView extends View { override drawImp (ctx: CanvasRenderingContext2D): void { const widget = this.getWidget() const pane = widget.getPane() diff --git a/src/view/CandleBarView.ts b/src/view/CandleBarView.ts index 9c0c53799..1f239ac95 100644 --- a/src/view/CandleBarView.ts +++ b/src/view/CandleBarView.ts @@ -28,7 +28,7 @@ import { RectAttrs } from '../extension/figure/rect' import ChildrenView from './ChildrenView' -import { PaneIdConstants } from '../pane/Pane' +import { PaneIdConstants } from '../pane/types' export interface CandleBarOptions { type: Exclude diff --git a/src/view/CandleTooltipView.ts b/src/view/CandleTooltipView.ts index f46769871..401c5fa35 100644 --- a/src/view/CandleTooltipView.ts +++ b/src/view/CandleTooltipView.ts @@ -24,7 +24,7 @@ import { import { CustomApi, FormatDateType } from '../Options' -import { PaneIdConstants } from '../pane/Pane' +import { PaneIdConstants } from '../pane/types' import Indicator from '../component/Indicator' diff --git a/src/view/CrosshairHorizontalLabelView.ts b/src/view/CrosshairHorizontalLabelView.ts index c01a88a5c..5c0e642d0 100644 --- a/src/view/CrosshairHorizontalLabelView.ts +++ b/src/view/CrosshairHorizontalLabelView.ts @@ -62,7 +62,7 @@ export default class CrosshairHorizontalLabelView extend return styles.horizontal } - protected getText (crosshair: Crosshair, chartStore: ChartStore, axis: C): string { + protected getText (crosshair: Crosshair, chartStore: ChartStore, axis: Axis): string { const yAxis = axis as unknown as YAxis const value = axis.convertFromPixel(crosshair.y as number) let text: string @@ -92,7 +92,7 @@ export default class CrosshairHorizontalLabelView extend return formatThousands(text, chartStore.getThousandsSeparator()) } - protected getTextAttrs (text: string, _textWidth: number, crosshair: Crosshair, bounding: Bounding, axis: C, _styles: StateTextStyle): TextAttrs { + protected getTextAttrs (text: string, _textWidth: number, crosshair: Crosshair, bounding: Bounding, axis: Axis, _styles: StateTextStyle): TextAttrs { const yAxis = axis as unknown as YAxis let x: number let textAlign: CanvasTextAlign diff --git a/src/view/CrosshairVerticalLabelView.ts b/src/view/CrosshairVerticalLabelView.ts index 06b307629..68cbc3a0a 100644 --- a/src/view/CrosshairVerticalLabelView.ts +++ b/src/view/CrosshairVerticalLabelView.ts @@ -17,6 +17,7 @@ import Crosshair from '../common/Crosshair' import { CrosshairStyle, CrosshairDirectionStyle, StateTextStyle } from '../common/Styles' import { FormatDateType } from '../Options' +import Axis from '../component/Axis' import XAxis from '../component/XAxis' import ChartStore from '../store/ChartStore' @@ -38,7 +39,7 @@ export default class CrosshairVerticalLabelView extends CrosshairHorizontalLabel return chartStore.getCustomApi().formatDate(chartStore.getTimeScaleStore().getDateTimeFormat(), timestamp, 'YYYY-MM-DD HH:mm', FormatDateType.Crosshair) } - override getTextAttrs (text: string, textWidth: number, crosshair: Crosshair, bounding: Bounding, _axis: XAxis, styles: StateTextStyle): TextAttrs { + override getTextAttrs (text: string, textWidth: number, crosshair: Crosshair, bounding: Bounding, _axis: Axis, styles: StateTextStyle): TextAttrs { const x = crosshair.realX as number let optimalX: number let align: CanvasTextAlign = 'center' diff --git a/src/view/GridView.ts b/src/view/GridView.ts index 5b26e1f72..17e7111bf 100644 --- a/src/view/GridView.ts +++ b/src/view/GridView.ts @@ -13,13 +13,12 @@ */ import XAxis from '../component/XAxis' -import YAxis from '../component/YAxis' -import { PaneIdConstants } from '../pane/Pane' +import { PaneIdConstants } from '../pane/types' import View from './View' -export default class GridView extends View { +export default class GridView extends View { override drawImp (ctx: CanvasRenderingContext2D): void { const widget = this.getWidget() const pane = this.getWidget().getPane() @@ -52,7 +51,7 @@ export default class GridView extends View { const verticalStyles = gridStyles.vertical const verticalShow = verticalStyles.show if (verticalShow) { - const xAxis = chart.getPaneById(PaneIdConstants.XAXIS)?.getAxisComponent() as XAxis + const xAxis = chart.getDrawPaneById(PaneIdConstants.X_AXIS)?.getAxisComponent() as XAxis xAxis.getTicks().forEach(tick => { this.createFigure( 'line', diff --git a/src/view/IndicatorLastValueView.ts b/src/view/IndicatorLastValueView.ts index 0983e0347..105668745 100644 --- a/src/view/IndicatorLastValueView.ts +++ b/src/view/IndicatorLastValueView.ts @@ -12,8 +12,6 @@ * limitations under the License. */ -import YAxis from '../component/YAxis' - import { eachFigures, IndicatorFigure, IndicatorFigureStyle } from '../component/Indicator' import View from './View' @@ -21,6 +19,8 @@ import View from './View' import { formatPrecision, formatThousands } from '../common/utils/format' import { isValid } from '../common/utils/typeChecks' +import YAxis from '../component/YAxis' + export default class IndicatorLastValueView extends View { override drawImp (ctx: CanvasRenderingContext2D): void { const widget = this.getWidget() diff --git a/src/view/IndicatorTooltipView.ts b/src/view/IndicatorTooltipView.ts index 4bedd6891..91b454cfa 100644 --- a/src/view/IndicatorTooltipView.ts +++ b/src/view/IndicatorTooltipView.ts @@ -26,7 +26,7 @@ import YAxis from '../component/YAxis' import IndicatorImp, { eachFigures, Indicator, IndicatorFigure, IndicatorFigureStyle, IndicatorTooltipData } from '../component/Indicator' -import { PaneIdConstants } from '../pane/Pane' +import { PaneIdConstants } from '../pane/types' import { formatPrecision, formatThousands } from '../common/utils/format' import { isValid, isObject } from '../common/utils/typeChecks' @@ -333,7 +333,7 @@ export default class IndicatorTooltipView extends View { bounding: widget.getBounding(), crosshair, defaultStyles: styles, - xAxis: pane.getChart().getPaneById(PaneIdConstants.XAXIS)?.getAxisComponent() as XAxis, + xAxis: pane.getChart().getDrawPaneById(PaneIdConstants.X_AXIS)?.getAxisComponent() as XAxis, yAxis: pane.getAxisComponent() }) if (customName !== undefined && tooltipStyles.showName) { diff --git a/src/view/IndicatorView.ts b/src/view/IndicatorView.ts index 2b3395dd2..12100e596 100644 --- a/src/view/IndicatorView.ts +++ b/src/view/IndicatorView.ts @@ -17,7 +17,7 @@ import VisibleData from '../common/VisibleData' import BarSpace from '../common/BarSpace' import { CandleType } from '../common/Styles' -import { PaneIdConstants } from '../pane/Pane' +import { PaneIdConstants } from '../pane/types' import ChartStore from '../store/ChartStore' @@ -70,7 +70,7 @@ export default class IndicatorView extends CandleBarView { const pane = widget.getPane() const chart = pane.getChart() const bounding = widget.getBounding() - const xAxis = chart.getPaneById(PaneIdConstants.XAXIS)?.getAxisComponent() as Axis + const xAxis = chart.getDrawPaneById(PaneIdConstants.X_AXIS)?.getAxisComponent() as Axis const yAxis = pane.getAxisComponent() const chartStore = chart.getChartStore() const dataList = chartStore.getDataList() diff --git a/src/view/OverlayView.ts b/src/view/OverlayView.ts index bdff570cc..88521689d 100644 --- a/src/view/OverlayView.ts +++ b/src/view/OverlayView.ts @@ -32,14 +32,15 @@ import Overlay, { OVERLAY_FIGURE_KEY_PREFIX, OverlayFigure, OverlayFigureIgnoreE import OverlayStore, { ProgressOverlayInfo, EventOverlayInfo, EventOverlayInfoFigureType } from '../store/OverlayStore' import TimeScaleStore from '../store/TimeScaleStore' -import { PaneIdConstants } from '../pane/Pane' +import { PaneIdConstants } from '../pane/types' -import Widget from '../widget/Widget' +import DrawWidget from '../widget/DrawWidget' +import DrawPane from '../pane/DrawPane' import View from './View' export default class OverlayView extends View { - constructor (widget: Widget) { + constructor (widget: DrawWidget>) { super(widget) this._initEvent() } @@ -287,7 +288,7 @@ export default class OverlayView extends View { const paneId = pane.getId() const timeScaleStore = chart.getChartStore().getTimeScaleStore() if (this.coordinateToPointTimestampDataIndexFlag()) { - const xAxis = chart.getPaneById(PaneIdConstants.XAXIS)?.getAxisComponent() as Axis + const xAxis = chart.getDrawPaneById(PaneIdConstants.X_AXIS)?.getAxisComponent() as Axis const dataIndex = xAxis.convertFromPixel(coordinate.x) const timestamp = timeScaleStore.dataIndexToTimestamp(dataIndex) ?? undefined point.dataIndex = dataIndex @@ -373,7 +374,7 @@ export default class OverlayView extends View { const paneId = pane.getId() const chart = pane.getChart() const yAxis = pane.getAxisComponent() as unknown as Nullable - const xAxis = chart.getPaneById(PaneIdConstants.XAXIS)?.getAxisComponent() as Nullable + const xAxis = chart.getDrawPaneById(PaneIdConstants.X_AXIS)?.getAxisComponent() as Nullable const bounding = widget.getBounding() const chartStore = chart.getChartStore() const customApi = chartStore.getCustomApi() diff --git a/src/view/View.ts b/src/view/View.ts index 32c73194c..1e0854c7d 100644 --- a/src/view/View.ts +++ b/src/view/View.ts @@ -19,22 +19,23 @@ import Eventful from '../common/Eventful' import Figure from '../component/Figure' import { getInnerFigureClass } from '../extension/figure/index' -import Axis from '../component/Axis' +import DrawWidget from '../widget/DrawWidget' +import DrawPane from '../pane/DrawPane' -import Widget from '../widget/Widget' +import Axis from '../component/Axis' export default abstract class View extends Eventful { /** * Parent widget */ - private readonly _widget: Widget + private readonly _widget: DrawWidget> - constructor (widget: Widget) { + constructor (widget: DrawWidget>) { super() this._widget = widget } - getWidget (): Widget { return this._widget } + getWidget (): DrawWidget> { return this._widget } protected createFigure (name: string, attrs: any, styles: any, eventHandler?: EventHandler): Nullable
{ const FigureClazz = getInnerFigureClass(name) diff --git a/src/widget/CandleWidget.ts b/src/widget/CandleWidget.ts index 117ebdf88..961b6fc05 100644 --- a/src/widget/CandleWidget.ts +++ b/src/widget/CandleWidget.ts @@ -12,12 +12,8 @@ * limitations under the License. */ -import Pane from '../pane/Pane' - import IndicatorWidget from './IndicatorWidget' -import YAxis from '../component/YAxis' - import CandleBarView from '../view/CandleBarView' import CandleAreaView from '../view/CandleAreaView' import CandleHighLowPriceView from '../view/CandleHighLowPriceView' @@ -28,13 +24,17 @@ import CandleTooltipView from '../view/CandleTooltipView' import { CandleType } from '../common/Styles' +import AxisPane from '../pane/DrawPane' + +import YAxis from '../component/YAxis' + export default class CandleWidget extends IndicatorWidget { private readonly _candleBarView = new CandleBarView(this) private readonly _candleAreaView = new CandleAreaView(this) private readonly _candleHighLowPriceView = new CandleHighLowPriceView(this) private readonly _candleLastPriceLineView = new CandleLastPriceLineView(this) - constructor (rootContainer: HTMLElement, pane: Pane) { + constructor (rootContainer: HTMLElement, pane: AxisPane) { super(rootContainer, pane) this.addChild(this._candleBarView) } diff --git a/src/widget/DrawWidget.ts b/src/widget/DrawWidget.ts index 445214e36..974620f6b 100644 --- a/src/widget/DrawWidget.ts +++ b/src/widget/DrawWidget.ts @@ -15,7 +15,7 @@ import Bounding from '../common/Bounding' import { UpdateLevel } from '../common/Updater' -import Axis from '../component/Axis' +import DrawPane from '../pane/DrawPane' import Widget from './Widget' @@ -25,7 +25,7 @@ import { requestAnimationFrame, cancelAnimationFrame } from '../common/utils/com const DEFAULT_REQUEST_ID = -1 -export default abstract class DrawWidget extends Widget { +export default abstract class DrawWidget

extends Widget

{ private _mainCanvas: HTMLCanvasElement private _mainCtx: CanvasRenderingContext2D private _overlayCanvas: HTMLCanvasElement @@ -34,18 +34,8 @@ export default abstract class DrawWidget extends Widget { - return { - margin: '0', - padding: '0', - position: 'absolute', - top: '0', - overflow: 'hidden', - boxSizing: 'border-box' - } - } - - override initDom (container: HTMLElement): void { + override init (rootContainer: HTMLElement): void { + super.init(rootContainer) this._mainCanvas = createDom('canvas', { position: 'absolute', top: '0', @@ -62,10 +52,23 @@ export default abstract class DrawWidget extends Widget { +export default class IndicatorWidget extends DrawWidget> { private readonly _gridView = new GridView(this) private readonly _indicatorView = new IndicatorView(this) private readonly _crosshairLineView = new CrosshairLineView(this) private readonly _tooltipView = this.createTooltipView() private readonly _overlayView = new OverlayView(this) - constructor (rootContainer: HTMLElement, pane: Pane) { + constructor (rootContainer: HTMLElement, pane: DrawPane) { super(rootContainer, pane) this.addChild(this._tooltipView) this.addChild(this._overlayView) diff --git a/src/widget/SeparatorWidget.ts b/src/widget/SeparatorWidget.ts index 5b07e7931..b1c0657bc 100644 --- a/src/widget/SeparatorWidget.ts +++ b/src/widget/SeparatorWidget.ts @@ -17,28 +17,24 @@ import { UpdateLevel } from '../common/Updater' import { MouseTouchEvent } from '../common/SyntheticEvent' import { ActionType } from '../common/Action' -import Axis from '../component/Axis' -import YAxis from '../component/YAxis' +import Widget from './Widget' +import { WidgetNameConstants, REAL_SEPARATOR_HEIGHT } from './types' -import Widget, { WidgetNameConstants } from './Widget' -import Pane from '../pane/Pane' +import SeparatorPane from '../pane/SeparatorPane' import { createDom } from '../common/utils/dom' import { getPixelRatio } from '../common/utils/canvas' import { throttle } from '../common/utils/performance' +import AxisPane from '../pane/DrawPane' -export const REAL_SEPARATOR_HEIGHT = 7 - -export default class SeparatorWidget extends Widget { - private _moveDom: HTMLElement - +export default class SeparatorWidget extends Widget { private _dragFlag = false private _dragStartY = 0 private _topPaneHeight = 0 - private _currentPaneHeight = 0 + private _bottomPaneHeight = 0 - constructor (rootContainer: HTMLElement, pane: Pane) { + constructor (rootContainer: HTMLElement, pane: SeparatorPane) { super(rootContainer, pane) this.registerEvent('touchStartEvent', this._mouseDownEvent.bind(this)) .registerEvent('touchMoveEvent', this._pressedMouseMoveEvent.bind(this)) @@ -62,8 +58,8 @@ export default class SeparatorWidget extends Widget { this._dragFlag = true this._dragStartY = event.pageY const pane = this.getPane() - this._topPaneHeight = pane.getTopPane()?.getBounding().height ?? 0 - this._currentPaneHeight = pane.getBounding().height + this._topPaneHeight = pane.getTopPane().getBounding().height + this._bottomPaneHeight = pane.getBottomPane().getBounding().height return true } @@ -78,21 +74,22 @@ export default class SeparatorWidget extends Widget { const dragDistance = event.pageY - this._dragStartY const currentPane = this.getPane() const topPane = currentPane.getTopPane() + const bottomPane = currentPane.getBottomPane() const isUpDrag = dragDistance < 0 - if (topPane !== null && currentPane.getOptions().dragEnabled) { - let reducedPane: Pane - let increasedPane: Pane + if (topPane !== null && bottomPane !== null && bottomPane.getOptions().dragEnabled) { + let reducedPane: AxisPane + let increasedPane: AxisPane let startDragReducedPaneHeight: number let startDragIncreasedPaneHeight: number if (isUpDrag) { reducedPane = topPane - increasedPane = currentPane + increasedPane = bottomPane startDragReducedPaneHeight = this._topPaneHeight - startDragIncreasedPaneHeight = this._currentPaneHeight + startDragIncreasedPaneHeight = this._bottomPaneHeight } else { - reducedPane = currentPane + reducedPane = bottomPane increasedPane = topPane - startDragReducedPaneHeight = this._currentPaneHeight + startDragReducedPaneHeight = this._bottomPaneHeight startDragIncreasedPaneHeight = this._topPaneHeight } const reducedPaneMinHeight = reducedPane.getOptions().minHeight @@ -111,10 +108,11 @@ export default class SeparatorWidget extends Widget { private _mouseEnterEvent (): boolean { const pane = this.getPane() - if (pane.getOptions().dragEnabled) { + const bottomPane = pane.getBottomPane() + if (bottomPane?.getOptions().dragEnabled ?? false) { const chart = pane.getChart() const styles = chart.getStyles().separator - this._moveDom.style.background = styles.activeBackgroundColor + this.getContainer().style.background = styles.activeBackgroundColor return true } return false @@ -122,25 +120,14 @@ export default class SeparatorWidget extends Widget { private _mouseLeaveEvent (): boolean { if (!this._dragFlag) { - this._moveDom.style.background = '' + this.getContainer().style.background = '' return true } return false } - override getContainerStyle (): Partial { - return { - margin: '0', - padding: '0', - position: 'relative', - boxSizing: 'border-box' - } - } - - override insertBefore (): boolean { return true } - - override initDom (container: HTMLElement): void { - this._moveDom = createDom('div', { + override createContainer (): HTMLElement { + return createDom('div', { width: '100%', height: `${REAL_SEPARATOR_HEIGHT}px`, margin: '0', @@ -151,19 +138,13 @@ export default class SeparatorWidget extends Widget { boxSizing: 'border-box', cursor: 'ns-resize' }) - container.appendChild(this._moveDom) } - override updateImp (container: HTMLElement, bounding: Bounding, level: UpdateLevel): void { + override updateImp (container: HTMLElement, _bounding: Bounding, level: UpdateLevel): void { if (level === UpdateLevel.All || level === UpdateLevel.Separator) { const styles = this.getPane().getChart().getStyles().separator - this._moveDom.style.top = `${-Math.floor((REAL_SEPARATOR_HEIGHT - styles.size) / 2)}px` - this._moveDom.style.height = `${REAL_SEPARATOR_HEIGHT}px` - const fill = styles.fill - container.style.backgroundColor = styles.color - container.style.height = `${styles.size}px` - container.style.marginLeft = `${fill ? 0 : bounding.left}px` - container.style.width = fill ? '100%' : `${bounding.width}px` + container.style.top = `${-Math.floor((REAL_SEPARATOR_HEIGHT - styles.size) / 2)}px` + container.style.height = `${REAL_SEPARATOR_HEIGHT}px` } } diff --git a/src/widget/Widget.ts b/src/widget/Widget.ts index 0cb00e22f..e427cbb96 100644 --- a/src/widget/Widget.ts +++ b/src/widget/Widget.ts @@ -16,55 +16,41 @@ import Bounding, { getDefaultBounding } from '../common/Bounding' import Updater, { UpdateLevel } from '../common/Updater' import Eventful from '../common/Eventful' -import Axis from '../component/Axis' - import Pane from '../pane/Pane' -import { createDom } from '../common/utils/dom' import { merge } from '../common/utils/typeChecks' -export const WidgetNameConstants = { - MAIN: 'main', - XAXIS: 'xAxis', - YAXIS: 'yAxis', - SEPARATOR: 'separator' -} +export default abstract class Widget

extends Eventful implements Updater { + /** + * root container + */ + private _rootContainer: HTMLElement -export default abstract class Widget extends Eventful implements Updater { /** * Parent pane */ - private readonly _pane: Pane + private readonly _pane: P /** - * Root dom container + * wrapper container */ private _container: HTMLElement private readonly _bounding: Bounding = getDefaultBounding() - constructor (rootContainer: HTMLElement, pane: Pane) { + constructor (rootContainer: HTMLElement, pane: P) { super() this._pane = pane - this._init(rootContainer) + this.init(rootContainer) } - private _init (rootContainer: HTMLElement): void { - this._container = createDom('div', this.getContainerStyle()) - if (this.insertBefore()) { - const lastElement = rootContainer.lastChild - if (lastElement !== null) { - rootContainer.insertBefore(this._container, lastElement) - } else { - rootContainer.appendChild(this._container) - } - } else { - rootContainer.appendChild(this._container) - } - this.initDom(this._container) + init (rootContainer: HTMLElement): void { + this._rootContainer = rootContainer + this._container = this.createContainer() + rootContainer.appendChild(this._container) } - setBounding (bounding: Partial): Widget { + setBounding (bounding: Partial): Widget

{ merge(this._bounding, bounding) return this } @@ -75,7 +61,7 @@ export default abstract class Widget extends Eventful imp return this._bounding } - getPane (): Pane { + getPane (): P { return this._pane } @@ -83,13 +69,13 @@ export default abstract class Widget extends Eventful imp this.updateImp(this._container, this._bounding, level ?? UpdateLevel.Drawer) } - abstract getName (): string - - protected insertBefore (): boolean { return false } + destroy (): void { + this._rootContainer.removeChild(this._container) + } - protected abstract getContainerStyle (): Partial + abstract getName (): string - protected abstract initDom (container: HTMLElement): void + protected abstract createContainer (): HTMLElement protected abstract updateImp (container: HTMLElement, bounding: Bounding, level: UpdateLevel): void } diff --git a/src/widget/XAxisWidget.ts b/src/widget/XAxisWidget.ts index d33c4b774..c7ed983cc 100644 --- a/src/widget/XAxisWidget.ts +++ b/src/widget/XAxisWidget.ts @@ -12,30 +12,30 @@ * limitations under the License. */ -import Pane from '../pane/Pane' - -import { WidgetNameConstants } from './Widget' +import { WidgetNameConstants } from './types' import DrawWidget from './DrawWidget' +import DrawPane from '../pane/DrawPane' + import XAxis from '../component/XAxis' import XAxisView from '../view/XAxisView' import OverlayXAxisView from '../view/OverlayXAxisView' import CrosshairVerticalLabelView from '../view/CrosshairVerticalLabelView' -export default class XAxisWidget extends DrawWidget { +export default class XAxisWidget extends DrawWidget> { private readonly _xAxisView = new XAxisView(this) private readonly _overlayXAxisView = new OverlayXAxisView(this) private readonly _crosshairVerticalLabelView = new CrosshairVerticalLabelView(this) - constructor (rootContainer: HTMLElement, pane: Pane) { + constructor (rootContainer: HTMLElement, pane: DrawPane) { super(rootContainer, pane) this.getContainer().style.cursor = 'ew-resize' this.addChild(this._overlayXAxisView) } override getName (): string { - return WidgetNameConstants.XAXIS + return WidgetNameConstants.X_AXIS } override updateMain (ctx: CanvasRenderingContext2D): void { diff --git a/src/widget/YAxisWidget.ts b/src/widget/YAxisWidget.ts index cc0309a06..4bf4eb579 100644 --- a/src/widget/YAxisWidget.ts +++ b/src/widget/YAxisWidget.ts @@ -12,9 +12,9 @@ * limitations under the License. */ -import Pane from '../pane/Pane' +import DrawPane from '../pane/DrawPane' -import { WidgetNameConstants } from './Widget' +import { WidgetNameConstants } from './types' import DrawWidget from './DrawWidget' import YAxis from '../component/YAxis' @@ -25,21 +25,21 @@ import IndicatorLastValueView from '../view/IndicatorLastValueView' import OverlayYAxisView from '../view/OverlayYAxisView' import CrosshairHorizontalLabelView from '../view/CrosshairHorizontalLabelView' -export default class YAxisWidget extends DrawWidget { +export default class YAxisWidget extends DrawWidget> { private readonly _yAxisView = new YAxisView(this) private readonly _candleLastPriceLabelView = new CandleLastPriceLabelView(this) private readonly _indicatorLastValueView = new IndicatorLastValueView(this) private readonly _overlayYAxisView = new OverlayYAxisView(this) private readonly _crosshairHorizontalLabelView = new CrosshairHorizontalLabelView(this) - constructor (rootContainer: HTMLElement, pane: Pane) { + constructor (rootContainer: HTMLElement, pane: DrawPane) { super(rootContainer, pane) this.getContainer().style.cursor = 'ns-resize' this.addChild(this._overlayYAxisView) } override getName (): string { - return WidgetNameConstants.YAXIS + return WidgetNameConstants.Y_AXIS } override updateMain (ctx: CanvasRenderingContext2D): void { diff --git a/src/widget/types.ts b/src/widget/types.ts new file mode 100644 index 000000000..d283467f7 --- /dev/null +++ b/src/widget/types.ts @@ -0,0 +1,22 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const WidgetNameConstants = { + MAIN: 'main', + X_AXIS: 'xAxis', + Y_AXIS: 'yAxis', + SEPARATOR: 'separator' +} + +export const REAL_SEPARATOR_HEIGHT = 7