Skip to content

Add axis side placement for right-to-left locales - #98

Open
3li7alaki wants to merge 2 commits into
TanStack:mainfrom
3li7alaki:axis-side
Open

Add axis side placement for right-to-left locales#98
3li7alaki wants to merge 2 commits into
TanStack:mainfrom
3li7alaki:axis-side

Conversation

@3li7alaki

@3li7alaki 3li7alaki commented Aug 14, 2026

Copy link
Copy Markdown

Closes #87.

Why

Arabic, Hebrew, and Farsi read right to left, and in those locales the value
axis belongs on the right. That is not a stylistic preference: the reading eye
starts at the right edge, so a left-hand value axis puts the scale behind the
data instead of in front of it.

@tanstack/charts had no lever for it. ChartAxisPresentationOptions covered
the line, ticks, tick labels, and title, but never the placement, so the y axis
always resolved against the left plot edge. x.reverse already ordered
categories right to left, which made the axis the single remaining element
pointing the wrong way.

The usual workarounds are not workarounds. transform: scaleX(-1) on the
container mirrors the marks and the tick text and breaks pointer hit-testing.
direction: rtl on the host changes nothing, because placement is resolved
during layout rather than by the browser.

We hit this migrating a bilingual production dashboard off Recharts, which
covers the case with <YAxis orientation="right" />. Everything else about the
migration was an improvement, so we shipped the Arabic locale with a knowingly
misplaced axis. This patch is the fix for that, written so the same option
serves anyone who wants a right-hand or top axis in any locale.

@Thom-ASM pointed at scene.ts in the issue thread, which was the right place
to start.

API

const chart = {
  x: { scale: xScale, reverse: true },
  y: { scale: yScale, axis: { side: 'end' } },
}

ChartAxisSide is 'start' | 'end' rather than physical literals. The y axis
reads start as left and end as right; the x axis reads start as bottom and
end as top. One spelling covers both dimensions, composes with the existing
reverse, and keeps a right-to-left chart to a single flag per axis rather than
a per-axis vocabulary the caller has to memorize.

side defaults to start, so every existing chart is untouched.

Implementation

axisPlacement() resolves each axis once into a plot edge and an outward sign,
and every coordinate derives from that pair: the axis line, the tick stubs, the
tick labels and their default anchor, the title with its rotation, and the
crosshair value label. No site branches on the side on its own.

Two things fell out of the existing design rather than needing new code:

  • Automatic margins already grow from measured label bounds, so the reserved
    gutter moves to the other edge on its own.
  • Every renderer and adapter consumes the same scene nodes, so SVG, canvas, and
    all twelve packages follow without a change.

The crosshair is included because it is the other half of a placed axis. Its
value labels are resolved outside createAxes, so SceneFocusGuideAxis carries
the side and the resolver derives the same placement from the guide's own plot
bounds.

Compatibility

side is optional and defaults to the current behavior. A test asserts that an
explicit 'start' produces a scene node tree identical to an unset side, so
this is provably inert for existing charts rather than only intended to be.

Verification

  • 901 charts-core tests pass, the 897 existing ones unchanged.
  • New scene tests cover the end-side y axis against the right plot edge (line,
    stubs, label anchor, 90 degree title, and the margin moving from left to
    right), the end-side x axis against the top edge, and the start-side
    equivalence above.
  • A new crosshair test asserts both value labels follow the placement, anchor
    included.
  • All 188 catalog previews regenerate byte-identically; only the manifest
    sourceHash changes, which is a second check that default rendering did not
    move.
  • Rendered SVG reviewed in all three configurations, including an Arabic locale
    with Eastern Arabic numerals: default, y.side: 'end' with x.reverse, and
    x.side: 'end'.

Rendered output: default start side, end side with x.reverse in Arabic, and an end-side x axis on top

Changeset, root docs, and an API-FRICTION.md entry (F-285) are included per
CONTRIBUTING.md and AGENTS.md. Package versions and changelogs are left to
the automated version pull request.

Two notes on the baselines

The comparison bundle baseline is refreshed in its own commit, following the
existing convention. Competitor measurements reproduced byte for byte, and only
the TanStack entries, the input digest, and the timestamp move.

