refactor: refactor chart store

This commit is contained in:
liihuu 2024-10-13 02:54:05 +08:00
parent 9d119f56d6
commit bb853ae4c3
27 changed files with 722 additions and 879 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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 +

View File

@ -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`

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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 (

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()) {

View File

@ -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) {

View File

@ -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 (

View File

@ -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) {

View File

@ -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 {

View File

@ -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!

View File

@ -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 {

View File

@ -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) {

View File

@ -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]

View File

@ -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,

View File

@ -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

View File

@ -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)) {

View File

@ -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)

View File

@ -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`
}