Skip to content

Fix *auto* text of zero-length bar traces - #8078

Open
william-xue wants to merge 4 commits into
plotly:mainfrom
william-xue:bar-auto-text-zero-length
Open

william-xue wants to merge 4 commits into
plotly:mainfrom
william-xue:bar-auto-text-zero-length

Conversation

@william-xue

@william-xue william-xue commented Sep 25, 2026 •

Copy link
Copy Markdown

Fixes #6474.

What changes

src/traces/bar/plot.js returned early for every blank bar when textposition was *auto* or *inside*, so a bar of zero length never drew its text. The early return now covers *inside* placement and bars that have no position at all (null values). Text with *auto* placement falls through to the existing fit check, which finds no room inside a zero-length bar and places the text outside.

*outside* text already drew for zero-length bars, so *auto* and *outside* now agree.

On the behavior change

Issue #6474 reports this same behaviour, and the maintainer record on it is split. alexcjohnson (collaborator) wrote on 2023-02-09 that it "does look like a bug to me, and certainly a zero-size bar is different from no bar", and that the fix "may be as simple as removing that textPosition === 'auto' || line (and adding this situation to a test image)". This branch does exactly that removal, and the moved baselines are the test image.

The other reading is on record too: in the equivalent plotly.py report (plotly/plotly.py#4686) archmoj wrote on 2024-07-24 "I'm not sure we should consider this a bug", pointing at explicit textposition as the way to choose the placement, and gvwilson agreed it was "a feature rather than a bug".

The attribute description in src/traces/bar/attributes.js states the contract for *auto* as well: the text is moved outside when the bar is too small and nothing is stacked on it. A zero-length bar is the extreme case of too small, and *outside* already draws its text today, so before this change the two documented placements disagreed with each other.

Bars that carry no value at all keep their current behaviour: a null value still produces no text, and the new test asserts that.

Validation

plotly-4.1.1.min.js from the CDN, loaded in headless Chrome. One page holds two figures; the .barlayer .bartext nodes come from the DOM after newPlot.

trace before after
y: [0, 5, 10], textposition: 'auto', text: ['ZERO', 'FIVE', 'TEN'] FIVE, TEN ZERO (bartext-outside, translate(96.66, 334)), FIVE, TEN
y: [null, 5, 10], textposition: 'auto', text: ['NULLPT', 'FIVE', 'TEN'] FIVE, TEN FIVE, TEN (unchanged)

The right column comes from the same page with the change applied to the bundle copy.

Tests

  • test/jasmine/tests/bar_test.js: new case inside the A bar plot describe, should draw *auto* text of zero-length bars outside. It fails before the change, because no ZERO node exists, and it asserts that a null value keeps its text hidden.

Baselines

Thirteen baselines move. They are the baselines-default-diff artifact of the test-baselines job from the CI run on 07834db0 (ubuntu-latest), so they carry the renderer the job uses rather than a local machine.

Every one keeps its dimensions, and diffing each updated image against the previous baseline shows label-sized deltas only — 0.02% to 0.38% of the pixels, with the largest change in blank-bar-outsidetext.png:

  • bar mocks, whose text is laid out by the code path that changed: bar_attrs_relative.png, round-bar_attrs_relative.png, bar_display_height_zero.png, bar_display_height_zero_no_line_width.png, bar_display_height_zero_only_line_width.png, rounded-bar_display_height_zero_no_line_width.png, bar_hide_nulls.png, bar-marker-line-colorscales.png, blank-bar-outsidetext.png
  • funnel mocks, which plot through src/traces/funnel/plot.js → barPlot.plot and therefore use the same text placement: funnel-grouping-vs-defaults.png, funnel-offsetgroups.png, funnel_horizontal_stack_basic.png, funnel_nonnumeric_sizes.png

map_* and scattermap_dynamic_defaults are deliberately left alone: those mocks cannot render without a Mapbox token.

I did not run the karma suites locally.

The draft log follows in a second commit, since its filename needs this PR number.

A zero-length bar kept its text hidden when textposition was
*auto*, while *outside* text was drawn. Only hide text for a bar
with no position or for *inside* placement.
@camdecoster camdecoster self-assigned this Sep 29, 2026
camdecoster and others added 2 commits October 21, 2026 20:11
Baselines taken from the ubuntu-latest CI renders of run 39709994398 (per CONTRIBUTING: download from the test-baselines job). Only the mocks whose text placement changes with this fix: bar/funnel zero-length cases. map_* and scattermap_dynamic_defaults are left untouched - they differ only because Mapbox tokens are not available to PR runs.
@camdecoster

Copy link
Copy Markdown
Contributor

Thanks for the PR. This changes behavior that might be considered a feature, so I'm going to pause on reviewing until I discuss this with my team.

@william-xue

Copy link
Copy Markdown
Author

Understood — no rush on the review.

For the discussion, both sides of the record are now in the description:

  • alexcjohnson on bar chart: textposition='auto' not working for 0 values #6474 (2023-02-09): "does look like a bug to me, and certainly a zero-size bar is different from no bar"
  • archmoj on plotly.py#4686 (2024-07-24), with gvwilson agreeing: not a bug / a feature, with explicit textposition as the way to pick the placement

What tipped it for me was consistency rather than preference: the textposition description in src/traces/bar/attributes.js says *auto* moves the text outside when the bar is too small, and *outside* already draws the label on a zero-length bar today — so the two documented placements disagreed before this change. The scope is zero-length bars only; a null bar still gets no text, and there is a test for that.

If the team lands on the current behaviour being intended, say so and I'll close this.

@camdecoster

Copy link
Copy Markdown
Contributor

My team and I decided that we do want to default to showing labels for zero-length bars, but it will have to be handled correctly and will take more than 6 lines to get it right. You can see that a number of the updated baseline images over label overlap problems. We'd like to avoid that.

Here's a table comparing some different ideas I've been working on. It will take some iterating to get this right. What are your thoughts?

image

@william-xue

Copy link
Copy Markdown
Author

Good that the direction is settled. The overlap point is fair, and it's the part my patch doesn't handle.

Where it comes from: a zero-length bar now falls through to the same toMoveOutsideBar path as textposition: 'outside', and that path puts the label TEXTPAD (2px) past the bar end, centred on the bar. For a zero bar the bar end is the zero line, so:

  • the label box sits on the axis line, with nothing keeping it clear of tick labels or gridlines;
  • in grouped mode the slot is plotWidth / (categories * series) wide, and once that is narrower than the label (a 0 is ~7px at the 12px default font) neighbouring zero bars' labels overlap each other;
  • in stack/relative only the outmost bar of a stack draws, so those are fine today - the collisions are the side-by-side ones.

What I'd try before any offset/stagger scheme: cull in pixel space rather than move anything. appendBarText already measures the label with Drawing.bBox when it picks inside vs outside, so the box is there for free - keep a per-subplot list of zero-bar label boxes while the bar loop runs, and skip a zero-bar outside label whose box intersects one already drawn. Isolated zeros stay labelled (that's the case #6474 is about), crowded ones drop out instead of overlapping, and the baseline churn should shrink with it. If we also want them off the axis line, the pad wants to be max(TEXTPAD, ticklen) rather than 2px.

One unrelated thing I noticed: the existing blank-bar-outsidetext mock has its auto case labelled "should not see" - whichever way we go, that fixture needs a new text and expectation.

I can't read the ranking off the screenshot you attached. Tell me which variant you lean toward and I'll prototype it here - or I can put the culling version up first so you have something concrete to compare against.

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.

bar chart: textposition='auto' not working for 0 values

2 participants