Skip to content

fix: remove sign-guarded symlog from logarithm axis to fix negative labels for sub-1 prices - #837

Open
NemeZZiZZ wants to merge 1 commit into
klinecharts:mainfrom
NemeZZiZZ:fix/log-axis-sub-1-prices
Open

NemeZZiZZ wants to merge 1 commit into
klinecharts:mainfrom
NemeZZiZZ:fix/log-axis-sub-1-prices

Conversation

@NemeZZiZZ

Copy link
Copy Markdown
Contributor

Problem

The built-in logarithm y-axis template maps values through a sign-guarded symlog:

valueToRealValue: (value) => (value < 0 ? -log10(Math.abs(value)) : log10(value))
realValueToDisplayValue: (value) => (value < 0 ? -index10(Math.abs(value)) : index10(value))

For any price in (0, 1) — e.g. an altcoin trading at 0.0000123 — log10(value) is negative, so the real-space coordinate is negative while the price is positive. The inverse functions then take that negative real value to mean "negative price" and return -index10(|real|), a huge negative number.

Verified against current main (replicating YAxis.createRangeImp + createTicksImp with the template installed), visible range [0.000008, 0.000023]:

Path Result
displayFrom / displayTo after range gap -138922.89 / -35200.16
Rendered axis tick labels -130000, -120000, …, -80000
Crosshair label (CrosshairHorizontalLabelView.getText → convertFromPixel) -69929.30 (should be ≈ 0.0000143)
Overlay point value stored on draw (OverlayView → convertFromPixel) -64399.94 (should be 0.0000155)
Public chart.convertFromPixel() API same garbage

Any instrument priced below 1 is affected (a [0.081, 0.119] range renders ticks -12.6 … -9.6). Prices above 1 work because log10(v) > 0 keeps the sign guard on the consistent branch.

The symlog is also not injective: valueToRealValue(10) === valueToRealValue(-0.1) === 1, so no sign-guard variant can be made invertible — the guards cannot be repaired, only removed.

Fix

Make the template a pure logarithmic axis: real = log10(value), value = index10(real), unconditionally. 4 functions + createRange, one file.

After the fix, same scenarios: tick labels 0.000009, 0.000012, …, crosshair 0.0000143, overlay round-trip exact (0.0000155 → 0.0000155). Prices above 1 are bit-for-bit unaffected (the sign guards were already taking the positive branch there).

Behavior note

Ranges containing values ≤ 0 previously rendered through the quasi-symlog (approximately correct only for purely negative ranges, wrong for mixed ones: a [-5, 100] range produced tick labels 0, 40, 80…). After the fix such ranges produce NaN coordinates — the axis renders no ticks instead of wrong ones, matching the mathematical domain of a log scale (same behavior as other charting libraries, e.g. TradingView treats non-positive values as invalid on log scale).

@liihuu

liihuu commented Sep 15, 2026

Copy link
Copy Markdown
Member

The Y-axis supports dragging and zooming; however, if negative values ​​appear, log(-xxx) cannot be calculated, resulting in a blank Y-axis.

@NemeZZiZZ

NemeZZiZZ commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi, thanks for checking this — your case is real, let me address it directly.

  1. Your case: drag the log axis below zero.

You are right: with this PR, log10(negative) = NaN, and the axis goes
blank. I checked _zoomYAxis — there is no clamp, so a user can drag
below zero. This needs handling, I agree.

  1. But the old code does not handle negatives either — it draws
    wrong values.

Old mapping for a negative price p: -log10(|p|).

Example — two prices, one position:

  • price 10 → real log10(10) = 1
  • price -0.1 → real -log10(0.1) = 1

Same position for 10 and -0.1. Crosshair and overlays cannot tell
them apart.

  1. Worse: the old code breaks normal prices below $1.
    This is the bug the PR fixes (issue [Bug] 使用对数轴坐标k线,价格y轴文字(yAxis.tickText)却显示负数 #836).
  • price 0.0085 → real log10(0.0085) ≈ -2.07
  • the real value is negative, but the price is positive and valid
  • old code sees "negative" and flips it: -10^2.07 ≈ -117

So the label shows "-117" for a 0.0085 price. Every coin under $1
shows negative labels. That is what users reported.

Live example — ARBUSDT 15m, price ≈ 0.168, but the log axis shows
-5.5…-6.2:
telegram-cloud-photo-size-4-5837026775253323816-y

  1. Proposal: keep pure log + clamp the range.

Instead of keeping the broken mapping, clamp from/to to positive
values in createRange. Then:

  • sub-$1 prices render correctly (this PR),
  • dragging below zero pins the range at a small positive value —
    the axis never goes blank, no NaN.

If you agree with the clamp approach, I will add it to this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants