diff --git a/src/common/VisibleData.ts b/src/common/VisibleData.ts index 89e5b92bc..77027f558 100644 --- a/src/common/VisibleData.ts +++ b/src/common/VisibleData.ts @@ -12,10 +12,11 @@ * limitations under the License. */ +import type Nullable from './Nullable' import type KLineData from './KLineData' export default interface VisibleData { dataIndex: number x: number - data: KLineData + data: Nullable } diff --git a/src/component/YAxis.ts b/src/component/YAxis.ts index f302b504c..55d889080 100644 --- a/src/component/YAxis.ts +++ b/src/component/YAxis.ts @@ -14,7 +14,7 @@ import { YAxisType, YAxisPosition, CandleType } from '../common/Styles' import type Bounding from '../common/Bounding' -import { isNumber } from '../common/utils/typeChecks' +import { isNumber, isValid } from '../common/utils/typeChecks' import { index10, log10 } from '../common/utils/number' import { calcTextWidth } from '../common/utils/canvas' import { formatPrecision, formatThousands } from '../common/utils/format' @@ -88,15 +88,17 @@ export default abstract class YAxisImp extends AxisImp implements YAxis { const areaValueKey = candleStyles.area.value const shouldCompareHighLow = (inCandle && !isArea) || (!inCandle && shouldOhlc) visibleDataList.forEach(({ dataIndex, data }) => { - if (shouldCompareHighLow) { - min = Math.min(min, data.low) - max = Math.max(max, data.high) - } - if (inCandle && isArea) { - const value = data[areaValueKey] - if (isNumber(value)) { - min = Math.min(min, value) - max = Math.max(max, value) + if (isValid(data)) { + if (shouldCompareHighLow) { + min = Math.min(min, data.low) + max = Math.max(max, data.high) + } + if (inCandle && isArea) { + const value = data[areaValueKey] + if (isNumber(value)) { + min = Math.min(min, value) + max = Math.max(max, value) + } } } figuresResultList.forEach(({ figures, result }) => { @@ -124,7 +126,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis { switch (type) { case YAxisType.Percentage: { const fromData = visibleDataList[0]?.data - if (isNumber(fromData?.close)) { + if (isValid(fromData) && isNumber(fromData.close)) { min = (min - fromData.close) / fromData.close * 100 max = (max - fromData.close) / fromData.close * 100 } @@ -391,7 +393,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis { const chartStore = this.getParent().getChart().getChartStore() const visibleDataList = chartStore.getVisibleDataList() const fromData = visibleDataList[0]?.data - if (isNumber(fromData?.close)) { + if (isValid(fromData) && isNumber(fromData.close)) { return fromData.close * value / 100 + fromData.close } return 0 @@ -420,7 +422,7 @@ export default abstract class YAxisImp extends AxisImp implements YAxis { const chartStore = this.getParent().getChart().getChartStore() const visibleDataList = chartStore.getVisibleDataList() const fromData = visibleDataList[0]?.data - if (isNumber(fromData?.close)) { + if (isValid(fromData) && isNumber(fromData.close)) { v = (value - fromData.close) / fromData.close * 100 } break diff --git a/src/extension/indicator/volume.ts b/src/extension/indicator/volume.ts index 6fa0b6429..9064e53d7 100644 --- a/src/extension/indicator/volume.ts +++ b/src/extension/indicator/volume.ts @@ -15,6 +15,7 @@ import type KLineData from '../../common/KLineData' import { type IndicatorStyle } from '../../common/Styles' import { formatValue } from '../../common/utils/format' +import { isValid } from '../../common/utils/typeChecks' import { type Indicator, type IndicatorTemplate, type IndicatorFigureStylesCallbackData, IndicatorSeries, type IndicatorFigure } from '../../component/Indicator' @@ -43,16 +44,16 @@ const volume: IndicatorTemplate = { type: 'bar', baseValue: 0, styles: (data: IndicatorFigureStylesCallbackData, indicator: Indicator, defaultStyles: IndicatorStyle) => { - const kLineData = data.current.kLineData! - let color: string - if (kLineData.close > kLineData.open) { - color = formatValue(indicator.styles, 'bars[0].upColor', (defaultStyles.bars)[0].upColor) as string - } else if (kLineData.close < kLineData.open) { - color = formatValue(indicator.styles, 'bars[0].downColor', (defaultStyles.bars)[0].downColor) as string - } else { - color = formatValue(indicator.styles, 'bars[0].noChangeColor', (defaultStyles.bars)[0].noChangeColor) as string + const kLineData = data.current.kLineData + let color = formatValue(indicator.styles, 'bars[0].noChangeColor', (defaultStyles.bars)[0].noChangeColor) + if (isValid(kLineData)) { + if (kLineData.close > kLineData.open) { + color = formatValue(indicator.styles, 'bars[0].upColor', (defaultStyles.bars)[0].upColor) + } else if (kLineData.close < kLineData.open) { + color = formatValue(indicator.styles, 'bars[0].downColor', (defaultStyles.bars)[0].downColor) + } } - return { color } + return { color: color as string } } } ], diff --git a/src/store/ChartStore.ts b/src/store/ChartStore.ts index b3fc154af..0a149af9f 100644 --- a/src/store/ChartStore.ts +++ b/src/store/ChartStore.ts @@ -107,8 +107,8 @@ export default class ChartStore { */ adjustVisibleDataList (): void { this._visibleDataList = [] - const { from, to } = this._timeScaleStore.getVisibleRange() - for (let i = from; i < to; i++) { + 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._visibleDataList.push({ diff --git a/src/store/TimeScaleStore.ts b/src/store/TimeScaleStore.ts index b893dd70c..945c4ce6f 100644 --- a/src/store/TimeScaleStore.ts +++ b/src/store/TimeScaleStore.ts @@ -178,6 +178,7 @@ export default class TimeScaleStore { } let to = Math.round(this._lastBarRightSideDiffBarCount + totalBarCount + 0.5) + const realTo = to if (to > totalBarCount) { to = totalBarCount } @@ -186,7 +187,7 @@ export default class TimeScaleStore { from = 0 } const realFrom = this._lastBarRightSideDiffBarCount > 0 ? Math.round(totalBarCount + this._lastBarRightSideDiffBarCount - visibleBarCount) - 1 : from - this._visibleRange = { from, to, realFrom, realTo: to } + this._visibleRange = { from, to, realFrom, realTo } this._chartStore.getActionStore().execute(ActionType.OnVisibleRangeChange, this._visibleRange) this._chartStore.adjustVisibleDataList() // More processing and loading, more loading if there are callback methods and no data is being loaded diff --git a/src/view/CandleAreaView.ts b/src/view/CandleAreaView.ts index 58ba9a953..40655c7b4 100644 --- a/src/view/CandleAreaView.ts +++ b/src/view/CandleAreaView.ts @@ -35,7 +35,7 @@ export default class CandleAreaView extends ChildrenView { this.eachChildren((data: VisibleData, barSpace: BarSpace, i: number) => { const { data: kLineData, x } = data const { halfGapBar } = barSpace - const value = kLineData[candleAreaStyles.value] + const value = kLineData?.[candleAreaStyles.value] if (isNumber(value)) { const y = yAxis.convertToPixel(value) if (i === 0) { diff --git a/src/view/CandleBarView.ts b/src/view/CandleBarView.ts index c68a5e37d..2abf54a84 100644 --- a/src/view/CandleBarView.ts +++ b/src/view/CandleBarView.ts @@ -27,6 +27,7 @@ import { type RectAttrs } from '../extension/figure/rect' import ChildrenView from './ChildrenView' import { PaneIdConstants } from '../pane/types' +import { isValid } from '../common/utils/typeChecks' export interface CandleBarOptions { type: Exclude @@ -48,101 +49,103 @@ export default class CandleBarView extends ChildrenView { const yAxis = pane.getAxisComponent() this.eachChildren((data: VisibleData, barSpace: BarSpace) => { const { data: kLineData, x } = data - const { open, high, low, close } = kLineData - const { type, styles } = candleBarOptions - const colors: string[] = [] - if (close > open) { - colors[0] = styles.upColor - colors[1] = styles.upBorderColor - colors[2] = styles.upWickColor - } else if (close < open) { - colors[0] = styles.downColor - colors[1] = styles.downBorderColor - colors[2] = styles.downWickColor - } else { - colors[0] = styles.noChangeColor - colors[1] = styles.noChangeBorderColor - colors[2] = styles.noChangeWickColor - } - const openY = yAxis.convertToPixel(open) - const closeY = yAxis.convertToPixel(close) - const priceY = [ - openY, closeY, - yAxis.convertToPixel(high), - yAxis.convertToPixel(low) - ] - priceY.sort((a, b) => a - b) - - let rects: Array>> = [] - switch (type) { - case CandleType.CandleSolid: { - rects = this._createSolidBar(x, priceY, barSpace, colors) - break + if (isValid(kLineData)) { + const { open, high, low, close } = kLineData + const { type, styles } = candleBarOptions + const colors: string[] = [] + if (close > open) { + colors[0] = styles.upColor + colors[1] = styles.upBorderColor + colors[2] = styles.upWickColor + } else if (close < open) { + colors[0] = styles.downColor + colors[1] = styles.downBorderColor + colors[2] = styles.downWickColor + } else { + colors[0] = styles.noChangeColor + colors[1] = styles.noChangeBorderColor + colors[2] = styles.noChangeWickColor } - case CandleType.CandleStroke: { - rects = this._createStrokeBar(x, priceY, barSpace, colors) - break - } - case CandleType.CandleUpStroke: { - if (close > open) { - rects = this._createStrokeBar(x, priceY, barSpace, colors) - } else { + const openY = yAxis.convertToPixel(open) + const closeY = yAxis.convertToPixel(close) + const priceY = [ + openY, closeY, + yAxis.convertToPixel(high), + yAxis.convertToPixel(low) + ] + priceY.sort((a, b) => a - b) + + let rects: Array>> = [] + switch (type) { + case CandleType.CandleSolid: { rects = this._createSolidBar(x, priceY, barSpace, colors) + break } - break - } - case CandleType.CandleDownStroke: { - if (open > close) { + case CandleType.CandleStroke: { rects = this._createStrokeBar(x, priceY, barSpace, colors) - } else { - rects = this._createSolidBar(x, priceY, barSpace, colors) + break } - break - } - case CandleType.Ohlc: { - const size = Math.min(Math.max(Math.round(barSpace.gapBar * 0.2), 1), 7) - rects = [ - { - name: 'rect', - attrs: { - x: x - size / 2, - y: priceY[0], - width: size, - height: priceY[3] - priceY[0] - }, - styles: { color: colors[0] } - }, { - name: 'rect', - attrs: { - x: x - barSpace.halfGapBar, - y: openY + size > priceY[3] ? priceY[3] - size : openY, - width: barSpace.halfGapBar - size / 2, - height: size - }, - styles: { color: colors[0] } - }, { - name: 'rect', - attrs: { - x: x + size / 2, - y: closeY + size > priceY[3] ? priceY[3] - size : closeY, - width: barSpace.halfGapBar - size / 2, - height: size - }, - styles: { color: colors[0] } + case CandleType.CandleUpStroke: { + if (close > open) { + rects = this._createStrokeBar(x, priceY, barSpace, colors) + } else { + rects = this._createSolidBar(x, priceY, barSpace, colors) } - ] - break - } - } - rects.forEach(rect => { - let handler: EventHandler | undefined - if (isMain) { - handler = { - mouseClickEvent: this._boundCandleBarClickEvent(data) + break + } + case CandleType.CandleDownStroke: { + if (open > close) { + rects = this._createStrokeBar(x, priceY, barSpace, colors) + } else { + rects = this._createSolidBar(x, priceY, barSpace, colors) + } + break + } + case CandleType.Ohlc: { + const size = Math.min(Math.max(Math.round(barSpace.gapBar * 0.2), 1), 7) + rects = [ + { + name: 'rect', + attrs: { + x: x - size / 2, + y: priceY[0], + width: size, + height: priceY[3] - priceY[0] + }, + styles: { color: colors[0] } + }, { + name: 'rect', + attrs: { + x: x - barSpace.halfGapBar, + y: openY + size > priceY[3] ? priceY[3] - size : openY, + width: barSpace.halfGapBar - size / 2, + height: size + }, + styles: { color: colors[0] } + }, { + name: 'rect', + attrs: { + x: x + size / 2, + y: closeY + size > priceY[3] ? priceY[3] - size : closeY, + width: barSpace.halfGapBar - size / 2, + height: size + }, + styles: { color: colors[0] } + } + ] + break } } - this.createFigure(rect, handler)?.draw(ctx) - }) + rects.forEach(rect => { + let handler: EventHandler | undefined + if (isMain) { + handler = { + mouseClickEvent: this._boundCandleBarClickEvent(data) + } + } + this.createFigure(rect, handler)?.draw(ctx) + }) + } }) } } diff --git a/src/view/CandleHighLowPriceView.ts b/src/view/CandleHighLowPriceView.ts index 95de6d3bc..7437ffb4c 100644 --- a/src/view/CandleHighLowPriceView.ts +++ b/src/view/CandleHighLowPriceView.ts @@ -19,6 +19,7 @@ import { type CandleHighLowPriceMarkStyle } from '../common/Styles' import ChildrenView from './ChildrenView' import { formatPrecision, formatThousands } from '../common/utils/format' +import { isValid } from '../common/utils/typeChecks' export default class CandleHighLowPriceView extends ChildrenView { override drawImp (ctx: CanvasRenderingContext2D): void { @@ -38,13 +39,15 @@ export default class CandleHighLowPriceView extends ChildrenView { let lowX = 0 this.eachChildren((data: VisibleData) => { const { data: kLineData, x } = data - if (high < kLineData.high) { - high = kLineData.high - highX = x - } - if (low > kLineData.low) { - low = kLineData.low - lowX = x + if (isValid(kLineData)) { + if (high < kLineData.high) { + high = kLineData.high + highX = x + } + if (low > kLineData.low) { + low = kLineData.low + lowX = x + } } }) const highY = yAxis.convertToPixel(high) diff --git a/src/view/CandleLastPriceLabelView.ts b/src/view/CandleLastPriceLabelView.ts index 06fd7fad1..75444ac50 100644 --- a/src/view/CandleLastPriceLabelView.ts +++ b/src/view/CandleLastPriceLabelView.ts @@ -49,7 +49,7 @@ export default class CandleLastPriceLabelView extends View { let text: string if (yAxis.getType() === YAxisType.Percentage) { const fromData = visibleDataList[0].data - const fromClose = fromData.close + const fromClose = fromData!.close text = `${((close - fromClose) / fromClose * 100).toFixed(2)}%` } else { text = formatPrecision(close, precision.price) diff --git a/src/view/CrosshairHorizontalLabelView.ts b/src/view/CrosshairHorizontalLabelView.ts index 6e7365a52..97562e7f7 100644 --- a/src/view/CrosshairHorizontalLabelView.ts +++ b/src/view/CrosshairHorizontalLabelView.ts @@ -68,8 +68,8 @@ export default class CrosshairHorizontalLabelView extend let text: string if (yAxis.getType() === YAxisType.Percentage) { const visibleDataList = chartStore.getVisibleDataList() - const fromData = visibleDataList[0]?.data ?? {} - text = `${((value - fromData.close) / fromData.close * 100).toFixed(2)}%` + const fromData = visibleDataList[0]?.data + text = `${((value - fromData!.close) / fromData!.close * 100).toFixed(2)}%` } else { const indicators = chartStore.getIndicatorStore().getInstances(crosshair.paneId!) let precision = 0