Skip to content

fix(echarts): prevent x-axis time labels from overlapping - #43669

Merged
sadpandajoe merged 3 commits into
masterfrom
sc-119084-xaxis-label-overlap
Sep 1, 2026
Merged

sadpandajoe merged 3 commits into
masterfrom
sc-119084-xaxis-label-overlap

Conversation

@sadpandajoe

@sadpandajoe sadpandajoe commented Aug 29, 2026 •

Copy link
Copy Markdown
Member

SUMMARY

On a time-series chart with the default configuration (0° x-axis label rotation, a resolved time
grain), ECharts' native label-collision suppression (axisLabel.hideOverlap) is disabled. This is a
deliberate trade-off from a prior fix that guarantees the forced boundary/max date label is never
hidden by ECharts' own overlap detection, but it has the side effect of disabling overlap suppression
for every other tick on the axis too — so dense/closely-spaced time labels can render on top of each
other instead of being thinned or spaced out.

This adds label-level overlap suppression instead of touching the axis-level toggle: a shared
createSpacedXAxisFormatter + getXAxisDomain pair (utils/formatters.ts) estimates each label's
rendered pixel footprint against the available per-tick spacing and blanks a label that would
visually collide with the previously-rendered one, while always keeping the forced boundary labels
visible. axisLabel.hideOverlap stays false and the existing boundary-label regression tests are
untouched.

Applied identically to Timeseries and MixedTimeseries, replacing the previously duplicated inline
formatter logic in each (which only deduplicated exact string-duplicate labels, not visually-colliding
ones).

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Reproduced with a dense, minute-level time series (X-Axis Label Interval = All, Time Grain = Minute,
X-Axis Time Format = %Y-%m-%d %H:%M:%S, 0° rotation) — the density needed to trigger the collision.

Before (labels stacked on top of each other, unreadable):

Before: overlapping x-axis labels

After (labels cleanly spaced, no overlap):

After: no overlapping x-axis labels

TESTING INSTRUCTIONS

cd superset-frontend
npm run test -- plugins/plugin-chart-echarts/test

New regression tests reproduce dense/closely-spaced time labels for both Timeseries and
MixedTimeseries (confirmed RED before the fix, GREEN after). Full plugin-chart-echarts suite:
994/994 passing.

Manually: create a time-series chart (Line/Area/Bar) with a temporal x-axis and a time grain fine
enough relative to the selected date range that many points/ticks land close together (e.g. Day grain
over several months), leave X-Axis Label Rotation at its default (0°) — x-axis labels no longer
overlap, and the first/last date labels remain visible.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@codecov

codecov Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.75000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.30%. Comparing base (e683aad) to head (66ae59d).

Files with missing lines Patch % Lines
...ugins/plugin-chart-echarts/src/utils/formatters.ts 93.33% 4 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master   #43669   +/-   ##
=======================================
  Coverage   79.29%   79.30%           
=======================================
  Files        2889     2889           
  Lines      166762   166788   +26     
  Branches    38604    38620   +16     
=======================================
+ Hits       132241   132266   +25     
- Misses      32027    32028    +1     
  Partials     2494     2494           
