mirror of
https://github.com/klinecharts/KLineChart.git
synced 2024-11-25 16:22:43 +08:00
refactor: refactor chart store
This commit is contained in:
parent
9d119f56d6
commit
bb853ae4c3
112
src/Chart.ts
112
src/Chart.ts
@ -13,7 +13,6 @@
|
||||
*/
|
||||
|
||||
import type Nullable from './common/Nullable'
|
||||
import type DeepPartial from './common/DeepPartial'
|
||||
import type Bounding from './common/Bounding'
|
||||
import { createDefaultBounding } from './common/Bounding'
|
||||
import type { KLineData } from './common/Data'
|
||||
@ -37,8 +36,7 @@ import { logWarn } from './common/utils/logger'
|
||||
import { binarySearchNearest } from './common/utils/number'
|
||||
import { LoadDataType } from './common/LoadDataCallback'
|
||||
|
||||
import ChartStore from './store/ChartStore'
|
||||
import { SCALE_MULTIPLIER } from './store/TimeScaleStore'
|
||||
import ChartStore, { SCALE_MULTIPLIER } from './store/ChartStore'
|
||||
|
||||
import CandlePane from './pane/CandlePane'
|
||||
import IndicatorPane from './pane/IndicatorPane'
|
||||
@ -74,15 +72,10 @@ export interface Chart {
|
||||
id: string
|
||||
getDom: (paneId?: string, position?: DomPosition) => Nullable<HTMLElement>
|
||||
getSize: (paneId?: string, position?: DomPosition) => Nullable<Bounding>
|
||||
setLocale: (locale: string) => void
|
||||
getLocale: () => string
|
||||
setStyles: (styles: string | DeepPartial<Styles>) => void
|
||||
getStyles: () => Styles
|
||||
setCustomApi: (customApi: Partial<CustomApi>) => void
|
||||
setOptions: (options: Options) => void
|
||||
getOptions: () => Required<Omit<Options, 'layout'>> & { customApi: CustomApi, styles: Styles }
|
||||
setPriceVolumePrecision: (pricePrecision: number, volumePrecision: number) => void
|
||||
getPriceVolumePrecision: () => Precision
|
||||
setTimezone: (timezone: string) => void
|
||||
getTimezone: () => string
|
||||
setOffsetRightDistance: (distance: number) => void
|
||||
getOffsetRightDistance: () => number
|
||||
setMaxOffsetLeftDistance: (distance: number) => void
|
||||
@ -303,7 +296,7 @@ export default class ChartImp implements Chart {
|
||||
|
||||
private _measurePaneHeight (): void {
|
||||
const totalHeight = this._chartBounding.height
|
||||
const separatorSize = this._chartStore.getStyles().separator.size
|
||||
const separatorSize = this._chartStore.getOptions().styles.separator.size
|
||||
const xAxisHeight = this._xAxisPane.getAxisComponent().getAutoSize()
|
||||
let remainingHeight = totalHeight - xAxisHeight
|
||||
if (remainingHeight < 0) {
|
||||
@ -390,7 +383,7 @@ export default class ChartImp implements Chart {
|
||||
|
||||
private _measurePaneWidth (): void {
|
||||
const totalWidth = this._chartBounding.width
|
||||
const styles = this._chartStore.getStyles()
|
||||
const styles = this._chartStore.getOptions().styles
|
||||
|
||||
let leftYAxisWidth = 0
|
||||
let leftYAxisOutside = true
|
||||
@ -429,7 +422,7 @@ export default class ChartImp implements Chart {
|
||||
mainRight = rightYAxisWidth
|
||||
}
|
||||
|
||||
this._chartStore.getTimeScaleStore().setTotalBarSpace(mainWidth)
|
||||
this._chartStore.setTotalBarSpace(mainWidth)
|
||||
|
||||
const paneBounding = { width: totalWidth }
|
||||
const mainBounding = { width: mainWidth, left: mainLeft, right: mainRight }
|
||||
@ -613,29 +606,6 @@ export default class ChartImp implements Chart {
|
||||
return null
|
||||
}
|
||||
|
||||
setStyles (styles: string | DeepPartial<Styles>): void {
|
||||
this._chartStore.setOptions({ styles })
|
||||
this.adjustPaneViewport(true, true, true, true, true)
|
||||
}
|
||||
|
||||
getStyles (): Styles {
|
||||
return this._chartStore.getStyles()
|
||||
}
|
||||
|
||||
setLocale (locale: string): void {
|
||||
this._chartStore.setOptions({ locale })
|
||||
this.adjustPaneViewport(true, true, true, true, true)
|
||||
}
|
||||
|
||||
getLocale (): string {
|
||||
return this._chartStore.getLocale()
|
||||
}
|
||||
|
||||
setCustomApi (customApi: Partial<CustomApi>): void {
|
||||
this._chartStore.setOptions({ customApi })
|
||||
this.adjustPaneViewport(true, true, true, true, true)
|
||||
}
|
||||
|
||||
setPriceVolumePrecision (pricePrecision: number, volumePrecision: number): void {
|
||||
this._chartStore.setPrecision({ price: pricePrecision, volume: volumePrecision })
|
||||
}
|
||||
@ -644,23 +614,23 @@ export default class ChartImp implements Chart {
|
||||
return this._chartStore.getPrecision()
|
||||
}
|
||||
|
||||
setTimezone (timezone: string): void {
|
||||
this._chartStore.setOptions({ timezone })
|
||||
setOptions (options: Options): void {
|
||||
this._chartStore.setOptions(options)
|
||||
const axis = (this._xAxisPane.getAxisComponent() as unknown as AxisImp)
|
||||
axis.buildTicks(true)
|
||||
this._xAxisPane.update(UpdateLevel.Drawer)
|
||||
this.adjustPaneViewport(true, true, true, true, true)
|
||||
}
|
||||
|
||||
getTimezone (): string {
|
||||
return this._chartStore.getTimeScaleStore().getTimezone()
|
||||
getOptions (): Required<Omit<Options, 'layout'>> & { customApi: CustomApi, styles: Styles } {
|
||||
return this._chartStore.getOptions()
|
||||
}
|
||||
|
||||
setOffsetRightDistance (distance: number): void {
|
||||
this._chartStore.getTimeScaleStore().setOffsetRightDistance(distance, true)
|
||||
this._chartStore.setOffsetRightDistance(distance, true)
|
||||
}
|
||||
|
||||
getOffsetRightDistance (): number {
|
||||
return this._chartStore.getTimeScaleStore().getOffsetRightDistance()
|
||||
return this._chartStore.getOffsetRightDistance()
|
||||
}
|
||||
|
||||
setMaxOffsetLeftDistance (distance: number): void {
|
||||
@ -668,7 +638,7 @@ export default class ChartImp implements Chart {
|
||||
logWarn('setMaxOffsetLeftDistance', 'distance', 'distance must greater than zero!!!')
|
||||
return
|
||||
}
|
||||
this._chartStore.getTimeScaleStore().setMaxOffsetLeftDistance(distance)
|
||||
this._chartStore.setMaxOffsetLeftDistance(distance)
|
||||
}
|
||||
|
||||
setMaxOffsetRightDistance (distance: number): void {
|
||||
@ -676,7 +646,7 @@ export default class ChartImp implements Chart {
|
||||
logWarn('setMaxOffsetRightDistance', 'distance', 'distance must greater than zero!!!')
|
||||
return
|
||||
}
|
||||
this._chartStore.getTimeScaleStore().setMaxOffsetRightDistance(distance)
|
||||
this._chartStore.setMaxOffsetRightDistance(distance)
|
||||
}
|
||||
|
||||
setLeftMinVisibleBarCount (barCount: number): void {
|
||||
@ -684,7 +654,7 @@ export default class ChartImp implements Chart {
|
||||
logWarn('setLeftMinVisibleBarCount', 'barCount', 'barCount must greater than zero!!!')
|
||||
return
|
||||
}
|
||||
this._chartStore.getTimeScaleStore().setLeftMinVisibleBarCount(Math.ceil(barCount))
|
||||
this._chartStore.setLeftMinVisibleBarCount(Math.ceil(barCount))
|
||||
}
|
||||
|
||||
setRightMinVisibleBarCount (barCount: number): void {
|
||||
@ -692,19 +662,19 @@ export default class ChartImp implements Chart {
|
||||
logWarn('setRightMinVisibleBarCount', 'barCount', 'barCount must greater than zero!!!')
|
||||
return
|
||||
}
|
||||
this._chartStore.getTimeScaleStore().setRightMinVisibleBarCount(Math.ceil(barCount))
|
||||
this._chartStore.setRightMinVisibleBarCount(Math.ceil(barCount))
|
||||
}
|
||||
|
||||
setBarSpace (space: number): void {
|
||||
this._chartStore.getTimeScaleStore().setBarSpace(space)
|
||||
this._chartStore.setBarSpace(space)
|
||||
}
|
||||
|
||||
getBarSpace (): number {
|
||||
return this._chartStore.getTimeScaleStore().getBarSpace().bar
|
||||
return this._chartStore.getBarSpace().bar
|
||||
}
|
||||
|
||||
getVisibleRange (): VisibleRange {
|
||||
return this._chartStore.getTimeScaleStore().getVisibleRange()
|
||||
return this._chartStore.getVisibleRange()
|
||||
}
|
||||
|
||||
clearData (): void {
|
||||
@ -877,50 +847,47 @@ export default class ChartImp implements Chart {
|
||||
}
|
||||
|
||||
setZoomEnabled (enabled: boolean): void {
|
||||
this._chartStore.getTimeScaleStore().setZoomEnabled(enabled)
|
||||
this._chartStore.setZoomEnabled(enabled)
|
||||
}
|
||||
|
||||
isZoomEnabled (): boolean {
|
||||
return this._chartStore.getTimeScaleStore().getZoomEnabled()
|
||||
return this._chartStore.getZoomEnabled()
|
||||
}
|
||||
|
||||
setScrollEnabled (enabled: boolean): void {
|
||||
this._chartStore.getTimeScaleStore().setScrollEnabled(enabled)
|
||||
this._chartStore.setScrollEnabled(enabled)
|
||||
}
|
||||
|
||||
isScrollEnabled (): boolean {
|
||||
return this._chartStore.getTimeScaleStore().getScrollEnabled()
|
||||
return this._chartStore.getScrollEnabled()
|
||||
}
|
||||
|
||||
scrollByDistance (distance: number, animationDuration?: number): void {
|
||||
const duration = isNumber(animationDuration) && animationDuration > 0 ? animationDuration : 0
|
||||
const timeScaleStore = this._chartStore.getTimeScaleStore()
|
||||
timeScaleStore.startScroll()
|
||||
this._chartStore.startScroll()
|
||||
if (duration > 0) {
|
||||
const animation = new Animation({ duration })
|
||||
animation.doFrame(frameTime => {
|
||||
const progressDistance = distance * (frameTime / duration)
|
||||
timeScaleStore.scroll(progressDistance)
|
||||
this._chartStore.scroll(progressDistance)
|
||||
})
|
||||
animation.start()
|
||||
} else {
|
||||
timeScaleStore.scroll(distance)
|
||||
this._chartStore.scroll(distance)
|
||||
}
|
||||
}
|
||||
|
||||
scrollToRealTime (animationDuration?: number): void {
|
||||
const timeScaleStore = this._chartStore.getTimeScaleStore()
|
||||
const { bar: barSpace } = timeScaleStore.getBarSpace()
|
||||
const difBarCount = timeScaleStore.getLastBarRightSideDiffBarCount() - timeScaleStore.getInitialOffsetRightDistance() / barSpace
|
||||
const { bar: barSpace } = this._chartStore.getBarSpace()
|
||||
const difBarCount = this._chartStore.getLastBarRightSideDiffBarCount() - this._chartStore.getInitialOffsetRightDistance() / barSpace
|
||||
const distance = difBarCount * barSpace
|
||||
this.scrollByDistance(distance, animationDuration)
|
||||
}
|
||||
|
||||
scrollToDataIndex (dataIndex: number, animationDuration?: number): void {
|
||||
const timeScaleStore = this._chartStore.getTimeScaleStore()
|
||||
const distance = (
|
||||
timeScaleStore.getLastBarRightSideDiffBarCount() + (this.getDataList().length - 1 - dataIndex)
|
||||
) * timeScaleStore.getBarSpace().bar
|
||||
this._chartStore.getLastBarRightSideDiffBarCount() + (this.getDataList().length - 1 - dataIndex)
|
||||
) * this._chartStore.getBarSpace().bar
|
||||
this.scrollByDistance(distance, animationDuration)
|
||||
}
|
||||
|
||||
@ -931,8 +898,7 @@ export default class ChartImp implements Chart {
|
||||
|
||||
zoomAtCoordinate (scale: number, coordinate?: Coordinate, animationDuration?: number): void {
|
||||
const duration = isNumber(animationDuration) && animationDuration > 0 ? animationDuration : 0
|
||||
const timeScaleStore = this._chartStore.getTimeScaleStore()
|
||||
const { bar: barSpace } = timeScaleStore.getBarSpace()
|
||||
const { bar: barSpace } = this._chartStore.getBarSpace()
|
||||
const scaleBarSpace = barSpace * scale
|
||||
const difSpace = scaleBarSpace - barSpace
|
||||
if (duration > 0) {
|
||||
@ -940,18 +906,18 @@ export default class ChartImp implements Chart {
|
||||
const animation = new Animation({ duration })
|
||||
animation.doFrame(frameTime => {
|
||||
const progressBarSpace = difSpace * (frameTime / duration)
|
||||
const scale = (progressBarSpace - prevProgressBarSpace) / timeScaleStore.getBarSpace().bar * SCALE_MULTIPLIER
|
||||
timeScaleStore.zoom(scale, coordinate)
|
||||
const scale = (progressBarSpace - prevProgressBarSpace) / this._chartStore.getBarSpace().bar * SCALE_MULTIPLIER
|
||||
this._chartStore.zoom(scale, coordinate)
|
||||
prevProgressBarSpace = progressBarSpace
|
||||
})
|
||||
animation.start()
|
||||
} else {
|
||||
timeScaleStore.zoom(difSpace / barSpace * SCALE_MULTIPLIER, coordinate)
|
||||
this._chartStore.zoom(difSpace / barSpace * SCALE_MULTIPLIER, coordinate)
|
||||
}
|
||||
}
|
||||
|
||||
zoomAtDataIndex (scale: number, dataIndex: number, animationDuration?: number): void {
|
||||
const x = this._chartStore.getTimeScaleStore().dataIndexToCoordinate(dataIndex)
|
||||
const x = this._chartStore.dataIndexToCoordinate(dataIndex)
|
||||
this.zoomAtCoordinate(scale, { x, y: 0 }, animationDuration)
|
||||
}
|
||||
|
||||
@ -966,7 +932,6 @@ export default class ChartImp implements Chart {
|
||||
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<Partial<Point>>().concat(points)
|
||||
const xAxis = this._xAxisPane.getAxisComponent()
|
||||
@ -975,7 +940,7 @@ export default class ChartImp implements Chart {
|
||||
const coordinate: Partial<Coordinate> = {}
|
||||
let dataIndex = point.dataIndex
|
||||
if (isNumber(point.timestamp)) {
|
||||
dataIndex = timeScaleStore.timestampToDataIndex(point.timestamp)
|
||||
dataIndex = this._chartStore.timestampToDataIndex(point.timestamp)
|
||||
}
|
||||
if (isNumber(dataIndex)) {
|
||||
coordinate.x = xAxis?.convertToPixel(dataIndex)
|
||||
@ -997,7 +962,6 @@ export default class ChartImp implements Chart {
|
||||
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<Partial<Coordinate>>().concat(coordinates)
|
||||
const xAxis = this._xAxisPane.getAxisComponent()
|
||||
@ -1007,7 +971,7 @@ export default class ChartImp implements Chart {
|
||||
if (isNumber(coordinate.x)) {
|
||||
const dataIndex = xAxis?.convertFromPixel(coordinate.x) ?? -1
|
||||
point.dataIndex = dataIndex
|
||||
point.timestamp = timeScaleStore.dataIndexToTimestamp(dataIndex) ?? undefined
|
||||
point.timestamp = this._chartStore.dataIndexToTimestamp(dataIndex) ?? undefined
|
||||
}
|
||||
if (isNumber(coordinate.y)) {
|
||||
const y = absolute ? coordinate.y - bounding.top : coordinate.y
|
||||
|
44
src/Event.ts
44
src/Event.ts
@ -71,23 +71,23 @@ export default class Event implements EventHandler {
|
||||
if (event.shiftKey) {
|
||||
switch (event.code) {
|
||||
case 'Equal': {
|
||||
this._chart.getChartStore().getTimeScaleStore().zoom(0.5)
|
||||
this._chart.getChartStore().zoom(0.5)
|
||||
break
|
||||
}
|
||||
case 'Minus': {
|
||||
this._chart.getChartStore().getTimeScaleStore().zoom(-0.5)
|
||||
this._chart.getChartStore().zoom(-0.5)
|
||||
break
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
const timeScaleStore = this._chart.getChartStore().getTimeScaleStore()
|
||||
timeScaleStore.startScroll()
|
||||
timeScaleStore.scroll(-3 * timeScaleStore.getBarSpace().bar)
|
||||
const store = this._chart.getChartStore()
|
||||
store.startScroll()
|
||||
store.scroll(-3 * store.getBarSpace().bar)
|
||||
break
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
const timeScaleStore = this._chart.getChartStore().getTimeScaleStore()
|
||||
timeScaleStore.startScroll()
|
||||
timeScaleStore.scroll(3 * timeScaleStore.getBarSpace().bar)
|
||||
const store = this._chart.getChartStore()
|
||||
store.startScroll()
|
||||
store.scroll(3 * store.getBarSpace().bar)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
@ -119,16 +119,16 @@ export default class Event implements EventHandler {
|
||||
const event = this._makeWidgetEvent(e, widget)
|
||||
const zoomScale = (scale - this._pinchScale) * 5
|
||||
this._pinchScale = scale
|
||||
this._chart.getChartStore().getTimeScaleStore().zoom(zoomScale, { x: event.x, y: event.y })
|
||||
this._chart.getChartStore().zoom(zoomScale, { x: event.x, y: event.y })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
mouseWheelHortEvent (_: MouseTouchEvent, distance: number): boolean {
|
||||
const timeScaleStore = this._chart.getChartStore().getTimeScaleStore()
|
||||
timeScaleStore.startScroll()
|
||||
timeScaleStore.scroll(distance)
|
||||
const store = this._chart.getChartStore()
|
||||
store.startScroll()
|
||||
store.scroll(distance)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ export default class Event implements EventHandler {
|
||||
const event = this._makeWidgetEvent(e, widget)
|
||||
const name = widget?.getName()
|
||||
if (name === WidgetNameConstants.MAIN) {
|
||||
this._chart.getChartStore().getTimeScaleStore().zoom(scale, { x: event.x, y: event.y })
|
||||
this._chart.getChartStore().zoom(scale, { x: event.x, y: event.y })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -157,7 +157,7 @@ export default class Event implements EventHandler {
|
||||
const range = (pane as DrawPane<YAxis>).getAxisComponent().getRange() ?? null
|
||||
this._prevYAxisRange = range === null ? range : { ...range }
|
||||
this._startScrollCoordinate = { x: event.x, y: event.y }
|
||||
this._chart.getChartStore().getTimeScaleStore().startScroll()
|
||||
this._chart.getChartStore().startScroll()
|
||||
return widget.dispatchEvent('mouseDownEvent', event)
|
||||
}
|
||||
case WidgetNameConstants.X_AXIS: {
|
||||
@ -270,7 +270,7 @@ export default class Event implements EventHandler {
|
||||
})
|
||||
}
|
||||
const distance = event.x - this._startScrollCoordinate.x
|
||||
this._chart.getChartStore().getTimeScaleStore().scroll(distance)
|
||||
this._chart.getChartStore().scroll(distance)
|
||||
}
|
||||
this._chart.getChartStore().getTooltipStore().setCrosshair({ x: event.x, y: event.y, paneId: pane?.getId() })
|
||||
return consumed
|
||||
@ -284,7 +284,7 @@ export default class Event implements EventHandler {
|
||||
if (Number.isFinite(scale)) {
|
||||
const zoomScale = (scale - this._xAxisScale) * 10
|
||||
this._xAxisScale = scale
|
||||
this._chart.getChartStore().getTimeScaleStore().zoom(zoomScale, this._xAxisStartScaleCoordinate ?? undefined)
|
||||
this._chart.getChartStore().zoom(zoomScale, this._xAxisStartScaleCoordinate ?? undefined)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -439,7 +439,7 @@ export default class Event implements EventHandler {
|
||||
}
|
||||
this._flingStartTime = new Date().getTime()
|
||||
this._startScrollCoordinate = { x: event.x, y: event.y }
|
||||
chartStore.getTimeScaleStore().startScroll()
|
||||
chartStore.startScroll()
|
||||
this._touchZoomed = false
|
||||
if (this._touchCoordinate !== null) {
|
||||
const xDif = event.x - this._touchCoordinate.x
|
||||
@ -493,7 +493,7 @@ export default class Event implements EventHandler {
|
||||
Math.abs(this._startScrollCoordinate.x - event.x) > this._startScrollCoordinate.y - event.y
|
||||
) {
|
||||
const distance = event.x - this._startScrollCoordinate.x
|
||||
chartStore.getTimeScaleStore().scroll(distance)
|
||||
chartStore.scroll(distance)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -525,11 +525,11 @@ export default class Event implements EventHandler {
|
||||
const distance = event.x - this._startScrollCoordinate.x
|
||||
let v = distance / (time > 0 ? time : 1) * 20
|
||||
if (time < 200 && Math.abs(v) > 0) {
|
||||
const timeScaleStore = this._chart.getChartStore().getTimeScaleStore()
|
||||
const store = this._chart.getChartStore()
|
||||
const flingScroll: (() => void) = () => {
|
||||
this._flingScrollRequestId = requestAnimationFrame(() => {
|
||||
timeScaleStore.startScroll()
|
||||
timeScaleStore.scroll(v)
|
||||
store.startScroll()
|
||||
store.scroll(v)
|
||||
v = v * (1 - 0.025)
|
||||
if (Math.abs(v) < 1) {
|
||||
if (this._flingScrollRequestId !== null) {
|
||||
@ -607,7 +607,7 @@ export default class Event implements EventHandler {
|
||||
private _findWidgetByEvent (event: MouseTouchEvent): EventTriggerWidgetInfo {
|
||||
const { x, y } = event
|
||||
const separatorPanes = this._chart.getSeparatorPanes()
|
||||
const separatorSize = this._chart.getChartStore().getStyles().separator.size
|
||||
const separatorSize = this._chart.getChartStore().getOptions().styles.separator.size
|
||||
for (const [, pane] of separatorPanes) {
|
||||
const bounding = pane.getBounding()
|
||||
const top = bounding.top - Math.round((REAL_SEPARATOR_HEIGHT - separatorSize) / 2)
|
||||
|
@ -24,10 +24,9 @@ import type { OverlayStyle } from '../common/Styles'
|
||||
import type { MouseTouchEvent } from '../common/SyntheticEvent'
|
||||
import { clone, isArray, isFunction, isNumber, isString, isValid, merge } from '../common/utils/typeChecks'
|
||||
|
||||
import type TimeScaleStore from '../store/TimeScaleStore'
|
||||
|
||||
import type { XAxis } from './XAxis'
|
||||
import type { YAxis } from './YAxis'
|
||||
import type ChartStore from '../store/ChartStore'
|
||||
|
||||
export enum OverlayMode {
|
||||
Normal = 'normal',
|
||||
@ -479,7 +478,7 @@ export default class OverlayImp implements Overlay {
|
||||
this._prevPressedPoints = clone(this.points)
|
||||
}
|
||||
|
||||
eventPressedOtherMove (point: Partial<Point>, timeScaleStore: TimeScaleStore): void {
|
||||
eventPressedOtherMove (point: Partial<Point>, chartStore: ChartStore): void {
|
||||
if (this._prevPressedPoint !== null) {
|
||||
let difDataIndex: Nullable<number> = null
|
||||
if (isNumber(point.dataIndex) && isNumber(this._prevPressedPoint.dataIndex)) {
|
||||
@ -491,12 +490,12 @@ export default class OverlayImp implements Overlay {
|
||||
}
|
||||
this.points = this._prevPressedPoints.map(p => {
|
||||
if (isNumber(p.timestamp)) {
|
||||
p.dataIndex = timeScaleStore.timestampToDataIndex(p.timestamp)
|
||||
p.dataIndex = chartStore.timestampToDataIndex(p.timestamp)
|
||||
}
|
||||
const newPoint = { ...p }
|
||||
if (isNumber(difDataIndex) && isNumber(p.dataIndex)) {
|
||||
newPoint.dataIndex = p.dataIndex + difDataIndex
|
||||
newPoint.timestamp = timeScaleStore.dataIndexToTimestamp(newPoint.dataIndex) ?? undefined
|
||||
newPoint.timestamp = chartStore.dataIndexToTimestamp(newPoint.dataIndex) ?? undefined
|
||||
}
|
||||
if (isNumber(difValue) && isNumber(p.value)) {
|
||||
newPoint.value = p.value + difValue
|
||||
|
@ -19,7 +19,7 @@ import { isFunction, isString } from '../common/utils/typeChecks'
|
||||
import AxisImp, { type AxisTemplate, type Axis, type AxisRange, type AxisTick } from './Axis'
|
||||
|
||||
import type DrawPane from '../pane/DrawPane'
|
||||
import { TimeWeightConstants } from '../store/TimeScaleStore'
|
||||
import { TimeWeightConstants } from '../store/ChartStore'
|
||||
import { FormatDateType } from '../Options'
|
||||
|
||||
export type XAxisTemplate = Pick<AxisTemplate, 'name' | 'scrollZoomEnabled' | 'createTicks'>
|
||||
@ -52,7 +52,7 @@ export default abstract class XAxisImp extends AxisImp implements XAxis {
|
||||
|
||||
protected override createRangeImp (): AxisRange {
|
||||
const chartStore = this.getParent().getChart().getChartStore()
|
||||
const visibleDataRange = chartStore.getTimeScaleStore().getVisibleRange()
|
||||
const visibleDataRange = chartStore.getVisibleRange()
|
||||
const { from, to } = visibleDataRange
|
||||
const af = from
|
||||
const at = to - 1
|
||||
@ -73,10 +73,9 @@ export default abstract class XAxisImp extends AxisImp implements XAxis {
|
||||
|
||||
protected override createTicksImp (): AxisTick[] {
|
||||
const chartStore = this.getParent().getChart().getChartStore()
|
||||
const timeScaleStore = chartStore.getTimeScaleStore()
|
||||
const formatDate = chartStore.getCustomApi().formatDate
|
||||
const timeTickList = timeScaleStore.getVisibleRangeTimeTickList()
|
||||
const dateTimeFormat = timeScaleStore.getDateTimeFormat()
|
||||
const formatDate = chartStore.getOptions().customApi.formatDate
|
||||
const timeTickList = chartStore.getVisibleRangeTimeTickList()
|
||||
const dateTimeFormat = chartStore.getDateTimeFormat()
|
||||
const ticks = timeTickList.map(({ dataIndex, weight, timestamp }) => {
|
||||
let text = ''
|
||||
switch (weight) {
|
||||
@ -119,7 +118,7 @@ export default abstract class XAxisImp extends AxisImp implements XAxis {
|
||||
}
|
||||
|
||||
override getAutoSize (): number {
|
||||
const styles = this.getParent().getChart().getStyles()
|
||||
const styles = this.getParent().getChart().getOptions().styles
|
||||
const xAxisStyles = styles.xAxis
|
||||
const height = xAxisStyles.size
|
||||
if (height !== 'auto') {
|
||||
@ -159,23 +158,23 @@ export default abstract class XAxisImp extends AxisImp implements XAxis {
|
||||
}
|
||||
|
||||
convertTimestampFromPixel (pixel: number): Nullable<number> {
|
||||
const timeScaleStore = this.getParent().getChart().getChartStore().getTimeScaleStore()
|
||||
const dataIndex = timeScaleStore.coordinateToDataIndex(pixel)
|
||||
return timeScaleStore.dataIndexToTimestamp(dataIndex)
|
||||
const chartStore = this.getParent().getChart().getChartStore()
|
||||
const dataIndex = chartStore.coordinateToDataIndex(pixel)
|
||||
return chartStore.dataIndexToTimestamp(dataIndex)
|
||||
}
|
||||
|
||||
convertTimestampToPixel (timestamp: number): number {
|
||||
const timeScaleStore = this.getParent().getChart().getChartStore().getTimeScaleStore()
|
||||
const dataIndex = timeScaleStore.timestampToDataIndex(timestamp)
|
||||
return timeScaleStore.dataIndexToCoordinate(dataIndex)
|
||||
const chartStore = this.getParent().getChart().getChartStore()
|
||||
const dataIndex = chartStore.timestampToDataIndex(timestamp)
|
||||
return chartStore.dataIndexToCoordinate(dataIndex)
|
||||
}
|
||||
|
||||
convertFromPixel (pixel: number): number {
|
||||
return this.getParent().getChart().getChartStore().getTimeScaleStore().coordinateToDataIndex(pixel)
|
||||
return this.getParent().getChart().getChartStore().coordinateToDataIndex(pixel)
|
||||
}
|
||||
|
||||
convertToPixel (value: number): number {
|
||||
return this.getParent().getChart().getChartStore().getTimeScaleStore().dataIndexToCoordinate(value)
|
||||
return this.getParent().getChart().getChartStore().dataIndexToCoordinate(value)
|
||||
}
|
||||
|
||||
static extend (template: XAxisTemplate): XAxisConstructor {
|
||||
|
@ -117,7 +117,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
|
||||
}
|
||||
}
|
||||
const visibleRangeDataList = chartStore.getVisibleRangeDataList()
|
||||
const candleStyles = chart.getStyles().candle
|
||||
const candleStyles = chart.getOptions().styles.candle
|
||||
const isArea = candleStyles.type === CandleType.Area
|
||||
const areaValueKey = candleStyles.area.value
|
||||
const shouldCompareHighLow = (inCandle && !isArea) || (!inCandle && shouldOhlc)
|
||||
@ -170,7 +170,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
|
||||
const range = this.createRange?.({
|
||||
paneId,
|
||||
kLineDataList: chartStore.getDataList(),
|
||||
dataVisibleRange: chartStore.getTimeScaleStore().getVisibleRange(),
|
||||
dataVisibleRange: chartStore.getVisibleRange(),
|
||||
indicators,
|
||||
defaultRange
|
||||
})
|
||||
@ -265,11 +265,9 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
|
||||
const pane = this.getParent()
|
||||
const height = pane.getYAxisWidget()?.getBounding().height ?? 0
|
||||
const chartStore = pane.getChart().getChartStore()
|
||||
const customApi = chartStore.getCustomApi()
|
||||
const optimalTicks: AxisTick[] = []
|
||||
const indicators = chartStore.getIndicatorStore().getInstanceByPaneId(pane.getId())
|
||||
const thousandsSeparator = chartStore.getThousandsSeparator()
|
||||
const decimalFoldThreshold = chartStore.getDecimalFoldThreshold()
|
||||
const { styles, customApi, thousandsSeparator, decimalFoldThreshold } = chartStore.getOptions()
|
||||
let precision = 0
|
||||
let shouldFormatBigNumber = false
|
||||
if (this.isInCandle()) {
|
||||
@ -282,7 +280,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
|
||||
}
|
||||
})
|
||||
}
|
||||
const textHeight = chartStore.getStyles().xAxis.tickText.size
|
||||
const textHeight = styles.xAxis.tickText.size
|
||||
let validY = NaN
|
||||
ticks.forEach(({ value }) => {
|
||||
let v = this.displayValueToText(+value, precision)
|
||||
@ -311,14 +309,15 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
|
||||
override getAutoSize (): number {
|
||||
const pane = this.getParent()
|
||||
const chart = pane.getChart()
|
||||
const styles = chart.getStyles()
|
||||
const chartOptions = chart.getOptions()
|
||||
const styles = chartOptions.styles
|
||||
const yAxisStyles = styles.yAxis
|
||||
const width = yAxisStyles.size
|
||||
if (width !== 'auto') {
|
||||
return width
|
||||
}
|
||||
const chartStore = chart.getChartStore()
|
||||
const customApi = chartStore.getCustomApi()
|
||||
const customApi = chartOptions.customApi
|
||||
let yAxisWidth = 0
|
||||
if (yAxisStyles.show) {
|
||||
if (yAxisStyles.axisLine.show) {
|
||||
@ -367,7 +366,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
|
||||
if (shouldFormatBigNumber) {
|
||||
valueText = customApi.formatBigNumber(valueText)
|
||||
}
|
||||
valueText = formatFoldDecimal(valueText, chartStore.getDecimalFoldThreshold())
|
||||
valueText = formatFoldDecimal(valueText, chartStore.getOptions().decimalFoldThreshold)
|
||||
crosshairVerticalTextWidth += (
|
||||
crosshairStyles.horizontal.text.paddingLeft +
|
||||
crosshairStyles.horizontal.text.paddingRight +
|
||||
|
@ -68,7 +68,7 @@ export default class SeparatorPane extends Pane {
|
||||
override getImage (_includeOverlay: boolean): HTMLCanvasElement {
|
||||
const { width, height } = this.getBounding()
|
||||
|
||||
const styles = this.getChart().getStyles().separator
|
||||
const styles = this.getChart().getOptions().styles.separator
|
||||
const canvas = createDom('canvas', {
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
@ -86,7 +86,7 @@ export default class SeparatorPane extends Pane {
|
||||
|
||||
override updateImp (level: UpdateLevel, container: HTMLElement, bounding: Bounding): void {
|
||||
if (level === UpdateLevel.All || level === UpdateLevel.Separator) {
|
||||
const styles = this.getChart().getStyles().separator
|
||||
const styles = this.getChart().getOptions().styles.separator
|
||||
container.style.backgroundColor = styles.color
|
||||
container.style.height = `${bounding.height}px`
|
||||
container.style.marginLeft = `${bounding.left}px`
|
||||
|
@ -13,17 +13,24 @@
|
||||
*/
|
||||
|
||||
import type Nullable from '../common/Nullable'
|
||||
import type { KLineData, VisibleRangeData } from '../common/Data'
|
||||
import type Precision from '../common/Precision'
|
||||
import type DeepPartial from '../common/DeepPartial'
|
||||
import { formatValue } from '../common/utils/format'
|
||||
import type { KLineData, VisibleRangeData } from '../common/Data'
|
||||
import type VisibleRange from '../common/VisibleRange';
|
||||
import type Coordinate from '../common/Coordinate';
|
||||
import { getDefaultVisibleRange } from '../common/VisibleRange'
|
||||
import type BarSpace from '../common/BarSpace';
|
||||
import type Precision from '../common/Precision'
|
||||
import { ActionType } from '../common/Action';
|
||||
import { formatValue, type DateTime, formatDateToDateTime } from '../common/utils/format'
|
||||
import { getDefaultStyles, type Styles, type TooltipLegend } from '../common/Styles'
|
||||
import { isArray, isNumber, isString, isValid, merge } from '../common/utils/typeChecks'
|
||||
import { isArray, isString, isValid, isNumber, merge } from '../common/utils/typeChecks'
|
||||
import { binarySearchNearest } from '../common/utils/number'
|
||||
import { logWarn } from '../common/utils/logger'
|
||||
import { calcTextWidth } from '../common/utils/canvas';
|
||||
import { type LoadDataCallback, type LoadDataParams, LoadDataType } from '../common/LoadDataCallback'
|
||||
|
||||
import { getDefaultCustomApi, type CustomApi, defaultLocale, type Options } from '../Options'
|
||||
|
||||
import TimeScaleStore from './TimeScaleStore'
|
||||
import IndicatorStore from './IndicatorStore'
|
||||
import TooltipStore from './TooltipStore'
|
||||
import OverlayStore from './OverlayStore'
|
||||
@ -32,6 +39,41 @@ import ActionStore from './ActionStore'
|
||||
import { getStyles } from '../extension/styles/index'
|
||||
|
||||
import type Chart from '../Chart'
|
||||
|
||||
export interface TimeTick {
|
||||
weight: number
|
||||
dataIndex: number
|
||||
dateTime: DateTime
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export const TimeWeightConstants = {
|
||||
Year: 365 * 24 * 3600,
|
||||
Month: 30 * 24 * 3600,
|
||||
Day: 24 * 3600,
|
||||
Hour: 3600,
|
||||
Minute: 60,
|
||||
Second: 1
|
||||
}
|
||||
|
||||
const BarSpaceLimitConstants = {
|
||||
MIN: 1,
|
||||
MAX: 50
|
||||
}
|
||||
|
||||
const enum ScrollLimitRole {
|
||||
BarCount,
|
||||
Distance
|
||||
}
|
||||
|
||||
const DEFAULT_BAR_SPACE = 10
|
||||
|
||||
const DEFAULT_OFFSET_RIGHT_DISTANCE = 80
|
||||
|
||||
const BAR_GAP_RATIO = 0.2
|
||||
|
||||
export const SCALE_MULTIPLIER = 10
|
||||
|
||||
export default class ChartStore {
|
||||
/**
|
||||
* Internal chart
|
||||
@ -39,35 +81,22 @@ export default class ChartStore {
|
||||
private readonly _chart: Chart
|
||||
|
||||
/**
|
||||
* Style config
|
||||
* Chart options
|
||||
*/
|
||||
private readonly _styles = getDefaultStyles()
|
||||
|
||||
/**
|
||||
* Custom api
|
||||
*/
|
||||
private readonly _customApi = getDefaultCustomApi()
|
||||
|
||||
/**
|
||||
* language
|
||||
*/
|
||||
private _locale = defaultLocale
|
||||
private readonly _options = {
|
||||
styles: getDefaultStyles(),
|
||||
customApi: getDefaultCustomApi(),
|
||||
locale: defaultLocale,
|
||||
thousandsSeparator: ',',
|
||||
decimalFoldThreshold: 3,
|
||||
timezone: 'auto'
|
||||
}
|
||||
|
||||
/**
|
||||
* Price and volume precision
|
||||
*/
|
||||
private _precision = { price: 2, volume: 0 }
|
||||
|
||||
/**
|
||||
* Thousands separator
|
||||
*/
|
||||
private _thousandsSeparator = ','
|
||||
|
||||
/**
|
||||
* Decimal fold threshold
|
||||
*/
|
||||
private _decimalFoldThreshold = 3
|
||||
|
||||
/**
|
||||
* Data source
|
||||
*/
|
||||
@ -89,9 +118,75 @@ export default class ChartStore {
|
||||
private readonly _loadDataMore = { forward: false, backward: false }
|
||||
|
||||
/**
|
||||
* Time scale store
|
||||
* Time format
|
||||
*/
|
||||
private _dateTimeFormat: Intl.DateTimeFormat
|
||||
|
||||
/**
|
||||
* Scale enabled flag
|
||||
*/
|
||||
private readonly _timeScaleStore = new TimeScaleStore(this)
|
||||
private _zoomEnabled = true
|
||||
|
||||
/**
|
||||
* Scroll enabled flag
|
||||
*/
|
||||
private _scrollEnabled = true
|
||||
|
||||
/**
|
||||
* Total space of drawing area
|
||||
*/
|
||||
private _totalBarSpace = 0
|
||||
|
||||
/**
|
||||
* Space occupied by a single piece of data
|
||||
*/
|
||||
private _barSpace = DEFAULT_BAR_SPACE
|
||||
|
||||
/**
|
||||
* The space of the draw bar
|
||||
*/
|
||||
private _gapBarSpace: number
|
||||
|
||||
/**
|
||||
* Distance from the last data to the right of the drawing area
|
||||
*/
|
||||
private _offsetRightDistance = DEFAULT_OFFSET_RIGHT_DISTANCE
|
||||
|
||||
/**
|
||||
* The number of bar calculated from the distance of the last data to the right of the drawing area
|
||||
*/
|
||||
private _lastBarRightSideDiffBarCount: number
|
||||
|
||||
/**
|
||||
* The number of bar to the right of the drawing area from the last data when scrolling starts
|
||||
*/
|
||||
private _startLastBarRightSideDiffBarCount = 0
|
||||
|
||||
/**
|
||||
* Scroll limit role
|
||||
*/
|
||||
private _scrollLimitRole = ScrollLimitRole.BarCount
|
||||
|
||||
/**
|
||||
* Scroll to the leftmost and rightmost visible bar
|
||||
*/
|
||||
private readonly _minVisibleBarCount = { left: 2, right: 2 }
|
||||
|
||||
/**
|
||||
* Scroll to the leftmost and rightmost distance
|
||||
*/
|
||||
private readonly _maxOffsetDistance = { left: 50, right: 50 }
|
||||
|
||||
/**
|
||||
* Start and end points of visible area data index
|
||||
*/
|
||||
private _visibleRange = getDefaultVisibleRange()
|
||||
|
||||
private _cacheVisibleRange = getDefaultVisibleRange()
|
||||
|
||||
private readonly _timeTicks = new Map<number, TimeTick[]>()
|
||||
|
||||
private _visibleRangeTimeTickList: TimeTick[] = []
|
||||
|
||||
/**
|
||||
* Indicator store
|
||||
@ -128,92 +223,58 @@ export default class ChartStore {
|
||||
|
||||
constructor (chart: Chart, options?: Options) {
|
||||
this._chart = chart
|
||||
this._calcOptimalBarSpace()
|
||||
this._lastBarRightSideDiffBarCount = this._offsetRightDistance / this._barSpace
|
||||
this.setOptions(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Adjust visible data
|
||||
* @return {*}
|
||||
*/
|
||||
adjustVisibleRangeDataList (): void {
|
||||
this._visibleRangeDataList = []
|
||||
this._visibleRangeHighLowPrice = [
|
||||
{ x: 0, price: Number.MIN_SAFE_INTEGER },
|
||||
{ x: 0, price: Number.MAX_SAFE_INTEGER },
|
||||
]
|
||||
const { realFrom, realTo } = this._timeScaleStore.getVisibleRange()
|
||||
for (let i = realFrom; i < realTo; i++) {
|
||||
const kLineData = this._dataList[i]
|
||||
const x = this._timeScaleStore.dataIndexToCoordinate(i)
|
||||
this._visibleRangeDataList.push({
|
||||
dataIndex: i,
|
||||
x,
|
||||
data: kLineData
|
||||
})
|
||||
if (this._visibleRangeHighLowPrice[0].price < kLineData.high) {
|
||||
this._visibleRangeHighLowPrice[0].price = kLineData.high
|
||||
this._visibleRangeHighLowPrice[0].x = x
|
||||
setOptions (options?: Options): void {
|
||||
if (
|
||||
!isValid(this._dateTimeFormat) ||
|
||||
(isString(options?.timezone) && options.timezone !== this._options.timezone)
|
||||
) {
|
||||
const dateTimeFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
}
|
||||
if (this._visibleRangeHighLowPrice[1].price > kLineData.low) {
|
||||
this._visibleRangeHighLowPrice[1].price = kLineData.low
|
||||
this._visibleRangeHighLowPrice[1].x = x
|
||||
if (isString(options?.timezone) && options.timezone !== 'auto') {
|
||||
dateTimeFormatOptions.timeZone = options.timezone
|
||||
}
|
||||
let dateTimeFormat: Nullable<Intl.DateTimeFormat> = null
|
||||
try {
|
||||
dateTimeFormat = new Intl.DateTimeFormat('en', dateTimeFormatOptions)
|
||||
} catch (e) {
|
||||
logWarn('', '', 'Timezone is error!!!')
|
||||
}
|
||||
if (dateTimeFormat !== null) {
|
||||
this._classifyTimeTicks(this._dataList)
|
||||
this._adjustVisibleRangeTimeTickList()
|
||||
this._dateTimeFormat = dateTimeFormat
|
||||
}
|
||||
}
|
||||
merge(this._options, options)
|
||||
const styles = options?.styles
|
||||
if (isValid(styles)) {
|
||||
let ss: Nullable<DeepPartial<Styles>> = null
|
||||
if (isString(styles)) {
|
||||
ss = getStyles(styles)
|
||||
} else {
|
||||
ss = styles
|
||||
}
|
||||
// `candle.tooltip.custom` should override
|
||||
if (isArray(ss?.candle?.tooltip?.custom)) {
|
||||
this._options.styles.candle.tooltip.custom = ss.candle.tooltip.custom as TooltipLegend[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOptions (options?: Options): this {
|
||||
if (isValid(options)) {
|
||||
const { locale, timezone, styles, customApi, thousandsSeparator, decimalFoldThreshold } = options
|
||||
if (isString(locale)) {
|
||||
this._locale = locale
|
||||
}
|
||||
if (isString(timezone)) {
|
||||
this._timeScaleStore.setTimezone(timezone)
|
||||
}
|
||||
if (isValid(styles)) {
|
||||
let ss: Nullable<DeepPartial<Styles>> = null
|
||||
if (isString(styles)) {
|
||||
ss = getStyles(styles)
|
||||
} else {
|
||||
ss = styles
|
||||
}
|
||||
merge(this._styles, ss)
|
||||
// `candle.tooltip.custom` should override
|
||||
if (isArray(ss?.candle?.tooltip?.custom)) {
|
||||
this._styles.candle.tooltip.custom = ss.candle.tooltip.custom as unknown as TooltipLegend[]
|
||||
}
|
||||
}
|
||||
if (isValid(customApi)) {
|
||||
merge(this._customApi, customApi)
|
||||
}
|
||||
if (isString(thousandsSeparator)) {
|
||||
this._thousandsSeparator = thousandsSeparator
|
||||
}
|
||||
if (isNumber(decimalFoldThreshold) && decimalFoldThreshold > 0) {
|
||||
this._decimalFoldThreshold = decimalFoldThreshold
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
getStyles (): Styles {
|
||||
return this._styles
|
||||
}
|
||||
|
||||
getLocale (): string {
|
||||
return this._locale
|
||||
}
|
||||
|
||||
getCustomApi (): CustomApi {
|
||||
return this._customApi
|
||||
}
|
||||
|
||||
getThousandsSeparator (): string {
|
||||
return this._thousandsSeparator
|
||||
}
|
||||
|
||||
getDecimalFoldThreshold (): number {
|
||||
return this._decimalFoldThreshold
|
||||
getOptions (): Required<Omit<Options, 'layout'>> & { customApi: CustomApi, styles: Styles } {
|
||||
return this._options
|
||||
}
|
||||
|
||||
getPrecision (): Precision {
|
||||
@ -254,13 +315,13 @@ export default class ChartStore {
|
||||
this._dataList = data
|
||||
this._loadDataMore.backward = more?.forward ?? false
|
||||
this._loadDataMore.forward = more?.forward ?? false
|
||||
this._timeScaleStore.classifyTimeTicks(this._dataList)
|
||||
this._timeScaleStore.resetOffsetRightDistance()
|
||||
this._classifyTimeTicks(this._dataList)
|
||||
this.setOffsetRightDistance(this._offsetRightDistance)
|
||||
adjustFlag = true
|
||||
break
|
||||
}
|
||||
case LoadDataType.Backward: {
|
||||
this._timeScaleStore.classifyTimeTicks(data, true)
|
||||
this._classifyTimeTicks(data, true)
|
||||
this._dataList = this._dataList.concat(data)
|
||||
this._loadDataMore.backward = more?.backward ?? false
|
||||
adjustFlag = dataLengthChange > 0
|
||||
@ -268,7 +329,7 @@ export default class ChartStore {
|
||||
}
|
||||
case LoadDataType.Forward: {
|
||||
this._dataList = data.concat(this._dataList)
|
||||
this._timeScaleStore.classifyTimeTicks(this._dataList)
|
||||
this._classifyTimeTicks(this._dataList)
|
||||
this._loadDataMore.forward = more?.forward ?? false
|
||||
adjustFlag = dataLengthChange > 0
|
||||
}
|
||||
@ -281,11 +342,11 @@ export default class ChartStore {
|
||||
const timestamp = data.timestamp
|
||||
const lastDataTimestamp = formatValue(this._dataList[dataCount - 1], 'timestamp', 0) as number
|
||||
if (timestamp > lastDataTimestamp) {
|
||||
this._timeScaleStore.classifyTimeTicks([data], true)
|
||||
this._classifyTimeTicks([data], true)
|
||||
this._dataList.push(data)
|
||||
let lastBarRightSideDiffBarCount = this._timeScaleStore.getLastBarRightSideDiffBarCount()
|
||||
let lastBarRightSideDiffBarCount = this.getLastBarRightSideDiffBarCount()
|
||||
if (lastBarRightSideDiffBarCount < 0) {
|
||||
this._timeScaleStore.setLastBarRightSideDiffBarCount(--lastBarRightSideDiffBarCount)
|
||||
this.setLastBarRightSideDiffBarCount(--lastBarRightSideDiffBarCount)
|
||||
}
|
||||
dataLengthChange = 1
|
||||
success = true
|
||||
@ -299,7 +360,7 @@ export default class ChartStore {
|
||||
if (success) {
|
||||
this._overlayStore.updatePointPosition(dataLengthChange, type)
|
||||
if (adjustFlag) {
|
||||
this._timeScaleStore.adjustVisibleRange()
|
||||
this._adjustVisibleRange()
|
||||
this._tooltipStore.recalculateCrosshair(true)
|
||||
this._indicatorStore.calcInstance(type, {})
|
||||
this._chart.adjustPaneViewport(false, true, true, true)
|
||||
@ -311,21 +372,399 @@ export default class ChartStore {
|
||||
this._loadMoreDataCallback = callback
|
||||
}
|
||||
|
||||
executeLoadMoreDataCallback (params: Omit<LoadDataParams, 'callback'>): void {
|
||||
if (
|
||||
!this._loading &&
|
||||
isValid(this._loadMoreDataCallback) &&
|
||||
(
|
||||
(this._loadDataMore.forward && params.type === LoadDataType.Forward) ||
|
||||
(this._loadDataMore.backward && params.type === LoadDataType.Backward)
|
||||
)
|
||||
) {
|
||||
const cb: ((data: KLineData[], more?: boolean) => void) = (data: KLineData[], more?: boolean) => {
|
||||
this.addData(data, params.type, { forward: more ?? false, backward: more ?? false })
|
||||
}
|
||||
this._loading = true
|
||||
this._loadMoreDataCallback({ ...params, callback: cb })
|
||||
private _calcOptimalBarSpace (): void {
|
||||
const specialBarSpace = 4
|
||||
const ratio = 1 - BAR_GAP_RATIO * Math.atan(Math.max(specialBarSpace, this._barSpace) - specialBarSpace) / (Math.PI * 0.5)
|
||||
let gapBarSpace = Math.min(Math.floor(this._barSpace * ratio), Math.floor(this._barSpace))
|
||||
if (gapBarSpace % 2 === 0 && gapBarSpace + 2 >= this._barSpace) {
|
||||
--gapBarSpace
|
||||
}
|
||||
this._gapBarSpace = Math.max(1, gapBarSpace)
|
||||
}
|
||||
|
||||
private _classifyTimeTicks (newDataList: KLineData[], isUpdate?: boolean): void {
|
||||
let baseDataIndex = 0
|
||||
let prevKLineData: Nullable<KLineData> = null
|
||||
if (isUpdate ?? false) {
|
||||
baseDataIndex = this._dataList.length
|
||||
prevKLineData = this._dataList[baseDataIndex - 1]
|
||||
} else {
|
||||
this._timeTicks.clear()
|
||||
}
|
||||
|
||||
for (let i = 0; i < newDataList.length; i++) {
|
||||
const kLineData = newDataList[i]
|
||||
let weight = TimeWeightConstants.Second
|
||||
const dateTime = formatDateToDateTime(this._dateTimeFormat, kLineData.timestamp)
|
||||
if (isValid(prevKLineData)) {
|
||||
const prevDateTime = formatDateToDateTime(this._dateTimeFormat, prevKLineData.timestamp)
|
||||
if (dateTime.YYYY !== prevDateTime.YYYY) {
|
||||
weight = TimeWeightConstants.Year
|
||||
} else if (dateTime.MM !== prevDateTime.MM) {
|
||||
weight = TimeWeightConstants.Month
|
||||
} else if (dateTime.DD !== prevDateTime.DD) {
|
||||
weight = TimeWeightConstants.Day
|
||||
} else if (dateTime.HH !== prevDateTime.HH) {
|
||||
weight = TimeWeightConstants.Hour
|
||||
} else if (dateTime.mm !== prevDateTime.mm) {
|
||||
weight = TimeWeightConstants.Minute
|
||||
} else {
|
||||
weight = TimeWeightConstants.Second
|
||||
}
|
||||
}
|
||||
const tickList = this._timeTicks.get(weight) ?? []
|
||||
tickList.push({ dataIndex: i + baseDataIndex, weight, dateTime, timestamp: kLineData.timestamp })
|
||||
this._timeTicks.set(weight, tickList)
|
||||
prevKLineData = kLineData
|
||||
}
|
||||
}
|
||||
|
||||
private _adjustVisibleRangeTimeTickList (): void {
|
||||
const tickTextStyles = this._options.styles.xAxis.tickText
|
||||
const width = Math.max(
|
||||
Math.ceil(this._totalBarSpace / 10),
|
||||
calcTextWidth('0000-00-00 00:00', tickTextStyles.size, tickTextStyles.weight, tickTextStyles.family)
|
||||
)
|
||||
const barCount = Math.ceil(width / this._barSpace)
|
||||
let tickList: TimeTick[] = []
|
||||
Array.from(this._timeTicks.keys()).sort((w1, w2) => w2 - w1).forEach(key => {
|
||||
const prevTickList = tickList
|
||||
tickList = []
|
||||
|
||||
const prevTickListLength = prevTickList.length
|
||||
let prevTickListPointer = 0
|
||||
const currentTicks = this._timeTicks.get(key)!
|
||||
const currentTicksLength = currentTicks.length
|
||||
|
||||
let rightIndex = Infinity
|
||||
let leftIndex = -Infinity
|
||||
for (let i = 0; i < currentTicksLength; i++) {
|
||||
const tick = currentTicks[i]
|
||||
const currentIndex = tick.dataIndex
|
||||
|
||||
while (prevTickListPointer < prevTickListLength) {
|
||||
const lastMark = prevTickList[prevTickListPointer]
|
||||
const lastIndex = lastMark.dataIndex
|
||||
if (lastIndex < currentIndex) {
|
||||
prevTickListPointer++
|
||||
tickList.push(lastMark)
|
||||
leftIndex = lastIndex
|
||||
rightIndex = Infinity
|
||||
} else {
|
||||
rightIndex = lastIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rightIndex - currentIndex >= barCount && currentIndex - leftIndex >= barCount) {
|
||||
tickList.push(tick)
|
||||
leftIndex = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
for (; prevTickListPointer < prevTickListLength; prevTickListPointer++) {
|
||||
tickList.push(prevTickList[prevTickListPointer])
|
||||
}
|
||||
})
|
||||
this._visibleRangeTimeTickList = []
|
||||
for (let i = 0; i < tickList.length; i++) {
|
||||
const tick = tickList[i]
|
||||
if (tick.dataIndex >= this._visibleRange.from && tick.dataIndex <= this._visibleRange.to) {
|
||||
this._visibleRangeTimeTickList.push(tick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getVisibleRangeTimeTickList (): TimeTick[] {
|
||||
return this._visibleRangeTimeTickList
|
||||
}
|
||||
|
||||
private _adjustVisibleRange (): void {
|
||||
const totalBarCount = this._dataList.length
|
||||
const visibleBarCount = this._totalBarSpace / this._barSpace
|
||||
|
||||
let leftMinVisibleBarCount = 0
|
||||
let rightMinVisibleBarCount = 0
|
||||
|
||||
if (this._scrollLimitRole === ScrollLimitRole.Distance) {
|
||||
leftMinVisibleBarCount = (this._totalBarSpace - this._maxOffsetDistance.right) / this._barSpace
|
||||
rightMinVisibleBarCount = (this._totalBarSpace - this._maxOffsetDistance.left) / this._barSpace
|
||||
} else {
|
||||
leftMinVisibleBarCount = this._minVisibleBarCount.left
|
||||
rightMinVisibleBarCount = this._minVisibleBarCount.right
|
||||
}
|
||||
|
||||
leftMinVisibleBarCount = Math.max(0, leftMinVisibleBarCount)
|
||||
rightMinVisibleBarCount = Math.max(0, rightMinVisibleBarCount)
|
||||
|
||||
const maxRightOffsetBarCount = visibleBarCount - Math.min(leftMinVisibleBarCount, totalBarCount)
|
||||
if (this._lastBarRightSideDiffBarCount > maxRightOffsetBarCount) {
|
||||
this._lastBarRightSideDiffBarCount = maxRightOffsetBarCount
|
||||
}
|
||||
|
||||
const minRightOffsetBarCount = -totalBarCount + Math.min(rightMinVisibleBarCount, totalBarCount)
|
||||
if (this._lastBarRightSideDiffBarCount < minRightOffsetBarCount) {
|
||||
this._lastBarRightSideDiffBarCount = minRightOffsetBarCount
|
||||
}
|
||||
|
||||
let to = Math.round(this._lastBarRightSideDiffBarCount + totalBarCount + 0.5)
|
||||
const realTo = to
|
||||
if (to > totalBarCount) {
|
||||
to = totalBarCount
|
||||
}
|
||||
let from = Math.round(to - visibleBarCount) - 1
|
||||
if (from < 0) {
|
||||
from = 0
|
||||
}
|
||||
const realFrom = this._lastBarRightSideDiffBarCount > 0 ? Math.round(totalBarCount + this._lastBarRightSideDiffBarCount - visibleBarCount) - 1 : from
|
||||
this._visibleRange = { from, to, realFrom, realTo }
|
||||
this.getActionStore().execute(ActionType.OnVisibleRangeChange, this._visibleRange)
|
||||
this._visibleRangeDataList = []
|
||||
this._visibleRangeHighLowPrice = [
|
||||
{ x: 0, price: Number.MIN_SAFE_INTEGER },
|
||||
{ x: 0, price: Number.MAX_SAFE_INTEGER },
|
||||
]
|
||||
for (let i = realFrom; i < realTo; i++) {
|
||||
const kLineData = this._dataList[i]
|
||||
const x = this.dataIndexToCoordinate(i)
|
||||
this._visibleRangeDataList.push({
|
||||
dataIndex: i,
|
||||
x,
|
||||
data: kLineData
|
||||
})
|
||||
if (isValid(kLineData)) {
|
||||
if (this._visibleRangeHighLowPrice[0].price < kLineData.high) {
|
||||
this._visibleRangeHighLowPrice[0].price = kLineData.high
|
||||
this._visibleRangeHighLowPrice[0].x = x
|
||||
}
|
||||
if (this._visibleRangeHighLowPrice[1].price > kLineData.low) {
|
||||
this._visibleRangeHighLowPrice[1].price = kLineData.low
|
||||
this._visibleRangeHighLowPrice[1].x = x
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
this._cacheVisibleRange.from !== this._visibleRange.from ||
|
||||
this._cacheVisibleRange.to !== this._visibleRange.to
|
||||
) {
|
||||
this._cacheVisibleRange = { ...this._visibleRange }
|
||||
this._adjustVisibleRangeTimeTickList()
|
||||
}
|
||||
// More processing and loading, more loading if there are callback methods and no data is being loaded
|
||||
if (!this._loading && isValid(this._loadMoreDataCallback)) {
|
||||
let params: Nullable<LoadDataParams> = null
|
||||
if (from === 0) {
|
||||
if (this._loadDataMore.forward) {
|
||||
this._loading = true
|
||||
params = {
|
||||
type: LoadDataType.Forward,
|
||||
data: this._dataList[0] ?? null,
|
||||
callback: (data: KLineData[], more?: boolean) => {
|
||||
this.addData(data, LoadDataType.Forward, { forward: more ?? false, backward: more ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (to === totalBarCount) {
|
||||
if (this._loadDataMore.backward) {
|
||||
this._loading = true
|
||||
params = {
|
||||
type: LoadDataType.Backward,
|
||||
data: this._dataList[totalBarCount - 1] ?? null,
|
||||
callback: (data: KLineData[], more?: boolean) => {
|
||||
this.addData(data, LoadDataType.Backward, { forward: more ?? false, backward: more ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isValid(params)) {
|
||||
this._loadMoreDataCallback(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDateTimeFormat (): Intl.DateTimeFormat {
|
||||
return this._dateTimeFormat
|
||||
}
|
||||
|
||||
getBarSpace (): BarSpace {
|
||||
return {
|
||||
bar: this._barSpace,
|
||||
halfBar: this._barSpace / 2,
|
||||
gapBar: this._gapBarSpace,
|
||||
halfGapBar: Math.floor(this._gapBarSpace / 2)
|
||||
}
|
||||
}
|
||||
|
||||
setBarSpace (barSpace: number, adjustBeforeFunc?: () => void): void {
|
||||
if (barSpace < BarSpaceLimitConstants.MIN || barSpace > BarSpaceLimitConstants.MAX || this._barSpace === barSpace) {
|
||||
return
|
||||
}
|
||||
this._barSpace = barSpace
|
||||
this._calcOptimalBarSpace()
|
||||
adjustBeforeFunc?.()
|
||||
this._adjustVisibleRange()
|
||||
this.getTooltipStore().recalculateCrosshair(true)
|
||||
this._chart.adjustPaneViewport(false, true, true, true)
|
||||
}
|
||||
|
||||
setTotalBarSpace (totalSpace: number): void {
|
||||
if (this._totalBarSpace !== totalSpace) {
|
||||
this._totalBarSpace = totalSpace
|
||||
this._adjustVisibleRange()
|
||||
this.getTooltipStore().recalculateCrosshair(true)
|
||||
}
|
||||
}
|
||||
|
||||
setOffsetRightDistance (distance: number, isUpdate?: boolean): this {
|
||||
this._offsetRightDistance = this._scrollLimitRole === ScrollLimitRole.Distance ? Math.min(this._maxOffsetDistance.right, distance) : distance
|
||||
this._lastBarRightSideDiffBarCount = this._offsetRightDistance / this._barSpace
|
||||
if (isUpdate ?? false) {
|
||||
this._adjustVisibleRange()
|
||||
this.getTooltipStore().recalculateCrosshair(true)
|
||||
this._chart.adjustPaneViewport(false, true, true, true)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
getInitialOffsetRightDistance (): number {
|
||||
return this._offsetRightDistance
|
||||
}
|
||||
|
||||
getOffsetRightDistance (): number {
|
||||
return Math.max(0, this._lastBarRightSideDiffBarCount * this._barSpace)
|
||||
}
|
||||
|
||||
getLastBarRightSideDiffBarCount (): number {
|
||||
return this._lastBarRightSideDiffBarCount
|
||||
}
|
||||
|
||||
setLastBarRightSideDiffBarCount (barCount: number): this {
|
||||
this._lastBarRightSideDiffBarCount = barCount
|
||||
return this
|
||||
}
|
||||
|
||||
setMaxOffsetLeftDistance (distance: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.Distance
|
||||
this._maxOffsetDistance.left = distance
|
||||
return this
|
||||
}
|
||||
|
||||
setMaxOffsetRightDistance (distance: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.Distance
|
||||
this._maxOffsetDistance.right = distance
|
||||
return this
|
||||
}
|
||||
|
||||
setLeftMinVisibleBarCount (barCount: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.BarCount
|
||||
this._minVisibleBarCount.left = barCount
|
||||
return this
|
||||
}
|
||||
|
||||
setRightMinVisibleBarCount (barCount: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.BarCount
|
||||
this._minVisibleBarCount.right = barCount
|
||||
return this
|
||||
}
|
||||
|
||||
getVisibleRange (): VisibleRange {
|
||||
return this._visibleRange
|
||||
}
|
||||
|
||||
startScroll (): void {
|
||||
this._startLastBarRightSideDiffBarCount = this._lastBarRightSideDiffBarCount
|
||||
}
|
||||
|
||||
scroll (distance: number): void {
|
||||
if (!this._scrollEnabled) {
|
||||
return
|
||||
}
|
||||
const distanceBarCount = distance / this._barSpace
|
||||
const prevLastBarRightSideDistance = this._lastBarRightSideDiffBarCount * this._barSpace
|
||||
this._lastBarRightSideDiffBarCount = this._startLastBarRightSideDiffBarCount - distanceBarCount
|
||||
this._adjustVisibleRange()
|
||||
this.getTooltipStore().recalculateCrosshair(true)
|
||||
this.getChart().adjustPaneViewport(false, true, true, true)
|
||||
const realDistance = Math.round(
|
||||
prevLastBarRightSideDistance - this._lastBarRightSideDiffBarCount * this._barSpace
|
||||
)
|
||||
if (realDistance !== 0) {
|
||||
this.getActionStore().execute(ActionType.OnScroll, { distance: realDistance })
|
||||
}
|
||||
}
|
||||
|
||||
getDataByDataIndex (dataIndex: number): Nullable<KLineData> {
|
||||
return this._dataList[dataIndex] ?? null
|
||||
}
|
||||
|
||||
coordinateToFloatIndex (x: number): number {
|
||||
const dataCount = this._dataList.length
|
||||
const deltaFromRight = (this._totalBarSpace - x) / this._barSpace
|
||||
const index = dataCount + this._lastBarRightSideDiffBarCount - deltaFromRight
|
||||
return Math.round(index * 1000000) / 1000000
|
||||
}
|
||||
|
||||
dataIndexToTimestamp (dataIndex: number): Nullable<number> {
|
||||
const data = this.getDataByDataIndex(dataIndex)
|
||||
return data?.timestamp ?? null
|
||||
}
|
||||
|
||||
timestampToDataIndex (timestamp: number): number {
|
||||
if (this._dataList.length === 0) {
|
||||
return 0
|
||||
}
|
||||
return binarySearchNearest(this._dataList, 'timestamp', timestamp)
|
||||
}
|
||||
|
||||
dataIndexToCoordinate (dataIndex: number): number {
|
||||
const dataCount = this._dataList.length
|
||||
const deltaFromRight = dataCount + this._lastBarRightSideDiffBarCount - dataIndex
|
||||
// return Math.floor(this._totalBarSpace - (deltaFromRight - 0.5) * this._barSpace) - 0.5
|
||||
return Math.floor(this._totalBarSpace - (deltaFromRight - 0.5) * this._barSpace + 0.5)
|
||||
}
|
||||
|
||||
coordinateToDataIndex (x: number): number {
|
||||
return Math.ceil(this.coordinateToFloatIndex(x)) - 1
|
||||
}
|
||||
|
||||
zoom (scale: number, coordinate?: Partial<Coordinate>): void {
|
||||
if (!this._zoomEnabled) {
|
||||
return
|
||||
}
|
||||
let zoomCoordinate: Nullable<Partial<Coordinate>> = coordinate ?? null
|
||||
if (!isNumber(zoomCoordinate?.x)) {
|
||||
const crosshair = this.getTooltipStore().getCrosshair()
|
||||
zoomCoordinate = { x: crosshair?.x ?? this._totalBarSpace / 2 }
|
||||
}
|
||||
const x = zoomCoordinate.x!
|
||||
const floatIndex = this.coordinateToFloatIndex(x)
|
||||
const prevBarSpace = this._barSpace
|
||||
const barSpace = this._barSpace + scale * (this._barSpace / SCALE_MULTIPLIER)
|
||||
this.setBarSpace(barSpace, () => {
|
||||
this._lastBarRightSideDiffBarCount += (floatIndex - this.coordinateToFloatIndex(x))
|
||||
})
|
||||
const realScale = this._barSpace / prevBarSpace
|
||||
if (realScale !== 1) {
|
||||
this.getActionStore().execute(ActionType.OnZoom, { scale: realScale })
|
||||
}
|
||||
}
|
||||
|
||||
setZoomEnabled (enabled: boolean): this {
|
||||
this._zoomEnabled = enabled
|
||||
return this
|
||||
}
|
||||
|
||||
getZoomEnabled (): boolean {
|
||||
return this._zoomEnabled
|
||||
}
|
||||
|
||||
setScrollEnabled (enabled: boolean): this {
|
||||
this._scrollEnabled = enabled
|
||||
return this
|
||||
}
|
||||
|
||||
getScrollEnabled (): boolean {
|
||||
return this._scrollEnabled
|
||||
}
|
||||
|
||||
clear (): void {
|
||||
@ -338,14 +777,12 @@ export default class ChartStore {
|
||||
{ x: 0, price: Number.MIN_SAFE_INTEGER },
|
||||
{ x: 0, price: Number.MAX_SAFE_INTEGER },
|
||||
]
|
||||
this._timeScaleStore.clear()
|
||||
this._visibleRange = getDefaultVisibleRange()
|
||||
this._cacheVisibleRange = getDefaultVisibleRange()
|
||||
this._timeTicks.clear()
|
||||
this._tooltipStore.clear()
|
||||
}
|
||||
|
||||
getTimeScaleStore (): TimeScaleStore {
|
||||
return this._timeScaleStore
|
||||
}
|
||||
|
||||
getIndicatorStore (): IndicatorStore {
|
||||
return this._indicatorStore
|
||||
}
|
||||
|
@ -1,560 +0,0 @@
|
||||
/**
|
||||
* 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 type Nullable from '../common/Nullable'
|
||||
import type Coordinate from '../common/Coordinate'
|
||||
import type { KLineData } from '../common/Data'
|
||||
import type BarSpace from '../common/BarSpace'
|
||||
import type VisibleRange from '../common/VisibleRange'
|
||||
import { getDefaultVisibleRange } from '../common/VisibleRange'
|
||||
import { ActionType } from '../common/Action'
|
||||
import { type DateTime, formatDateToDateTime } from '../common/utils/format'
|
||||
import { isValid, isNumber, isString } from '../common/utils/typeChecks'
|
||||
import { logWarn } from '../common/utils/logger'
|
||||
import { binarySearchNearest } from '../common/utils/number'
|
||||
import { LoadDataType } from '../common/LoadDataCallback'
|
||||
import { calcTextWidth } from '../common/utils/canvas'
|
||||
|
||||
import type ChartStore from './ChartStore'
|
||||
|
||||
export interface TimeTick {
|
||||
weight: number
|
||||
dataIndex: number
|
||||
dateTime: DateTime
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export const TimeWeightConstants = {
|
||||
Year: 365 * 24 * 3600,
|
||||
Month: 30 * 24 * 3600,
|
||||
Day: 24 * 3600,
|
||||
Hour: 3600,
|
||||
Minute: 60,
|
||||
Second: 1
|
||||
}
|
||||
|
||||
interface LeftRightSide {
|
||||
left: number
|
||||
right: number
|
||||
}
|
||||
|
||||
const BarSpaceLimitConstants = {
|
||||
MIN: 1,
|
||||
MAX: 50
|
||||
}
|
||||
|
||||
const enum ScrollLimitRole {
|
||||
BarCount,
|
||||
Distance
|
||||
}
|
||||
|
||||
const DEFAULT_BAR_SPACE = 10
|
||||
|
||||
const DEFAULT_OFFSET_RIGHT_DISTANCE = 80
|
||||
|
||||
const BAR_GAP_RATIO = 0.2
|
||||
|
||||
export const SCALE_MULTIPLIER = 10
|
||||
|
||||
export default class TimeScaleStore {
|
||||
/**
|
||||
* Root store
|
||||
*/
|
||||
private readonly _chartStore: ChartStore
|
||||
|
||||
/**
|
||||
* Time format
|
||||
*/
|
||||
private _dateTimeFormat: Intl.DateTimeFormat = this._buildDateTimeFormat()!
|
||||
|
||||
/**
|
||||
* Scale enabled flag
|
||||
*/
|
||||
private _zoomEnabled = true
|
||||
|
||||
/**
|
||||
* Scroll enabled flag
|
||||
*/
|
||||
private _scrollEnabled = true
|
||||
|
||||
/**
|
||||
* Total space of drawing area
|
||||
*/
|
||||
private _totalBarSpace = 0
|
||||
|
||||
/**
|
||||
* Space occupied by a single piece of data
|
||||
*/
|
||||
private _barSpace: number = DEFAULT_BAR_SPACE
|
||||
|
||||
/**
|
||||
* The space of the draw bar
|
||||
*/
|
||||
private _gapBarSpace: number
|
||||
|
||||
/**
|
||||
* Distance from the last data to the right of the drawing area
|
||||
*/
|
||||
private _offsetRightDistance = DEFAULT_OFFSET_RIGHT_DISTANCE
|
||||
|
||||
/**
|
||||
* The number of bar calculated from the distance of the last data to the right of the drawing area
|
||||
*/
|
||||
private _lastBarRightSideDiffBarCount: number
|
||||
|
||||
/**
|
||||
* The number of bar to the right of the drawing area from the last data when scrolling starts
|
||||
*/
|
||||
private _startLastBarRightSideDiffBarCount = 0
|
||||
|
||||
/**
|
||||
* Scroll limit role
|
||||
*/
|
||||
private _scrollLimitRole: ScrollLimitRole = ScrollLimitRole.BarCount
|
||||
|
||||
/**
|
||||
* Scroll to the leftmost and rightmost visible bar
|
||||
*/
|
||||
private readonly _minVisibleBarCount: LeftRightSide = { left: 2, right: 2 }
|
||||
|
||||
/**
|
||||
* Scroll to the leftmost and rightmost distance
|
||||
*/
|
||||
private readonly _maxOffsetDistance: LeftRightSide = { left: 50, right: 50 }
|
||||
|
||||
/**
|
||||
* Start and end points of visible area data index
|
||||
*/
|
||||
private _visibleRange = getDefaultVisibleRange()
|
||||
|
||||
private _cacheVisibleRange = getDefaultVisibleRange()
|
||||
|
||||
private readonly _timeTicks = new Map<number, TimeTick[]>()
|
||||
|
||||
private _visibleRangeTimeTickList: TimeTick[] = []
|
||||
|
||||
constructor (chartStore: ChartStore) {
|
||||
this._chartStore = chartStore
|
||||
this._calcOptimalBarSpace()
|
||||
this._lastBarRightSideDiffBarCount = this._offsetRightDistance / this._barSpace
|
||||
}
|
||||
|
||||
private _calcOptimalBarSpace (): void {
|
||||
const specialBarSpace = 4
|
||||
const ratio = 1 - BAR_GAP_RATIO * Math.atan(Math.max(specialBarSpace, this._barSpace) - specialBarSpace) / (Math.PI * 0.5)
|
||||
let gapBarSpace = Math.min(Math.floor(this._barSpace * ratio), Math.floor(this._barSpace))
|
||||
if (gapBarSpace % 2 === 0 && gapBarSpace + 2 >= this._barSpace) {
|
||||
--gapBarSpace
|
||||
}
|
||||
this._gapBarSpace = Math.max(1, gapBarSpace)
|
||||
}
|
||||
|
||||
classifyTimeTicks (newDataList: KLineData[], isUpdate?: boolean): void {
|
||||
let baseDataIndex = 0
|
||||
let prevKLineData: Nullable<KLineData> = null
|
||||
if (isUpdate ?? false) {
|
||||
const dataList = this._chartStore.getDataList()
|
||||
baseDataIndex = dataList.length
|
||||
prevKLineData = dataList[baseDataIndex - 1]
|
||||
} else {
|
||||
this._timeTicks.clear()
|
||||
}
|
||||
|
||||
for (let i = 0; i < newDataList.length; i++) {
|
||||
const kLineData = newDataList[i]
|
||||
let weight = TimeWeightConstants.Second
|
||||
const dateTime = formatDateToDateTime(this._dateTimeFormat, kLineData.timestamp)
|
||||
if (isValid(prevKLineData)) {
|
||||
const prevDateTime = formatDateToDateTime(this._dateTimeFormat, prevKLineData.timestamp)
|
||||
if (dateTime.YYYY !== prevDateTime.YYYY) {
|
||||
weight = TimeWeightConstants.Year
|
||||
} else if (dateTime.MM !== prevDateTime.MM) {
|
||||
weight = TimeWeightConstants.Month
|
||||
} else if (dateTime.DD !== prevDateTime.DD) {
|
||||
weight = TimeWeightConstants.Day
|
||||
} else if (dateTime.HH !== prevDateTime.HH) {
|
||||
weight = TimeWeightConstants.Hour
|
||||
} else if (dateTime.mm !== prevDateTime.mm) {
|
||||
weight = TimeWeightConstants.Minute
|
||||
} else {
|
||||
weight = TimeWeightConstants.Second
|
||||
}
|
||||
}
|
||||
const tickList = this._timeTicks.get(weight) ?? []
|
||||
tickList.push({ dataIndex: i + baseDataIndex, weight, dateTime, timestamp: kLineData.timestamp })
|
||||
this._timeTicks.set(weight, tickList)
|
||||
prevKLineData = kLineData
|
||||
}
|
||||
}
|
||||
|
||||
adjustVisibleRangeTimeTickList (): void {
|
||||
const tickTextStyles = this._chartStore.getStyles().xAxis.tickText
|
||||
const width = Math.max(
|
||||
Math.ceil(this._totalBarSpace / 10),
|
||||
calcTextWidth('0000-00-00 00:00', tickTextStyles.size, tickTextStyles.weight, tickTextStyles.family)
|
||||
)
|
||||
const barCount = Math.ceil(width / this._barSpace)
|
||||
let tickList: TimeTick[] = []
|
||||
Array.from(this._timeTicks.keys()).sort((w1, w2) => w2 - w1).forEach(key => {
|
||||
const prevTickList = tickList
|
||||
tickList = []
|
||||
|
||||
const prevTickListLength = prevTickList.length
|
||||
let prevTickListPointer = 0
|
||||
const currentTicks = this._timeTicks.get(key)!
|
||||
const currentTicksLength = currentTicks.length
|
||||
|
||||
let rightIndex = Infinity
|
||||
let leftIndex = -Infinity
|
||||
for (let i = 0; i < currentTicksLength; i++) {
|
||||
const tick = currentTicks[i]
|
||||
const currentIndex = tick.dataIndex
|
||||
|
||||
while (prevTickListPointer < prevTickListLength) {
|
||||
const lastMark = prevTickList[prevTickListPointer]
|
||||
const lastIndex = lastMark.dataIndex
|
||||
if (lastIndex < currentIndex) {
|
||||
prevTickListPointer++
|
||||
tickList.push(lastMark)
|
||||
leftIndex = lastIndex
|
||||
rightIndex = Infinity
|
||||
} else {
|
||||
rightIndex = lastIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rightIndex - currentIndex >= barCount && currentIndex - leftIndex >= barCount) {
|
||||
tickList.push(tick)
|
||||
leftIndex = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
for (; prevTickListPointer < prevTickListLength; prevTickListPointer++) {
|
||||
tickList.push(prevTickList[prevTickListPointer])
|
||||
}
|
||||
})
|
||||
this._visibleRangeTimeTickList = []
|
||||
for (let i = 0; i < tickList.length; i++) {
|
||||
const tick = tickList[i]
|
||||
if (tick.dataIndex >= this._visibleRange.from && tick.dataIndex <= this._visibleRange.to) {
|
||||
this._visibleRangeTimeTickList.push(tick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getVisibleRangeTimeTickList (): TimeTick[] {
|
||||
return this._visibleRangeTimeTickList
|
||||
}
|
||||
|
||||
/**
|
||||
* adjust visible range
|
||||
*/
|
||||
adjustVisibleRange (): void {
|
||||
const dataList = this._chartStore.getDataList()
|
||||
const totalBarCount = dataList.length
|
||||
const visibleBarCount = this._totalBarSpace / this._barSpace
|
||||
|
||||
let leftMinVisibleBarCount = 0
|
||||
let rightMinVisibleBarCount = 0
|
||||
|
||||
if (this._scrollLimitRole === ScrollLimitRole.Distance) {
|
||||
leftMinVisibleBarCount = (this._totalBarSpace - this._maxOffsetDistance.right) / this._barSpace
|
||||
rightMinVisibleBarCount = (this._totalBarSpace - this._maxOffsetDistance.left) / this._barSpace
|
||||
} else {
|
||||
leftMinVisibleBarCount = this._minVisibleBarCount.left
|
||||
rightMinVisibleBarCount = this._minVisibleBarCount.right
|
||||
}
|
||||
|
||||
leftMinVisibleBarCount = Math.max(0, leftMinVisibleBarCount)
|
||||
rightMinVisibleBarCount = Math.max(0, rightMinVisibleBarCount)
|
||||
|
||||
const maxRightOffsetBarCount = visibleBarCount - Math.min(leftMinVisibleBarCount, totalBarCount)
|
||||
if (this._lastBarRightSideDiffBarCount > maxRightOffsetBarCount) {
|
||||
this._lastBarRightSideDiffBarCount = maxRightOffsetBarCount
|
||||
}
|
||||
|
||||
const minRightOffsetBarCount = -totalBarCount + Math.min(rightMinVisibleBarCount, totalBarCount)
|
||||
if (this._lastBarRightSideDiffBarCount < minRightOffsetBarCount) {
|
||||
this._lastBarRightSideDiffBarCount = minRightOffsetBarCount
|
||||
}
|
||||
|
||||
let to = Math.round(this._lastBarRightSideDiffBarCount + totalBarCount + 0.5)
|
||||
const realTo = to
|
||||
if (to > totalBarCount) {
|
||||
to = totalBarCount
|
||||
}
|
||||
let from = Math.round(to - visibleBarCount) - 1
|
||||
if (from < 0) {
|
||||
from = 0
|
||||
}
|
||||
const realFrom = this._lastBarRightSideDiffBarCount > 0 ? Math.round(totalBarCount + this._lastBarRightSideDiffBarCount - visibleBarCount) - 1 : from
|
||||
this._visibleRange = { from, to, realFrom, realTo }
|
||||
this._chartStore.getActionStore().execute(ActionType.OnVisibleRangeChange, this._visibleRange)
|
||||
this._chartStore.adjustVisibleRangeDataList()
|
||||
if (
|
||||
this._cacheVisibleRange.from !== this._visibleRange.from ||
|
||||
this._cacheVisibleRange.to !== this._visibleRange.to
|
||||
) {
|
||||
this._cacheVisibleRange = { ...this._visibleRange }
|
||||
this.adjustVisibleRangeTimeTickList()
|
||||
}
|
||||
// More processing and loading, more loading if there are callback methods and no data is being loaded
|
||||
if (from === 0) {
|
||||
const firstData = dataList[0]
|
||||
this._chartStore.executeLoadMoreDataCallback({
|
||||
type: LoadDataType.Forward,
|
||||
data: firstData ?? null
|
||||
})
|
||||
}
|
||||
if (to === totalBarCount) {
|
||||
this._chartStore.executeLoadMoreDataCallback({
|
||||
type: LoadDataType.Backward,
|
||||
data: dataList[totalBarCount - 1] ?? null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getDateTimeFormat (): Intl.DateTimeFormat {
|
||||
return this._dateTimeFormat
|
||||
}
|
||||
|
||||
_buildDateTimeFormat (timezone?: string): Nullable<Intl.DateTimeFormat> {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
}
|
||||
if (isString(timezone)) {
|
||||
options.timeZone = timezone
|
||||
}
|
||||
let dateTimeFormat: Nullable<Intl.DateTimeFormat> = null
|
||||
try {
|
||||
dateTimeFormat = new Intl.DateTimeFormat('en', options)
|
||||
} catch (e) {
|
||||
logWarn('', '', 'Timezone is error!!!')
|
||||
}
|
||||
return dateTimeFormat
|
||||
}
|
||||
|
||||
setTimezone (timezone: string): void {
|
||||
const dateTimeFormat: Nullable<Intl.DateTimeFormat> = this._buildDateTimeFormat(timezone)
|
||||
if (dateTimeFormat !== null) {
|
||||
this.classifyTimeTicks(this._chartStore.getDataList())
|
||||
this.adjustVisibleRangeTimeTickList()
|
||||
this._dateTimeFormat = dateTimeFormat
|
||||
}
|
||||
}
|
||||
|
||||
getTimezone (): string {
|
||||
return this._dateTimeFormat.resolvedOptions().timeZone
|
||||
}
|
||||
|
||||
getBarSpace (): BarSpace {
|
||||
return {
|
||||
bar: this._barSpace,
|
||||
halfBar: this._barSpace / 2,
|
||||
gapBar: this._gapBarSpace,
|
||||
halfGapBar: Math.floor(this._gapBarSpace / 2)
|
||||
}
|
||||
}
|
||||
|
||||
setBarSpace (barSpace: number, adjustBeforeFunc?: () => void): void {
|
||||
if (barSpace < BarSpaceLimitConstants.MIN || barSpace > BarSpaceLimitConstants.MAX || this._barSpace === barSpace) {
|
||||
return
|
||||
}
|
||||
this._barSpace = barSpace
|
||||
this._calcOptimalBarSpace()
|
||||
adjustBeforeFunc?.()
|
||||
this.adjustVisibleRange()
|
||||
this._chartStore.getTooltipStore().recalculateCrosshair(true)
|
||||
this._chartStore.getChart().adjustPaneViewport(false, true, true, true)
|
||||
}
|
||||
|
||||
setTotalBarSpace (totalSpace: number): this {
|
||||
if (this._totalBarSpace !== totalSpace) {
|
||||
this._totalBarSpace = totalSpace
|
||||
this.adjustVisibleRange()
|
||||
this._chartStore.getTooltipStore().recalculateCrosshair(true)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
setOffsetRightDistance (distance: number, isUpdate?: boolean): this {
|
||||
this._offsetRightDistance = this._scrollLimitRole === ScrollLimitRole.Distance ? Math.min(this._maxOffsetDistance.right, distance) : distance
|
||||
this._lastBarRightSideDiffBarCount = this._offsetRightDistance / this._barSpace
|
||||
if (isUpdate ?? false) {
|
||||
this.adjustVisibleRange()
|
||||
this._chartStore.getTooltipStore().recalculateCrosshair(true)
|
||||
this._chartStore.getChart().adjustPaneViewport(false, true, true, true)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
resetOffsetRightDistance (): void {
|
||||
this.setOffsetRightDistance(this._offsetRightDistance)
|
||||
}
|
||||
|
||||
getInitialOffsetRightDistance (): number {
|
||||
return this._offsetRightDistance
|
||||
}
|
||||
|
||||
getOffsetRightDistance (): number {
|
||||
return Math.max(0, this._lastBarRightSideDiffBarCount * this._barSpace)
|
||||
}
|
||||
|
||||
getLastBarRightSideDiffBarCount (): number {
|
||||
return this._lastBarRightSideDiffBarCount
|
||||
}
|
||||
|
||||
setLastBarRightSideDiffBarCount (barCount: number): this {
|
||||
this._lastBarRightSideDiffBarCount = barCount
|
||||
return this
|
||||
}
|
||||
|
||||
setMaxOffsetLeftDistance (distance: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.Distance
|
||||
this._maxOffsetDistance.left = distance
|
||||
return this
|
||||
}
|
||||
|
||||
setMaxOffsetRightDistance (distance: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.Distance
|
||||
this._maxOffsetDistance.right = distance
|
||||
return this
|
||||
}
|
||||
|
||||
setLeftMinVisibleBarCount (barCount: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.BarCount
|
||||
this._minVisibleBarCount.left = barCount
|
||||
return this
|
||||
}
|
||||
|
||||
setRightMinVisibleBarCount (barCount: number): this {
|
||||
this._scrollLimitRole = ScrollLimitRole.BarCount
|
||||
this._minVisibleBarCount.right = barCount
|
||||
return this
|
||||
}
|
||||
|
||||
getVisibleRange (): VisibleRange {
|
||||
return this._visibleRange
|
||||
}
|
||||
|
||||
startScroll (): void {
|
||||
this._startLastBarRightSideDiffBarCount = this._lastBarRightSideDiffBarCount
|
||||
}
|
||||
|
||||
scroll (distance: number): void {
|
||||
if (!this._scrollEnabled) {
|
||||
return
|
||||
}
|
||||
const distanceBarCount = distance / this._barSpace
|
||||
const prevLastBarRightSideDistance = this._lastBarRightSideDiffBarCount * this._barSpace
|
||||
this._lastBarRightSideDiffBarCount = this._startLastBarRightSideDiffBarCount - distanceBarCount
|
||||
this.adjustVisibleRange()
|
||||
this._chartStore.getTooltipStore().recalculateCrosshair(true)
|
||||
this._chartStore.getChart().adjustPaneViewport(false, true, true, true)
|
||||
const realDistance = Math.round(
|
||||
prevLastBarRightSideDistance - this._lastBarRightSideDiffBarCount * this._barSpace
|
||||
)
|
||||
if (realDistance !== 0) {
|
||||
this._chartStore.getActionStore().execute(ActionType.OnScroll, { distance: realDistance })
|
||||
}
|
||||
}
|
||||
|
||||
getDataByDataIndex (dataIndex: number): Nullable<KLineData> {
|
||||
return this._chartStore.getDataList()[dataIndex] ?? null
|
||||
}
|
||||
|
||||
coordinateToFloatIndex (x: number): number {
|
||||
const dataCount = this._chartStore.getDataList().length
|
||||
const deltaFromRight = (this._totalBarSpace - x) / this._barSpace
|
||||
const index = dataCount + this._lastBarRightSideDiffBarCount - deltaFromRight
|
||||
return Math.round(index * 1000000) / 1000000
|
||||
}
|
||||
|
||||
dataIndexToTimestamp (dataIndex: number): Nullable<number> {
|
||||
const data = this.getDataByDataIndex(dataIndex)
|
||||
return data?.timestamp ?? null
|
||||
}
|
||||
|
||||
timestampToDataIndex (timestamp: number): number {
|
||||
const dataList = this._chartStore.getDataList()
|
||||
if (dataList.length === 0) {
|
||||
return 0
|
||||
}
|
||||
return binarySearchNearest(dataList, 'timestamp', timestamp)
|
||||
}
|
||||
|
||||
dataIndexToCoordinate (dataIndex: number): number {
|
||||
const dataCount = this._chartStore.getDataList().length
|
||||
const deltaFromRight = dataCount + this._lastBarRightSideDiffBarCount - dataIndex
|
||||
// return Math.floor(this._totalBarSpace - (deltaFromRight - 0.5) * this._barSpace) - 0.5
|
||||
return Math.floor(this._totalBarSpace - (deltaFromRight - 0.5) * this._barSpace + 0.5)
|
||||
}
|
||||
|
||||
coordinateToDataIndex (x: number): number {
|
||||
return Math.ceil(this.coordinateToFloatIndex(x)) - 1
|
||||
}
|
||||
|
||||
zoom (scale: number, coordinate?: Partial<Coordinate>): void {
|
||||
if (!this._zoomEnabled) {
|
||||
return
|
||||
}
|
||||
let zoomCoordinate: Nullable<Partial<Coordinate>> = coordinate ?? null
|
||||
if (!isNumber(zoomCoordinate?.x)) {
|
||||
const crosshair = this._chartStore.getTooltipStore().getCrosshair()
|
||||
zoomCoordinate = { x: crosshair?.x ?? this._totalBarSpace / 2 }
|
||||
}
|
||||
const x = zoomCoordinate.x!
|
||||
const floatIndex = this.coordinateToFloatIndex(x)
|
||||
const prevBarSpace = this._barSpace
|
||||
const barSpace = this._barSpace + scale * (this._barSpace / SCALE_MULTIPLIER)
|
||||
this.setBarSpace(barSpace, () => {
|
||||
this._lastBarRightSideDiffBarCount += (floatIndex - this.coordinateToFloatIndex(x))
|
||||
})
|
||||
const realScale = this._barSpace / prevBarSpace
|
||||
if (realScale !== 1) {
|
||||
this._chartStore.getActionStore().execute(ActionType.OnZoom, { scale: realScale })
|
||||
}
|
||||
}
|
||||
|
||||
setZoomEnabled (enabled: boolean): this {
|
||||
this._zoomEnabled = enabled
|
||||
return this
|
||||
}
|
||||
|
||||
getZoomEnabled (): boolean {
|
||||
return this._zoomEnabled
|
||||
}
|
||||
|
||||
setScrollEnabled (enabled: boolean): this {
|
||||
this._scrollEnabled = enabled
|
||||
return this
|
||||
}
|
||||
|
||||
getScrollEnabled (): boolean {
|
||||
return this._scrollEnabled
|
||||
}
|
||||
|
||||
clear (): void {
|
||||
this._visibleRange = getDefaultVisibleRange()
|
||||
this._cacheVisibleRange = getDefaultVisibleRange()
|
||||
this._timeTicks.clear()
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ export default class TooltipStore {
|
||||
let realDataIndex = 0
|
||||
let dataIndex = 0
|
||||
if (isNumber(cr.x)) {
|
||||
realDataIndex = this._chartStore.getTimeScaleStore().coordinateToDataIndex(cr.x)
|
||||
realDataIndex = this._chartStore.coordinateToDataIndex(cr.x)
|
||||
if (realDataIndex < 0) {
|
||||
dataIndex = 0
|
||||
} else if (realDataIndex > dataList.length - 1) {
|
||||
@ -59,7 +59,7 @@ export default class TooltipStore {
|
||||
dataIndex = realDataIndex
|
||||
}
|
||||
const kLineData: Nullable<KLineData> = dataList[dataIndex]
|
||||
const realX = this._chartStore.getTimeScaleStore().dataIndexToCoordinate(realDataIndex)
|
||||
const realX = this._chartStore.dataIndexToCoordinate(realDataIndex)
|
||||
const prevCrosshair = { x: this._crosshair.x, y: this._crosshair.y, paneId: this._crosshair.paneId }
|
||||
this._crosshair = { ...cr, realX, kLineData, realDataIndex, dataIndex }
|
||||
if (
|
||||
|
@ -28,7 +28,7 @@ export default abstract class AxisView<C extends Axis = Axis> extends View<C> {
|
||||
const pane = widget.getPane()
|
||||
const bounding = widget.getBounding()
|
||||
const axis = pane.getAxisComponent()
|
||||
const styles: AxisStyle = this.getAxisStyles(pane.getChart().getStyles())
|
||||
const styles: AxisStyle = this.getAxisStyles(pane.getChart().getOptions().styles)
|
||||
if (styles.show) {
|
||||
if (styles.axisLine.show) {
|
||||
this.createFigure({
|
||||
|
@ -53,7 +53,7 @@ export default class CandleAreaView extends ChildrenView {
|
||||
const lastDataIndex = dataList.length - 1
|
||||
const bounding = widget.getBounding()
|
||||
const yAxis = pane.getAxisComponent()
|
||||
const styles = chart.getStyles().candle.area
|
||||
const styles = chart.getOptions().styles.candle.area
|
||||
const coordinates: Coordinate[] = []
|
||||
let minY = Number.MAX_SAFE_INTEGER
|
||||
let areaStartX: number = Number.MIN_SAFE_INTEGER
|
||||
|
@ -49,7 +49,7 @@ export default class CandleBarView extends ChildrenView {
|
||||
let ohlcSize = 0
|
||||
let halfOhlcSize = 0
|
||||
if (candleBarOptions.type === CandleType.Ohlc) {
|
||||
const { gapBar } = chartStore.getTimeScaleStore().getBarSpace()
|
||||
const { gapBar } = chartStore.getBarSpace()
|
||||
ohlcSize = Math.min(Math.max(Math.round(gapBar * 0.2), 1), 8)
|
||||
if (ohlcSize > 2 && ohlcSize % 2 === 1) {
|
||||
ohlcSize--
|
||||
@ -157,7 +157,7 @@ export default class CandleBarView extends ChildrenView {
|
||||
}
|
||||
|
||||
protected getCandleBarOptions (chartStore: ChartStore): Nullable<CandleBarOptions> {
|
||||
const candleStyles = chartStore.getStyles().candle
|
||||
const candleStyles = chartStore.getOptions().styles.candle
|
||||
return {
|
||||
type: candleStyles.type as Exclude<CandleType, CandleType.Area>,
|
||||
styles: candleStyles.bar
|
||||
|
@ -26,13 +26,14 @@ export default class CandleHighLowPriceView extends View<YAxis> {
|
||||
const widget = this.getWidget()
|
||||
const pane = widget.getPane()
|
||||
const chartStore = pane.getChart().getChartStore()
|
||||
const priceMarkStyles = chartStore.getStyles().candle.priceMark
|
||||
const chartOptions = chartStore.getOptions()
|
||||
const priceMarkStyles = chartOptions.styles.candle.priceMark
|
||||
const highPriceMarkStyles = priceMarkStyles.high
|
||||
const lowPriceMarkStyles = priceMarkStyles.low
|
||||
if (priceMarkStyles.show && (highPriceMarkStyles.show || lowPriceMarkStyles.show)) {
|
||||
const highestLowestPrice = chartStore.getVisibleRangeHighLowPrice()
|
||||
const thousandsSeparator = chartStore.getThousandsSeparator()
|
||||
const decimalFoldThreshold = chartStore.getDecimalFoldThreshold()
|
||||
const thousandsSeparator = chartOptions.thousandsSeparator
|
||||
const decimalFoldThreshold = chartOptions.decimalFoldThreshold
|
||||
const precision = chartStore.getPrecision()
|
||||
const yAxis = pane.getAxisComponent()
|
||||
|
||||
|
@ -25,7 +25,8 @@ export default class CandleLastPriceLabelView extends View {
|
||||
const pane = widget.getPane()
|
||||
const bounding = widget.getBounding()
|
||||
const chartStore = pane.getChart().getChartStore()
|
||||
const priceMarkStyles = chartStore.getStyles().candle.priceMark
|
||||
const chartOptions = chartStore.getOptions()
|
||||
const priceMarkStyles = chartOptions.styles.candle.priceMark
|
||||
const lastPriceMarkStyles = priceMarkStyles.last
|
||||
const lastPriceMarkTextStyles = lastPriceMarkStyles.text
|
||||
if (priceMarkStyles.show && lastPriceMarkStyles.show && lastPriceMarkTextStyles.show) {
|
||||
@ -52,7 +53,7 @@ export default class CandleLastPriceLabelView extends View {
|
||||
),
|
||||
precision.price
|
||||
)
|
||||
text = formatFoldDecimal(formatThousands(text, chartStore.getThousandsSeparator()), chartStore.getDecimalFoldThreshold())
|
||||
text = formatFoldDecimal(formatThousands(text, chartOptions.thousandsSeparator), chartOptions.decimalFoldThreshold)
|
||||
let x = 0
|
||||
let textAlgin: CanvasTextAlign = 'left'
|
||||
if (yAxis.isFromZero()) {
|
||||
|
@ -23,7 +23,7 @@ export default class CandleLastPriceView extends View {
|
||||
const pane = widget.getPane()
|
||||
const bounding = widget.getBounding()
|
||||
const chartStore = pane.getChart().getChartStore()
|
||||
const priceMarkStyles = chartStore.getStyles().candle.priceMark
|
||||
const priceMarkStyles = chartStore.getOptions().styles.candle.priceMark
|
||||
const lastPriceMarkStyles = priceMarkStyles.last
|
||||
const lastPriceMarkLineStyles = lastPriceMarkStyles.line
|
||||
if (priceMarkStyles.show && lastPriceMarkStyles.show && lastPriceMarkLineStyles.show) {
|
||||
|
@ -50,14 +50,16 @@ export default class CandleTooltipView extends IndicatorTooltipView {
|
||||
const yAxisBounding = pane.getYAxisWidget()!.getBounding()
|
||||
const dataList = chartStore.getDataList()
|
||||
const precision = chartStore.getPrecision()
|
||||
const locale = chartStore.getLocale()
|
||||
const customApi = chartStore.getCustomApi()
|
||||
const thousandsSeparator = chartStore.getThousandsSeparator()
|
||||
const decimalFoldThreshold = chartStore.getDecimalFoldThreshold()
|
||||
const {
|
||||
styles,
|
||||
locale,
|
||||
thousandsSeparator,
|
||||
decimalFoldThreshold,
|
||||
customApi
|
||||
} = chartStore.getOptions()
|
||||
const activeIcon = chartStore.getTooltipStore().getActiveIcon()
|
||||
const indicators = chartStore.getIndicatorStore().getInstanceByPaneId(pane.getId())
|
||||
const dateTimeFormat = chartStore.getTimeScaleStore().getDateTimeFormat()
|
||||
const styles = chartStore.getStyles()
|
||||
const dateTimeFormat = chartStore.getDateTimeFormat()
|
||||
const candleStyles = styles.candle
|
||||
const indicatorStyles = styles.indicator
|
||||
if (
|
||||
|
@ -28,7 +28,7 @@ export default abstract class ChildrenView extends View<YAxis> {
|
||||
const pane = this.getWidget().getPane()
|
||||
const chartStore = pane.getChart().getChartStore()
|
||||
const visibleRangeDataList = chartStore.getVisibleRangeDataList()
|
||||
const barSpace = chartStore.getTimeScaleStore().getBarSpace()
|
||||
const barSpace = chartStore.getBarSpace()
|
||||
const dataLength = visibleRangeDataList.length
|
||||
let index = 0
|
||||
while (index < dataLength) {
|
||||
|
@ -35,7 +35,7 @@ export default class CrosshairHorizontalLabelView<C extends Axis = YAxis> extend
|
||||
const bounding = widget.getBounding()
|
||||
const chartStore = widget.getPane().getChart().getChartStore()
|
||||
const crosshair = chartStore.getTooltipStore().getCrosshair()
|
||||
const styles = chartStore.getStyles().crosshair
|
||||
const styles = chartStore.getOptions().styles.crosshair
|
||||
if (isString(crosshair.paneId) && this.compare(crosshair, pane.getId())) {
|
||||
if (styles.show) {
|
||||
const directionStyles = this.getDirectionStyles(styles)
|
||||
@ -87,10 +87,11 @@ export default class CrosshairHorizontalLabelView<C extends Axis = YAxis> extend
|
||||
precision
|
||||
)
|
||||
|
||||
const { customApi, thousandsSeparator, decimalFoldThreshold } = chartStore.getOptions()
|
||||
if (shouldFormatBigNumber) {
|
||||
text = chartStore.getCustomApi().formatBigNumber(text)
|
||||
text = customApi.formatBigNumber(text)
|
||||
}
|
||||
return formatFoldDecimal(formatThousands(text, chartStore.getThousandsSeparator()), chartStore.getDecimalFoldThreshold())
|
||||
return formatFoldDecimal(formatThousands(text, thousandsSeparator), decimalFoldThreshold)
|
||||
}
|
||||
|
||||
protected getTextAttrs (text: string, _textWidth: number, crosshair: Crosshair, bounding: Bounding, axis: Axis, _styles: StateTextStyle): TextAttrs {
|
||||
|
@ -25,7 +25,7 @@ export default class CrosshairLineView extends View {
|
||||
const bounding = widget.getBounding()
|
||||
const chartStore = widget.getPane().getChart().getChartStore()
|
||||
const crosshair = chartStore.getTooltipStore().getCrosshair()
|
||||
const styles = chartStore.getStyles().crosshair
|
||||
const styles = chartStore.getOptions().styles.crosshair
|
||||
if (isString(crosshair.paneId) && styles.show) {
|
||||
if (crosshair.paneId === pane.getId()) {
|
||||
const y = crosshair.y!
|
||||
|
@ -38,7 +38,7 @@ export default class CrosshairVerticalLabelView extends CrosshairHorizontalLabel
|
||||
|
||||
override getText (crosshair: Crosshair, chartStore: ChartStore): string {
|
||||
const timestamp = crosshair.kLineData?.timestamp
|
||||
return chartStore.getCustomApi().formatDate(chartStore.getTimeScaleStore().getDateTimeFormat(), timestamp!, 'YYYY-MM-DD HH:mm', FormatDateType.Crosshair)
|
||||
return chartStore.getOptions().customApi.formatDate(chartStore.getDateTimeFormat(), timestamp!, 'YYYY-MM-DD HH:mm', FormatDateType.Crosshair)
|
||||
}
|
||||
|
||||
override getTextAttrs (text: string, textWidth: number, crosshair: Crosshair, bounding: Bounding, _axis: Axis, styles: StateTextStyle): TextAttrs {
|
||||
|
@ -23,7 +23,7 @@ export default class GridView extends View {
|
||||
const chart = pane.getChart()
|
||||
const bounding = widget.getBounding()
|
||||
|
||||
const gridStyles = chart.getStyles().grid
|
||||
const gridStyles = chart.getOptions().styles.grid
|
||||
const show = gridStyles.show
|
||||
|
||||
if (show) {
|
||||
|
@ -27,8 +27,8 @@ export default class IndicatorLastValueView extends View<YAxis> {
|
||||
const pane = widget.getPane()
|
||||
const bounding = widget.getBounding()
|
||||
const chartStore = pane.getChart().getChartStore()
|
||||
const customApi = chartStore.getCustomApi()
|
||||
const defaultStyles = chartStore.getStyles().indicator
|
||||
const chartOptions = chartStore.getOptions()
|
||||
const defaultStyles = chartOptions.styles.indicator
|
||||
const lastValueMarkStyles = defaultStyles.lastValueMark
|
||||
const lastValueMarkTextStyles = lastValueMarkStyles.text
|
||||
if (lastValueMarkStyles.show) {
|
||||
@ -37,8 +37,9 @@ export default class IndicatorLastValueView extends View<YAxis> {
|
||||
const dataList = chartStore.getDataList()
|
||||
const dataIndex = dataList.length - 1
|
||||
const indicators = chartStore.getIndicatorStore().getInstanceByPaneId(pane.getId())
|
||||
const thousandsSeparator = chartStore.getThousandsSeparator()
|
||||
const decimalFoldThreshold = chartStore.getDecimalFoldThreshold()
|
||||
const customApi = chartOptions.customApi
|
||||
const thousandsSeparator = chartOptions.thousandsSeparator
|
||||
const decimalFoldThreshold = chartOptions.decimalFoldThreshold
|
||||
indicators.forEach(indicator => {
|
||||
const result = indicator.result
|
||||
const indicatorData = result[dataIndex] ?? result[dataIndex - 1]
|
||||
|
@ -55,12 +55,10 @@ export default class IndicatorTooltipView extends View<YAxis> {
|
||||
const crosshair = chartStore.getTooltipStore().getCrosshair()
|
||||
if (isValid(crosshair.kLineData)) {
|
||||
const bounding = widget.getBounding()
|
||||
const customApi = chartStore.getCustomApi()
|
||||
const thousandsSeparator = chartStore.getThousandsSeparator()
|
||||
const decimalFoldThreshold = chartStore.getDecimalFoldThreshold()
|
||||
const { styles, customApi, thousandsSeparator, decimalFoldThreshold } = chartStore.getOptions()
|
||||
const indicators = chartStore.getIndicatorStore().getInstanceByPaneId(pane.getId())
|
||||
const activeIcon = chartStore.getTooltipStore().getActiveIcon()
|
||||
const defaultStyles = chartStore.getStyles().indicator
|
||||
const defaultStyles = styles.indicator
|
||||
const { offsetLeft, offsetTop, offsetRight } = defaultStyles.tooltip
|
||||
this.drawIndicatorTooltip(
|
||||
ctx, pane.getId(), chartStore.getDataList(),
|
||||
@ -307,7 +305,7 @@ export default class IndicatorTooltipView extends View<YAxis> {
|
||||
const { name: customName, calcParamsText: customCalcParamsText, legends: customLegends, icons: customIcons } = indicator.createTooltipDataSource({
|
||||
kLineDataList: dataList,
|
||||
indicator,
|
||||
visibleRange: chartStore.getTimeScaleStore().getVisibleRange(),
|
||||
visibleRange: chartStore.getVisibleRange(),
|
||||
bounding: widget.getBounding(),
|
||||
crosshair,
|
||||
defaultStyles: styles,
|
||||
|
@ -34,7 +34,7 @@ export default class IndicatorView extends CandleBarView {
|
||||
const indicator = indicators[i]
|
||||
if (indicator.shouldOhlc && indicator.visible) {
|
||||
const indicatorStyles = indicator.styles
|
||||
const defaultStyles = chartStore.getStyles().indicator
|
||||
const defaultStyles = chartStore.getOptions().styles.indicator
|
||||
const upColor = formatValue(indicatorStyles, 'ohlc.upColor', defaultStyles.ohlc.upColor) as string
|
||||
const downColor = formatValue(indicatorStyles, 'ohlc.downColor', defaultStyles.ohlc.downColor) as string
|
||||
const noChangeColor = formatValue(indicatorStyles, 'ohlc.noChangeColor', defaultStyles.ohlc.noChangeColor) as string
|
||||
@ -68,10 +68,9 @@ export default class IndicatorView extends CandleBarView {
|
||||
const yAxis = pane.getAxisComponent()
|
||||
const chartStore = chart.getChartStore()
|
||||
const dataList = chartStore.getDataList()
|
||||
const timeScaleStore = chartStore.getTimeScaleStore()
|
||||
const visibleRange = timeScaleStore.getVisibleRange()
|
||||
const visibleRange = chartStore.getVisibleRange()
|
||||
const indicators = chartStore.getIndicatorStore().getInstanceByPaneId(pane.getId())
|
||||
const defaultStyles = chartStore.getStyles().indicator
|
||||
const defaultStyles = chartStore.getOptions().styles.indicator
|
||||
ctx.save()
|
||||
indicators.forEach(indicator => {
|
||||
if (indicator.visible) {
|
||||
@ -89,7 +88,7 @@ export default class IndicatorView extends CandleBarView {
|
||||
indicator,
|
||||
visibleRange,
|
||||
bounding,
|
||||
barSpace: timeScaleStore.getBarSpace(),
|
||||
barSpace: chartStore.getBarSpace(),
|
||||
defaultStyles,
|
||||
xAxis,
|
||||
yAxis
|
||||
|
@ -33,7 +33,7 @@ import { OVERLAY_FIGURE_KEY_PREFIX, OverlayMode, getAllOverlayFigureIgnoreEventT
|
||||
import type { ProgressOverlayInfo, EventOverlayInfo } from '../store/OverlayStore'
|
||||
import type OverlayStore from '../store/OverlayStore'
|
||||
import { EventOverlayInfoFigureType } from '../store/OverlayStore'
|
||||
import type TimeScaleStore from '../store/TimeScaleStore'
|
||||
import type ChartStore from '../store/ChartStore'
|
||||
|
||||
import { PaneIdConstants } from '../pane/types'
|
||||
|
||||
@ -169,7 +169,7 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
if (figureType === EventOverlayInfoFigureType.Point) {
|
||||
overlay.eventPressedPointMove(point, figureIndex)
|
||||
} else {
|
||||
overlay.eventPressedOtherMove(point, this.getWidget().getPane().getChart().getChartStore().getTimeScaleStore())
|
||||
overlay.eventPressedOtherMove(point, this.getWidget().getPane().getChart().getChartStore())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,11 +289,11 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
const pane = this.getWidget().getPane()
|
||||
const chart = pane.getChart()
|
||||
const paneId = pane.getId()
|
||||
const timeScaleStore = chart.getChartStore().getTimeScaleStore()
|
||||
const chartStore = chart.getChartStore()
|
||||
if (this.coordinateToPointTimestampDataIndexFlag()) {
|
||||
const xAxis = chart.getXAxisPane().getAxisComponent()
|
||||
const dataIndex = xAxis.convertFromPixel(coordinate.x)
|
||||
const timestamp = timeScaleStore.dataIndexToTimestamp(dataIndex) ?? undefined
|
||||
const timestamp = chartStore.dataIndexToTimestamp(dataIndex) ?? undefined
|
||||
point.dataIndex = dataIndex
|
||||
point.timestamp = timestamp
|
||||
}
|
||||
@ -301,7 +301,7 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
const yAxis = pane.getAxisComponent()
|
||||
let value = yAxis.convertFromPixel(coordinate.y)
|
||||
if (o.mode !== OverlayMode.Normal && paneId === PaneIdConstants.CANDLE && isNumber(point.dataIndex)) {
|
||||
const kLineData = timeScaleStore.getDataByDataIndex(point.dataIndex)
|
||||
const kLineData = chartStore.getDataByDataIndex(point.dataIndex)
|
||||
if (kLineData !== null) {
|
||||
const modeSensitivity = o.modeSensitivity
|
||||
if (value > kLineData.high) {
|
||||
@ -380,14 +380,16 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
const xAxis = chart.getXAxisPane().getAxisComponent()
|
||||
const bounding = widget.getBounding()
|
||||
const chartStore = chart.getChartStore()
|
||||
const customApi = chartStore.getCustomApi()
|
||||
const thousandsSeparator = chartStore.getThousandsSeparator()
|
||||
const decimalFoldThreshold = chartStore.getDecimalFoldThreshold()
|
||||
const timeScaleStore = chartStore.getTimeScaleStore()
|
||||
const dateTimeFormat = timeScaleStore.getDateTimeFormat()
|
||||
const barSpace = timeScaleStore.getBarSpace()
|
||||
const {
|
||||
styles,
|
||||
customApi,
|
||||
thousandsSeparator,
|
||||
decimalFoldThreshold,
|
||||
} = chartStore.getOptions()
|
||||
const dateTimeFormat = chartStore.getDateTimeFormat()
|
||||
const barSpace = chartStore.getBarSpace()
|
||||
const precision = chartStore.getPrecision()
|
||||
const defaultStyles = chartStore.getStyles().overlay
|
||||
const defaultStyles = styles.overlay
|
||||
const overlayStore = chartStore.getOverlayStore()
|
||||
const hoverInstanceInfo = overlayStore.getHoverInstanceInfo()
|
||||
const clickInstanceInfo = overlayStore.getClickInstanceInfo()
|
||||
@ -414,7 +416,7 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
ctx, overlay, bounding, barSpace, overlayPrecision,
|
||||
dateTimeFormat, customApi, thousandsSeparator, decimalFoldThreshold,
|
||||
defaultStyles, xAxis, yAxis,
|
||||
hoverInstanceInfo, clickInstanceInfo, timeScaleStore
|
||||
hoverInstanceInfo, clickInstanceInfo, chartStore
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -427,7 +429,7 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
ctx, overlay, bounding, barSpace,
|
||||
overlayPrecision, dateTimeFormat, customApi, thousandsSeparator, decimalFoldThreshold,
|
||||
defaultStyles, xAxis, yAxis,
|
||||
hoverInstanceInfo, clickInstanceInfo, timeScaleStore
|
||||
hoverInstanceInfo, clickInstanceInfo, chartStore
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -448,13 +450,13 @@ export default class OverlayView<C extends Axis = YAxis> extends View<C> {
|
||||
yAxis: Nullable<YAxis>,
|
||||
hoverInstanceInfo: EventOverlayInfo,
|
||||
clickInstanceInfo: EventOverlayInfo,
|
||||
timeScaleStore: TimeScaleStore
|
||||
chartStore: ChartStore
|
||||
): void {
|
||||
const { points } = overlay
|
||||
const coordinates = points.map(point => {
|
||||
let dataIndex = point.dataIndex
|
||||
if (isNumber(point.timestamp)) {
|
||||
dataIndex = timeScaleStore.timestampToDataIndex(point.timestamp)
|
||||
dataIndex = chartStore.timestampToDataIndex(point.timestamp)
|
||||
}
|
||||
const coordinate = { x: 0, y: 0 }
|
||||
if (isNumber(dataIndex)) {
|
||||
|
@ -40,7 +40,7 @@ export default class CandleWidget extends IndicatorWidget {
|
||||
}
|
||||
|
||||
override updateMainContent (ctx: CanvasRenderingContext2D): void {
|
||||
const candleStyles = this.getPane().getChart().getStyles().candle
|
||||
const candleStyles = this.getPane().getChart().getOptions().styles.candle
|
||||
if (candleStyles.type !== CandleType.Area) {
|
||||
this._candleBarView.draw(ctx)
|
||||
this._candleHighLowPriceView.draw(ctx)
|
||||
|
@ -159,7 +159,7 @@ export default class SeparatorWidget extends Widget<SeparatorPane> {
|
||||
const bottomPane = pane.getBottomPane()
|
||||
if (bottomPane?.getOptions().dragEnabled ?? false) {
|
||||
const chart = pane.getChart()
|
||||
const styles = chart.getStyles().separator
|
||||
const styles = chart.getOptions().styles.separator
|
||||
this.getContainer().style.background = styles.activeBackgroundColor
|
||||
return true
|
||||
}
|
||||
@ -190,7 +190,7 @@ export default class SeparatorWidget extends Widget<SeparatorPane> {
|
||||
|
||||
override updateImp (container: HTMLElement, _bounding: Bounding, level: UpdateLevel): void {
|
||||
if (level === UpdateLevel.All || level === UpdateLevel.Separator) {
|
||||
const styles = this.getPane().getChart().getStyles().separator
|
||||
const styles = this.getPane().getChart().getOptions().styles.separator
|
||||
container.style.top = `${-Math.floor((REAL_SEPARATOR_HEIGHT - styles.size) / 2)}px`
|
||||
container.style.height = `${REAL_SEPARATOR_HEIGHT}px`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user