fix: stabilize y-axis width against live label changes - #851
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The y-axis column width is measured live, every layout, from texts that move:
getAutoSize(). When scrolling across a magnitude boundary (e.g.999,950.00→1,000,050.00), the widest label gains a character and the column jumps.close, which changes on every tick.range.displayTo, which moves on every frame of a scroll/zoom.On top of that, the existing
cacheYAxisWidthmechanism is a grow-only ratchet (Math.max(cache, current)) applied on scroll/zoom/data-update layout paths (Store.ts), while the deferred TaskScheduler layout recomputes without the cache — the two policies fight, so the column pumps in both directions.Since the plot area is chart width minus this column, every width change shifts the entire chart: the scale visibly trembles while scrolling at large magnitudes.
Measured (production UMD, 3000 bars trending 600k → 2M, programmatic scroll)
Hover-scroll at the 1M digit boundary (alternate the visible window across it every 50 ms, 40 steps):
84px ↔ 74pxon every step84px → 76px → 84px, then stableSingle-direction sweep across the boundary: both versions step once per decade crossing (unavoidable — a wider label must fit); this PR grid-rounds the step (74px → 76px) and, unlike the ratchet, allows the column to come back.
Fix
Stabilize what the width is measured from, and settle what is reported:
stableWidthText— the last-price mark and the crosshair reservation are measured from a stand-in of the same shape: every digit replaced by a fixed representative (8), with the minus reserved whenever the range reaches below zero. The width then depends on how many characters the label has — which changes when the data changes magnitude, not on every frame. (Digits are tabular in the default font, and the grid below absorbs any residual difference.)widthAnchorValue— the crosshair can read anywhere in the range, so its reservation is sized from the top of the decade the range is in (9.99 × 10^⌊log10(max|from|,|to|)⌋) instead of the movingdisplayTo. Within a decade it does not move; it changes once when the data genuinely changes magnitude, and never under-reserves (every value in a decade is shorter than its top).cacheYAxisWidthratchet removed (Chart.layoutoption + the fourStorecall sites). Its grow-only clamp would mask the settle logic — the width could never come back on exactly the paths that matter — and its purpose (prevent width pumping during scroll) is now served at the source by 1–3. The measurement work itself was never skipped by the cache, so this is not a perf regression.No public API changes;
yAxis.sizefixed-width configs bypass the new logic as before.