Flag Coverage Δ
javascript 74.78% <93.75%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sadpandajoe
sadpandajoe marked this pull request as ready for review August 29, 2026 04:37
@dosubot dosubot Bot added viz:charts:echarts Related to Echarts viz:charts:timeseries Related to Timeseries labels Aug 29, 2026
Comment on lines +1219 to +1227
const deduplicatedFormatter = showMaxLabel
? (() => {
let lastLabel: string | undefined;
let lastValue: number | undefined;
const wrapper = (value: number | string) => {
// ECharts formats the labels in repeated ascending passes. Reset the
// dedup state when the sequence restarts so a forced boundary label
// (e.g. the min date) isn't blanked by the previous pass's last label
// when both format identically (e.g. a May-to-May range).
if (
typeof value === 'number' &&
lastValue !== undefined &&
value <= lastValue
) {
lastLabel = undefined;
}
if (typeof value === 'number') {
lastValue = value;
}
const label =
typeof xAxisFormatter === 'function'
? (xAxisFormatter as Function)(value)
: String(value);
if (label === lastLabel) {
return '';
}
lastLabel = label;
return label;
};
if (typeof xAxisFormatter === 'function' && 'id' in xAxisFormatter) {
(wrapper as any).id = (xAxisFormatter as any).id;
}
return wrapper;
})()
? createSpacedXAxisFormatter(
xAxisFormatter,
...getXAxisDomain(
[rebasedData as Record<string, unknown>[]],
xAxisLabel,
),
Math.max(width - 2 * TIMESERIES_CONSTANTS.gridOffsetLeft, 0),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The spacing calculation assumes the plot width is the full chart width minus only the fixed 20px left and right offsets. The actual ECharts plot is further reduced by y-axis title margins, legend placement, and other padding, so this overestimates available label space and can leave labels visually overlapping when hideOverlap is disabled. Base the calculation on the effective grid width. [logic error]

Severity Level: Major ⚠️
- ⚠️ Dense labels can overlap with left-side axis titles.
- ⚠️ Timeseries charts use incorrect available x-axis width.
- ⚠️ MixedTimeseries has the same approximation at lines 672-682.

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
**Line:** 1219:1227
**Comment:**
	*Logic Error: The spacing calculation assumes the plot width is the full chart width minus only the fixed 20px left and right offsets. The actual ECharts plot is further reduced by y-axis title margins, legend placement, and other padding, so this overestimates available label space and can leave labels visually overlapping when `hideOverlap` is disabled. Base the calculation on the effective grid width.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — per rebenitez1802's review, this is a real but acceptable heuristic. Cheap improvement noted: swap the fixed width - 2 * gridOffsetLeft for the already-computed padding.left/padding.right insets. Leaving as a follow-up rather than blocking this fix.

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. The current spacing calculation uses a simplified width (width - 2 * TIMESERIES_CONSTANTS.gridOffsetLeft) which ignores additional ECharts layout elements like y-axis titles, legends, and padding, leading to an overestimation of available space and potential label overlap.

To resolve this, you should calculate the effective grid width by accounting for these additional margins. In transformProps.ts, you can access these values from the chart props or ECharts configuration and subtract them from the total width before passing the value to createSpacedXAxisFormatter.

Would you like me to fetch all other comments on this PR to validate and implement fixes for them as well?

superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts

Math.max(width - 2 * TIMESERIES_CONSTANTS.gridOffsetLeft, 0),

Comment on lines +58 to +60
xAxisLabelCharWidthPx: 7,
// Minimum gap (px) to keep between adjacent x-axis time labels.
xAxisLabelMinGapPx: 8,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The collision threshold assumes every rendered character is exactly seven pixels wide, but the formatter is also used for localized and custom time formats. Wide glyphs or long localized month names can exceed this estimate, causing the wrapper to retain labels whose actual rendered footprints overlap; narrow glyphs can also cause valid labels to be unnecessarily removed. Use measured text width or a conservative width calculation based on the actual formatter output and font. [possible bug]

Severity Level: Major ⚠️
- ⚠️ Custom time formats can remain visually overlapped.
- ⚠️ Localized month labels may be thinned inaccurately.
- ⚠️ Non-overlapping labels may be unnecessarily hidden.

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/plugins/plugin-chart-echarts/src/constants.ts
**Line:** 58:60
**Comment:**
	*Possible Bug: The collision threshold assumes every rendered character is exactly seven pixels wide, but the formatter is also used for localized and custom time formats. Wide glyphs or long localized month names can exceed this estimate, causing the wrapper to retain labels whose actual rendered footprints overlap; narrow glyphs can also cause valid labels to be unnecessarily removed. Use measured text width or a conservative width calculation based on the actual formatter output and font.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — per rebenitez1802's review, the fixed 7px/char estimate is a real but acceptable heuristic (worst case still beats no suppression). A measureText-based estimate would be a nice follow-up, not blocking this fix.

Comment on lines +311 to +316
const value = record[xAxisCol];
if (typeof value === 'number') {
if (domainMin === undefined || value < domainMin) domainMin = value;
if (domainMax === undefined || value > domainMax) domainMax = value;
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: getXAxisDomain only recognizes numeric values, but temporal query rows can contain timestamp strings or Date objects. For those charts both bounds remain undefined, so pixelsPerMs is disabled and, because hideOverlap is simultaneously set to false, no overlap suppression is applied at all. Normalize temporal values to epoch milliseconds before calculating the domain. [logic error]

Severity Level: Major ⚠️
- ❌ Dense string-timestamp charts retain overlapping labels.
- ❌ Date-valued query records bypass spacing suppression.
- ⚠️ Timeseries and MixedTimeseries are both affected.

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts
**Line:** 311:316
**Comment:**
	*Logic Error: `getXAxisDomain` only recognizes numeric values, but temporal query rows can contain timestamp strings or `Date` objects. For those charts both bounds remain undefined, so `pixelsPerMs` is disabled and, because `hideOverlap` is simultaneously set to false, no overlap suppression is applied at all. Normalize temporal values to epoch milliseconds before calculating the domain.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — per rebenitez1802's review, this looks like a false positive on the standard pipeline: the chart-data API serializes temporal columns to epoch-ms numbers, so the numeric-only filter in getXAxisDomain is correct there. Not blocking this fix.

@bito-code-review

bito-code-review Bot commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #28dbff

Actionable Suggestions - 0
Additional Suggestions - 2
  • superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts - 2
    • Dedup reset defeats boundary blanking · Line 259-259
      The reset at line 259 uses `value <= lastValue`, so when the forced boundary label (showMaxLabel) formats identically to the last real tick at the same value, the reset clears `lastLabel` right before the dedup check, and the duplicate is shown instead of blanked — defeating the function's stated purpose. Use `value < lastValue` so only a genuinely new ascending pass resets state.
    • Missing unit tests for new logic · Line 238-297
      The new `createSpacedXAxisFormatter`/`getXAxisDomain` logic (dedup, spacing heuristic, pass-restart reset) has no unit tests, and the reset-condition bug above slipped through. Add tests asserting the actual blanking behavior rather than just rendering, per the repo's testing guidance.
Review Details
  • Files reviewed - 6 · Commit Range: 246dc83..84552d4
    • superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
    • superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
    • superset-frontend/plugins/plugin-chart-echarts/src/constants.ts
    • superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts
    • superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts
    • superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sadpandajoe LGTM, thanks for the fix (and for consolidating the duplicated formatter logic while you were in there). The bot threads all seem to be poking at the same thing: the width heuristics. I think those are fine as-is, since the worst case still beats no suppression at all... but a measureText-based estimate could be a nice follow-up. Approving!

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes: the core approach (label-level thinning while keeping hideOverlap: false) is sound and consolidating the duplicated inline formatters is a genuine improvement — but this introduces a default-settings regression for horizontal time charts, and a private tracker ID leaked into the new test names. The bot threads above flag real-but-acceptable heuristics; the horizontal regression is the one thing not yet covered.

🔴 High — Horizontal charts: the new spacing formatter over-thins the (vertical) time axis

createSpacedXAxisFormatter is applied whenever showMaxLabel is true (Timeseries/transformProps.ts:1214), gated only on xAxisType === AxisType.Time && rotation === 0 && resolvedTimeGrain — with no !isHorizontal guard. getAxisType returns AxisType.Time for any temporal column regardless of orientation, so this fires on horizontal bar/line charts at the default 0° rotation. But on horizontal orientation the time axis is swapped to run vertically ([xAxis, yAxis] = [yAxis, xAxis] at :1373), while the spacing model uses horizontal geometry (plotWidthPx = width − 40 and label.length * 7px).

Concretely: 24 monthly points at width=800 → computed per-tick spacing ≈ 33px vs a ≈ 64px threshold → all ~22 interior labels are blanked, only the two boundaries survive. On master the formatter was dedup-only, so every distinct label rendered. This is pure loss (vertically-stacked labels weren't overlapping horizontally to begin with), not a "worst case beats no suppression" trade-off.

Fix: gate the spacing formatter on showMaxLabel && !isHorizontal and fall back to the plain consecutive-dedup wrapper when horizontal.

🟡 Medium — Horizontal charts: the !isHorizontal guard on alignMaxLabel/alignMinLabel was dropped

On master, alignMaxLabel: 'right' / alignMinLabel: 'left' sit in a separate showMaxLabel && !isHorizontal spread, with a comment noting the alignments assume the axis runs along the bottom and misplace labels when a horizontal chart puts the axis on the side. This PR collapses everything into one unconditional block (Timeseries/transformProps.ts:1257-1262), so those alignment hints now apply to the side-mounted vertical axis — a direct revert of that previously-fixed behavior.

Fix: restore the separate ...(showMaxLabel && !isHorizontal && { alignMaxLabel: 'right', alignMinLabel: 'left' }) spread, keeping showMaxLabel/showMinLabel unconditional. (MixedTimeseries has no horizontal mode, so it needs no change.)

🟡 Medium — Forced boundary label can regress #39899 when the axis extent ≠ the data extent

isBoundary keys on value === domainMin || value === domainMax, where the domain comes from getXAxisDomain(rebasedData) — the query rows only. The time x-axis gets no explicit min/max (getMinAndMaxFromBounds returns {} for non-Value axes), so ECharts derives the extent from the union of all series on axis 0, including timeseries/overlay annotation lines (transformTimeseriesAnnotation pushes a type:'line' series) and time_shift overrides. When an overlay extends past the last data point, ECharts calls the formatter with a max boundary tick > domainMax, isBoundary is false, and the spacing check can blank the forced last label — the exact #39899 symptom.

It's self-mitigating (the pixelsPerMs computed from the narrower data span biases toward showing the label), so it's narrow — but the mechanism is real and the current tests can't catch it (see below). Worth protecting the first/last formatter invocation of each ascending pass positionally rather than matching against the data-derived domain.

🟢 Low — plotWidth = width − 40 ignores the real grid inset (and degenerates at tiny widths)

Math.max(width - 2 * TIMESERIES_CONSTANTS.gridOffsetLeft, 0) (Timeseries/transformProps.ts:1226) overestimates the plot area — with grid.containLabel the y-axis label + title width are reserved inside that box — so pixelsPerMs runs high and dense labels under-thin (residual overlap persists on charts with wide y-labels). This is the width thread above; it's an acceptable heuristic, but padding is already computed a few lines up, so improving it is nearly free: Math.max(width - (padding.left ?? 0) - (padding.right ?? 0), 0). Note the opposite degenerate too: when width ≤ 40, plotWidthPx clamps to 0 → pixelsPerMs = 0 → every non-boundary label blanked (thumbnails / tiny dashboard tiles).

🟢 Low — New tests: a private internal tracker ID is embedded in the test names

The two new tests are named after an internal Preset/Shortcut story ID (the SC-… prefix) — test/Timeseries/transformers.test.ts:355 and test/MixedTimeseries/transformProps.test.ts:1327. That ID has no meaning to OSS contributors and shouldn't land in the public history. Fix: rename to a behavioral title referencing the public issue, e.g. '#39899 - closely spaced x-axis time labels do not visually overlap'.

🟢 Low — Tests assert "thinning happened," not "thinning is sufficient"

Both tests only check hideOverlap === false, labels.filter(l => l === '').length > 0, and that the first/last labels are non-empty. A formatter that blanks a single interior label while leaving 19 dense ones overlapping would still pass. And because they build labels via data.map(ts => formatter(ts)), the last value always equals domainMax, so isBoundary short-circuits and the boundary-protection path is never exercised. Fix: assert adjacent surviving labels are at least the collision threshold apart, and add a case that calls the formatter with a trailing value > max(data) to exercise a boundary tick that differs from domainMax.


On the existing bot threads: the "string/Date timestamps" thread looks like a false positive on the standard pipeline — the chart-data API serializes temporal columns to epoch-ms numbers (json_int_dttm_ser), so getXAxisDomain's numeric filter is correct there; a defensive coercion is optional, not required. The grid-width and fixed-7px/char threads are real but acceptable heuristics (the width one has the cheap fix noted above).

Net: a solid fix that clearly improves the common vertical case — please add the !isHorizontal guard (High + Medium) and rename the two tests before merge; the rest are non-blocking.

@netlify

netlify Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 66ae59d
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a9729efc8a7760008c6ace6
😎 Deploy Preview https://deploy-preview-43669--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

bito-code-review Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #0e7118

Actionable Suggestions - 0
Review Details
  • Files reviewed - 5 · Commit Range: 84552d4..a516f57
    • superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
    • superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts
    • superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts
    • superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts
    • superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rebenitez1802 rebenitez1802 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — thanks for the quick turnaround, @sadpandajoe. a516f57 resolves everything I flagged in round 1, with good regression coverage. Verified against the delta:

  • Horizontal over-thinning (was High) — fixed: deduplicatedFormatter now falls back to the new createDedupXAxisFormatter when isHorizontal, so the horizontal-plot spacing geometry is no longer applied to the side-mounted time axis. The dedup helper is a faithful extraction of the previous inline logic (same pass-reset, dedup, and .id propagation), so horizontal charts behave exactly as before. The #39899 - horizontal orientation does not over-thin the time axis labels test locks it in.
  • Boundary-label alignment guard (was Medium) — fixed: alignMaxLabel/alignMinLabel are back behind the showMaxLabel && !isHorizontal spread (with the explanatory comment), while showMaxLabel/showMinLabel stay unconditional. The new alignment test codifies both orientations.
  • Test names (was Low) — fixed: renamed to reference the public issue.

Scope looks right — MixedTimeseries has no horizontal orientation mode, so it correctly needs no guard.

The remaining bot-thread items (grid-width estimate, fixed px/char) are acceptable heuristics and good follow-up candidates as discussed; the annotation/overlay boundary-extent edge case is narrow and self-mitigating. None block. Nice work consolidating the formatters.

sadpandajoe and others added 3 commits September 1, 2026 18:44
When showMaxLabel is active (the default for most timeseries charts),
axisLabel.hideOverlap must stay off to keep the forced boundary label
visible (#39899), which also disabled ECharts' own overlap suppression
for every other tick. Dense/closely-spaced time data would then render
with all labels overlapping.

Extend the x-axis label formatter to estimate each label's pixel
footprint against the available per-tick spacing and blank labels that
would visually collide, while always keeping the forced axis boundary
labels visible. Applies to both Timeseries and MixedTimeseries charts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Timeseries/transformProps.ts assigns xAxisFormatter as TimeFormatter |
StringConstructor | NumberFormatter | undefined, since it can be a
NumberFormatter when xAxisDataType is Numeric. The showMaxLabel branch
passes it to createSpacedXAxisFormatter, whose XAxisFormatterFn type
omitted NumberFormatter, causing a tsc build failure even though that
branch only runs for AxisType.Time (which never pairs with Numeric
data). NumberFormatter is callable like TimeFormatter, so widening the
union is safe.
…l orientation

The x-axis label spacing formatter estimated collisions using
horizontal-plot geometry (chart width, char width in px), but a
horizontal-orientation chart swaps the time axis onto the side of the
chart where that geometry no longer applies, causing most interior
labels to be blanked. Fall back to the dedup-only formatter there, and
restore the horizontal-orientation guard on the boundary label
alignment hints that was dropped when the formatters were consolidated.

Also renames two regression tests that had leaked an internal tracker
ID into their titles.
@sadpandajoe
sadpandajoe force-pushed the sc-119084-xaxis-label-overlap branch from a516f57 to 66ae59d Compare September 1, 2026 19:39
@sadpandajoe
sadpandajoe merged commit ba50bdf into master Sep 1, 2026
81 checks passed
@sadpandajoe
sadpandajoe deleted the sc-119084-xaxis-label-overlap branch September 1, 2026 20:23
@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants