Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 4 additions & 19 deletions src/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ export default class ChartImp implements Chart {
secondMeasureWidth: false,
update: true,
buildYAxisTick: false,
cacheYAxisWidth: false,
forceBuildYAxisTick: false
}

Expand All @@ -145,8 +144,6 @@ export default class ChartImp implements Chart {
}
}

private readonly _cacheYAxisWidth = { left: 0, right: 0 }

constructor(container: HTMLElement, options?: Options) {
this._initContainer(container)
this._chartEvent = new Event(this._chartContainer, this)
Expand Down Expand Up @@ -238,7 +235,7 @@ export default class ChartImp implements Chart {
return this._separatorPanes
}

layout(options: { sort?: boolean; measureHeight?: boolean; measureWidth?: boolean; secondMeasureWidth?: boolean; update?: boolean; buildYAxisTick?: boolean; cacheYAxisWidth?: boolean; forceBuildYAxisTick?: boolean }): void {
layout(options: { sort?: boolean; measureHeight?: boolean; measureWidth?: boolean; secondMeasureWidth?: boolean; update?: boolean; buildYAxisTick?: boolean; forceBuildYAxisTick?: boolean }): void {
if (options.sort ?? false) {
this._layoutUpdateOptions.sort = options.sort!
}
Expand All @@ -257,9 +254,6 @@ export default class ChartImp implements Chart {
if (options.buildYAxisTick ?? false) {
this._layoutUpdateOptions.buildYAxisTick = options.buildYAxisTick!
}
if (options.cacheYAxisWidth ?? false) {
this._layoutUpdateOptions.cacheYAxisWidth = options.cacheYAxisWidth!
}
if (options.forceBuildYAxisTick ?? false) {
this._layoutUpdateOptions.forceBuildYAxisTick = options.forceBuildYAxisTick!
}
Expand All @@ -280,7 +274,7 @@ export default class ChartImp implements Chart {
}

private _layout(): void {
const { sort, measureHeight, measureWidth, secondMeasureWidth, update, buildYAxisTick, cacheYAxisWidth, forceBuildYAxisTick } = this._layoutUpdateOptions
const { sort, measureHeight, measureWidth, secondMeasureWidth, update, buildYAxisTick, forceBuildYAxisTick } = this._layoutUpdateOptions
if (sort) {
while (isValid(this._chartContainer.firstChild)) {
this._chartContainer.removeChild(this._chartContainer.firstChild)
Expand Down Expand Up @@ -418,16 +412,8 @@ export default class ChartImp implements Chart {
})
})

let leftYAxisWidth = leftOutsideYAxisWidths.reduce((total, width) => total + width, 0)
let rightYAxisWidth = rightOutsideYAxisWidths.reduce((total, width) => total + width, 0)

if (cacheYAxisWidth) {
leftYAxisWidth = Math.max(this._cacheYAxisWidth.left, leftYAxisWidth)
rightYAxisWidth = Math.max(this._cacheYAxisWidth.right, rightYAxisWidth)
}

this._cacheYAxisWidth.left = leftYAxisWidth
this._cacheYAxisWidth.right = rightYAxisWidth
const leftYAxisWidth = leftOutsideYAxisWidths.reduce((total, width) => total + width, 0)
const rightYAxisWidth = rightOutsideYAxisWidths.reduce((total, width) => total + width, 0)

let mainWidth = totalWidth
let mainLeft = 0
Expand Down Expand Up @@ -524,7 +510,6 @@ export default class ChartImp implements Chart {
secondMeasureWidth: false,
update: false,
buildYAxisTick: false,
cacheYAxisWidth: false,
forceBuildYAxisTick: false
}
}
Expand Down
12 changes: 4 additions & 8 deletions src/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,7 @@ export default class StoreImp implements Store {
this._chart.layout({
measureWidth: true,
update: true,
buildYAxisTick: true,
cacheYAxisWidth: type !== 'init'
buildYAxisTick: true
})
}
}
Expand Down Expand Up @@ -834,8 +833,7 @@ export default class StoreImp implements Store {
this._chart.layout({
measureWidth: true,
update: true,
buildYAxisTick: true,
cacheYAxisWidth: true
buildYAxisTick: true
})
}

Expand All @@ -860,8 +858,7 @@ export default class StoreImp implements Store {
this._chart.layout({
measureWidth: true,
update: true,
buildYAxisTick: true,
cacheYAxisWidth: true
buildYAxisTick: true
})
}
return this
Expand Down Expand Up @@ -923,8 +920,7 @@ export default class StoreImp implements Store {
this._chart.layout({
measureWidth: true,
update: true,
buildYAxisTick: true,
cacheYAxisWidth: true
buildYAxisTick: true
})
const realDistance = Math.round(prevLastBarRightSideDistance - this._lastBarRightSideDiffBarCount * this._barSpace)
if (realDistance !== 0) {
Expand Down
72 changes: 68 additions & 4 deletions src/component/YAxis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
*/

import type Bounding from '../common/Bounding'
import type Nullable from '../common/Nullable'
import { SymbolDefaultPrecisionConstants } from '../common/SymbolInfo'
import { calcTextWidth } from '../common/utils/canvas'
import { formatPrecision } from '../common/utils/format'
import { getPrecision, index10, nice, round } from '../common/utils/number'
import { getPrecision, index10, log10, nice, round } from '../common/utils/number'
import { isFunction, isNumber, isString, isValid, merge } from '../common/utils/typeChecks'
import type DrawPane from '../pane/DrawPane'
import { PaneIdConstants } from '../pane/types'
Expand All @@ -38,6 +39,34 @@ export interface YAxis extends Axis, Required<YAxisTemplate> {

export type YAxisConstructor = new (parent: DrawPane) => YAxis

const Y_AXIS_WIDTH_GRID_PX = 4
const Y_AXIS_WIDTH_SETTLE_MS = 400

/**
* Replace every digit with a fixed representative so the measured width depends
* on the label's shape (length), not on which digits it currently holds.
*/
function stableWidthText(text: string, reserveSign: boolean): string {
const stable = text.replace(/[0-9]/g, '8')
return reserveSign && !stable.includes('-') ? `-${stable}` : stable
}

/**
* The widest label the crosshair can produce within the current decade of the
* range — the decade's top over whichever end is larger in magnitude. It does
* not move while the range drifts inside a decade and changes once when the
* data genuinely changes magnitude.
*/
function widthAnchorValue(range: AxisRange): number {
const { displayFrom, displayTo } = range
const magnitude = Math.max(Math.abs(displayFrom), Math.abs(displayTo))
if (!Number.isFinite(magnitude) || magnitude === 0) {
return displayTo
}
const anchored = 9.99 * index10(Math.floor(log10(magnitude)))
return displayFrom < 0 || displayTo < 0 ? -anchored : anchored
}

export default abstract class YAxisImp extends AxisImp implements YAxis {
id = ''
paneId = ''
Expand All @@ -49,6 +78,9 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
bottom: 0.1
}

private _settledWidth: Nullable<number> = null
private _settledWidthLastNeededAt = 0

createRange: AxisCreateRangeCallback = (params) => params.defaultRange
minSpan: AxisMinSpanCallback = (precision) => index10(-precision)
valueToRealValue: AxisValueToValueCallback = (value) => value
Expand Down Expand Up @@ -376,14 +408,14 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {

if (lastPriceMarkTextVisible || crosshairHorizontalTextVisible) {
const pricePrecision = chartStore.getSymbol()?.pricePrecision ?? SymbolDefaultPrecisionConstants.PRICE
const max = this.getRange().displayTo
const max = widthAnchorValue(this.getRange())

if (lastPriceMarkTextVisible) {
const dataList = chartStore.getDataList()
const data = dataList[dataList.length - 1]
if (isValid(data)) {
const { paddingLeft, paddingRight, size, family, weight } = priceMarkStyles.last.text
lastPriceTextWidth = paddingLeft + calcTextWidth(formatPrecision(data.close, pricePrecision), size, weight, family) + paddingRight
lastPriceTextWidth = paddingLeft + calcTextWidth(stableWidthText(formatPrecision(data.close, pricePrecision), false), size, weight, family) + paddingRight
const formatExtendText = chartStore.getInnerFormatter().formatExtendText
priceMarkStyles.last.extendTexts.forEach((item, index) => {
const text = formatExtendText({ type: 'last_price', data, index })
Expand Down Expand Up @@ -418,11 +450,43 @@ export default abstract class YAxisImp extends AxisImp implements YAxis {
valueText = chartStore.getInnerFormatter().formatBigNumber(valueText)
}
valueText = chartStore.getDecimalFold().format(valueText)
valueText = stableWidthText(valueText, this.getRange().displayFrom < 0 || this.getRange().displayTo < 0)
crosshairHorizontalTextWidth +=
crosshairStyles.horizontal.text.paddingLeft + crosshairStyles.horizontal.text.paddingRight + crosshairStyles.horizontal.text.borderSize * 2 + calcTextWidth(valueText, crosshairStyles.horizontal.text.size, crosshairStyles.horizontal.text.weight, crosshairStyles.horizontal.text.family)
}
}
return Math.max(yAxisWidth, lastPriceTextWidth, crosshairHorizontalTextWidth)
return this._settleWidth(Math.max(yAxisWidth, lastPriceTextWidth, crosshairHorizontalTextWidth))
}

/**
* Grow immediately (a label must always fit) but shrink only after the wider
* label has been gone for a quiet moment, rounded up to a small pixel grid so
* digit noise cannot move the column.
*/
private _settleWidth(measured: number): number {
if (!Number.isFinite(measured) || measured <= 0) {
return measured
}
const wanted = Math.ceil(measured / Y_AXIS_WIDTH_GRID_PX) * Y_AXIS_WIDTH_GRID_PX
const now = Date.now()
const held = this._settledWidth
if (held === null || wanted > held) {
this._settledWidth = wanted
this._settledWidthLastNeededAt = now
return wanted
}
// The stamp records when the held width was last NEEDED: the wait to give
// the room back starts when the wide label leaves and restarts if one returns.
if (wanted === held) {
this._settledWidthLastNeededAt = now
return held
}
if (now - this._settledWidthLastNeededAt >= Y_AXIS_WIDTH_SETTLE_MS) {
this._settledWidth = wanted
this._settledWidthLastNeededAt = now
return wanted
}
return held
}

protected override getBounding(): Bounding {
Expand Down