fix: re-nice linear axis with the real plot-area length after layout#4594
Conversation
xuefei1313
left a comment
There was a problem hiding this comment.
CODE REVIEW REPORT
Files reviewed: 3
Recommendation: COMMENT
MEDIUM
packages/vchart/src/component/axis/cartesian/linear-axis.ts:89
reTransformDomainByLayout() is executed from onLayoutEnd(). This can refresh tick/axis attributes before the chart-level layoutEnd handlers run, but it still happens after layout bounds have already been measured in getBoundsInRect(). If the re-niced domain changes the ceiling label or formatter output width, axis allocation / autoIndent will still be based on the old pre-layout ticks and may clip or squeeze the plot area. The PR body also calls out this limitation. A more robust place would be after updateScaleRange() but before computeData('range') and bounds measurement, or otherwise trigger a relayout when the re-niced domain actually changes.
packages/vchart/src/component/axis/mixin/linear-axis-mixin.ts:164
This adds subtle lifecycle behavior without a regression test. Please add a focused case under packages/vchart/__tests__/unit/component/cartesian/axis/linear-axis.test.ts: mock collectData, make getChartViewRect().height larger than the real layout rect height, use a function tick.tickCount that records axisLength, and assert that after onLayoutEnd() the domain/tick count is based on the real axis length rather than the full chart viewRect.
Verification: inspected PR metadata, full diff, related axis layout/event lifecycle, and ran git diff --check origin/develop...origin/pr-4594 with no output. Full test suite was not run.
…sion test Address review on PR VisActor#4594: 1. Move the length-based re-nice from onLayoutEnd into a updateScaleRange() override on CartesianLinearAxis. getBoundsInRect() calls updateScaleRange() before computeData('range') and bounds measurement, so the axis space is now allocated from the corrected (real-plot-length) ticks within the same layout pass - no extra relayout, no clip from stale pre-layout ticks. The base onLayoutEnd also calls updateScaleRange(), so post-layout stays covered. 2. Add a focused regression test: a function tick.tickCount records axisLength; with getChartViewRect().height (500) larger than the real layout length (200), assert the domain/tickCount after the range update is based on the real axis length (200 -> [0,1000]) rather than the full chart viewRect (500 -> [0,700]).
Address review on PR VisActor#4594: - Fix TS2769: collectData is protected, so spy via jest.spyOn(CartesianAxis.prototype as any, 'collectData') instead of a misplaced // @ts-ignore. Cast the axis instance to any so the member calls type-check. - Drive the assertion through the real fix entry: stub getNewScaleRange() to return the real plot-area range and call updateScaleRange() (instead of calling reTransformDomainByLayout() directly), so the test fails if the updateScaleRange() override is removed.
…er layout
When tick.tickCount is a function (length-dependent), setLinearScaleNice runs
before layout while the scale range is still [0, 1] and falls back to the full
chart viewRect (a TODO already noted in linear-axis-mixin). The viewRect is
larger than the real plot area (it still includes title / legend / opposite
axis), so the tick count is too dense and the niced ceiling too low, and it is
never recomputed after layout.
CartesianLinearAxis.updateScaleRange() now re-nices the domain once the scale
range reflects the real plot-area pixel length. Because getBoundsInRect() calls
updateScaleRange() before computeData('range') and bounds measurement, the axis
space is allocated from the corrected ticks within the same layout pass (no
extra relayout, no clipping). It only takes effect when tickCount is a function;
fixed / default tick counts are unaffected, and a cached last-nice axis length
guards against redundant work across layout passes.
Adds a regression test asserting that, after updateScaleRange(), the domain and
tick count are based on the real axis length rather than the full chart viewRect.
ba2f17a to
073fbd3
Compare
Problem
When a cartesian linear (value) axis uses a function
tick.tickCount(i.e. the tick count depends on the axis length), the ticks come out too dense and the niced ceiling too low compared to the real plot area.The root cause is already flagged by a
TODOinlinear-axis-mixin.ts(setLinearScaleNice):setLinearScaleNiceruns before layout, whilescale.range()is still the initial[0, 1](rangeSize === 1). In that case it falls back to the whole chartviewRectto estimate the axis length. ButviewRectis larger than the real plot area (it still includes title / legend / opposite axis / padding), so:axisLengthpassed totickCount(...)is too large →tickCounttoo large → ticks too dense;After layout the scale range is updated to the real plot-area length (
onLayoutEnd → updateScaleRange), but the nice computation is never re-run, so the wrong tick count / ceiling persists.Fix
CartesianLinearAxis.onLayoutEndnow re-runs the domain + nice computation once, after the scale range reflects the real plot-area pixel length:tick.tickCountis a function (length-dependent). FixedtickCount,forceTickCount, and the default count are range-independent and are left untouched → no behavior change for existing users._lastNiceAxisLength) skips the recompute when the axis length hasn't changed, guarding against redundant work / re-entrancy across multiple layout passes.Scope / safety
tick.tickCountis a function.updateScaleDomain()path (the same path used when series data changes), so domain extensions,zero, soft min/max, axis breaks, etc. are all preserved.Known limitation
The axis space is allocated during layout using the pre-layout tick labels; if re-nicing produces a wider ceiling label, the axis width is not re-measured afterwards. In practice the niced ceiling label width is essentially unchanged, and this matches the trade-off already implied by the existing
TODO.