Skip to content

feat(BarChart): anchor non-stacked bars at value 0 when 0 is in domain#129

Closed
jamesxu-lightspark wants to merge 1 commit intomainfrom
barchart-zero-anchor
Closed

feat(BarChart): anchor non-stacked bars at value 0 when 0 is in domain#129
jamesxu-lightspark wants to merge 1 commit intomainfrom
barchart-zero-anchor

Conversation

@jamesxu-lightspark
Copy link
Copy Markdown

@jamesxu-lightspark jamesxu-lightspark commented May 1, 2026

What

Non-stacked bar charts now anchor at the zero line when the y-domain spans both positive and negative values. Negative bars hang below 0; positive bars grow up from 0.

Before this change, every bar was drawn from the bottom of the plot area up to its value — so a small negative number on a chart whose y-axis went from –150k to +100k rendered as a near-full-height bar instead of a short stub below 0. Pretty much every other charting tool (Sheets, Looker, d3 defaults, recharts) anchors at 0 for signed data, so this brings Origin in line with that expectation.

How it works

For each bar, we compute anchor = clamp(0, yMin, yMax) and draw the bar between anchor and the value:

  • All-positive data: anchor lands at yMin (the bottom), so the visual is identical to before.
  • Mixed signs: anchor is 0 — positive values grow up, negatives hang down.
  • All-negative data: anchor lands at yMax (the top), so bars hang down to their value.

The same treatment applies to the horizontal orientation. The stacked rendering path is intentionally untouched — cumulative bars already have their own semantics that differ from the simple value → height mapping.

Why now

Came up while building a daily net inflow/outflow bar chart in lighthouse. The chart was rendering with all bars anchored at the plot bottom, which made small negative days look as dramatic as the worst negative day. Fixing it at the Origin layer makes every consumer get the right behaviour for free.

Test plan

  • Existing storybook bar charts (all-positive) render identically — visual diff is a no-op.
  • Mixed-sign story: bars cross the zero line cleanly, no overlap with the axis.
  • Horizontal orientation: same treatment, bars extend left of the zero column for negatives.
  • Stacked path unchanged.

Non-stacked bar charts on signed data (e.g. net = inflow - outflow) used
to draw every bar from the bottom of the plot area up to its value, so
negative values produced large bars hugging the lower edge instead of
short bars hanging below the zero line.

Anchor each bar at clamp(0, yMin, yMax) so:
  - All-positive data: anchor = yMin (bottom) — same as before.
  - Mixed signs: anchor = 0; positive bars grow up, negative bars grow down.
  - All-negative data: anchor = yMax (top); bars hang down to value.

Same treatment applied to the horizontal orientation. Stacked path is
unchanged (cumulative semantics already differ).
@jamesxu-lightspark
Copy link
Copy Markdown
Author

@greptile review

@jamesxu-lightspark
Copy link
Copy Markdown
Author

Closing — went with a recharts-based renderer in lighthouse instead of waiting on Origin. Leaving this here as a reference for the eventual Origin-side fix; once it lands, lighthouse can drop the recharts branch and consolidate back to OriginChart.Bar.

@jamesxu-lightspark
Copy link
Copy Markdown
Author

Reopening — keeping this open as the proper long-term fix. Lighthouse currently bridges via a small recharts-based SignedBarChart for the signed-bar use case, but the plan is to drop that bridge and use Origin directly once this lands.

@jamesxu-lightspark
Copy link
Copy Markdown
Author

Moved to the new monorepo per @coreymartin: https://github.com/lightsparkdev/webdev/pull/26977

lightspark-copybara Bot pushed a commit to lightsparkdev/js-sdk that referenced this pull request May 1, 2026
…n domain (#26977)

## What

Small change to BarChart so signed-value bars anchor at the zero line —
negatives hang down, positives grow up — instead of all rendering from
the plot bottom. Sheets, Looker, d3 defaults, and recharts all do this;
Origin was the odd one out.

For each non-stacked bar we compute `anchor = clamp(0, yMin, yMax)` and
draw between `anchor` and the value:

- **All-positive data** — anchor lands at `yMin` (bottom). Visual
identical to before.
- **Mixed signs** — anchor is `0`. Positives grow up, negatives hang
down.
- **All-negative data** — anchor lands at `yMax` (top). Bars hang down
to their value.

Same treatment applied to the horizontal orientation. Stacked path is
intentionally untouched — cumulative semantics already differ from the
simple value→height mapping.

## Why

Came up while building a daily net inflow/outflow bar chart in
lighthouse — the chart's domain spanned negative values, but every red
bar was rendered from the bottom of the plot area up to the value, which
made small negative days look as severe as the worst negative day.

## Not a breaking change

- No API change — no props added, removed, or retyped.
- All-positive data renders pixel-identical (`clamp(0, yMin, yMax) =
yMin` when yMin is 0).
- Only diffs are mixed-sign and all-negative charts, which were arguably
broken before this.

## Notes

- Originally proposed against the old origin repo at
lightsparkdev/origin#129; moved here per @coreymartin.
- Lighthouse currently has a small recharts-based bar chart bridging the
signed-data case
([lighthouse#383](lightsparkdev/lighthouse#383)).
Plan is to drop that bridge and use Origin directly once this lands.

## Test plan
- [ ] Existing storybook bar charts (all-positive) render identically —
visual diff is a no-op.
- [ ] Mixed-sign story: bars cross the zero line cleanly.
- [ ] Horizontal orientation: bars extend left of the zero column for
negatives.
- [ ] Stacked path unchanged.

GitOrigin-RevId: 807866e8c7aa64e986b8b31f370630e162cabb6c
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