benchmarks/bundle-size/universal-baseline.json is deliberately not
refreshed. It already mismatches on unmodified main in my environment, so I
could not separate a real delta from local drift and did not want to lock in
numbers measured on my laptop. For the record, the placement code adds roughly
190 B gzip to the React adapter here. Say the word and I will include the
refresh, or leave it to a maintainer run.

Resolve each axis to a plot edge and an outward sign so `axis.side` can move
the line, stubs, tick labels, title, and crosshair value label to the opposite
edge. Automatic margins follow the placement, and an unset side keeps the
existing scene output.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ccaae431-072e-49a8-b059-23d52b61ee6a

📥 Commits

Reviewing files that changed from the base of the PR and between b7b3f0f and 3b8cda9.

📒 Files selected for processing (16)
  • .changeset/opposite-axis-side.md
  • API-FRICTION.md
  • benchmarks/comparison/bundle-baseline.json
  • benchmarks/conformance/previews/manifest.json
  • docs/concepts/layout-axes-and-coordinates.md
  • docs/reference/types.md
  • packages/charts-core/docs/concepts/layout-axes-and-coordinates.md
  • packages/charts-core/docs/reference/types.md
  • packages/charts-core/src/crosshair-resolver.ts
  • packages/charts-core/src/crosshair.test.ts
  • packages/charts-core/src/guide-layout.ts
  • packages/charts-core/src/index.ts
  • packages/charts-core/src/scene-layout.test.ts
  • packages/charts-core/src/scene.ts
  • packages/charts-core/src/types.ts
  • packages/charts-core/src/universal-types.ts

📝 Walkthrough

Walkthrough

The chart API adds axis.side: 'start' | 'end'. Layout, axis labels, titles, focus guides, crosshair labels, margins, tests, documentation, and benchmark metadata now reflect configurable axis placement.

Changes

Axis-side placement

Layer / File(s) Summary
Axis-side contracts and placement resolver
packages/charts-core/src/types.ts, packages/charts-core/src/guide-layout.ts, packages/charts-core/src/universal-types.ts, packages/charts-core/src/index.ts, docs/reference/types.md, packages/charts-core/docs/reference/types.md
Adds ChartAxisSide, optional side configuration, focus-guide side metadata, public exports, and the axisPlacement resolver.
Scene axis and focus-guide layout
packages/charts-core/src/scene.ts, packages/charts-core/src/scene-layout.test.ts
Positions axes, ticks, labels, titles, focus guides, extents, and margins from the resolved axis edge and direction. Tests cover start and end placement.
Crosshair label placement
packages/charts-core/src/crosshair-resolver.ts, packages/charts-core/src/crosshair.test.ts
Positions crosshair value labels according to the configured x- and y-axis sides.
Axis-side documentation and release records
docs/concepts/layout-axes-and-coordinates.md, packages/charts-core/docs/concepts/layout-axes-and-coordinates.md, .changeset/opposite-axis-side.md, API-FRICTION.md
Documents start/end behavior and the RTL example using y.axis.side: 'end' with x.reverse: true.

Benchmark metadata refresh

Layer / File(s) Summary
Bundle baseline regeneration
benchmarks/comparison/bundle-baseline.json
Updates generation metadata, source identity, and compression baselines for the compared chart libraries.
Conformance preview manifest
benchmarks/conformance/previews/manifest.json
Updates the manifest source hash.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 3b8cd

The change adds optional axis-side placement while preserving existing defaults, with targeted coverage for axes, margins, crosshair labels, and rendering. No actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant ChartConfig
  participant scene
  participant axisPlacement
  participant crosshairResolver
  ChartConfig->>scene: set x.axis.side and y.axis.side
  scene->>axisPlacement: resolve axis edges and direction signs
  axisPlacement-->>scene: return placement data
  scene->>scene: render axes, labels, titles, guides, and margins
  crosshairResolver->>axisPlacement: resolve guide-side placement
  axisPlacement-->>crosshairResolver: return edge and direction
  crosshairResolver-->>scene: position crosshair value labels
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes add start/end axis placement and support right-side Y axes for RTL layouts as requested in issue #87.
Out of Scope Changes check ✅ Passed The documentation, tests, changeset, API notes, and regenerated benchmark metadata support the axis-side placement change.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: configurable axis-side placement for right-to-left locales.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

No way to place the Y axis on the right, which breaks RTL locales

1 participant