Skip to content

A right-side y axis reserves no gutter under dir="rtl" #117

Description

@3li7alaki

Summary

A y scale with side: 'right' reserves no gutter when the chart's container is
dir="rtl". The axis draws at the right edge, the plot keeps the full width
underneath it, and the tick labels are painted across the plot with the grid
lines running through them.

Reproduced on main at 258ed39 (0.16.0), with a new vanilla example, listed
below. Also present in 0.15.0, the release side shipped in.

Repro

examples/rtl-axis-side/src/main.ts: one definition, mounted twice, once in an
ltr container and once in an rtl one. Nothing else differs.

import { defineChart, lineY } from '@tanstack/charts'
import { mountChart } from '@tanstack/charts/dom'
import { scaleLinear } from '@tanstack/charts/scales/linear'
import { scalePoint } from '@tanstack/charts/scales/point'

const data = [
  { month: 'Jan', revenue: 128000 },
  { month: 'Feb', revenue: 194500 },
  { month: 'Mar', revenue: 151250 },
  { month: 'Apr', revenue: 233900 },
]

const definition = defineChart({
  marks: [lineY(data, { x: 'month', y: 'revenue' })],
  scales: {
    x: { scale: () => scalePoint<string>().padding(0.4) },
    y: {
      scale: scaleLinear,
      nice: true,
      grid: true,
      side: 'right',
      axis: { ticks: { format: (v: number) => v.toLocaleString('en-US') } },
    },
  },
})

for (const dir of ['ltr', 'rtl'] as const) {
  const caption = document.createElement('p')
  caption.textContent = `dir="${dir}", y scale side: "right"`
  const host = document.createElement('div')
  host.dir = dir
  host.style.cssText = 'width:640px;outline:1px solid #ccc'
  document.body.append(caption, host)
  mountChart(host, { definition, height: 260, ariaLabel: `revenue ${dir}` })
}

Measured in the resulting SVGs, coordinates relative to each SVG's own left
edge, both 640px wide:

container plot x-range y tick labels rendered text-anchor axis x
ltr 4 to 593 601 to 636 start 600.67
rtl 4 to 628 601 to 636 start 636.42

The LTR row is the control: a 47px gutter, labels clear of the plot. The RTL row
reserves nothing, and the plot runs 27px under its own labels.

Where it comes from

createTickLabelCandidates in packages/charts-core/src/scene.ts:

const defaultAnchor =
  guide.channel === 'y'
    ? positiveSide
      ? 'start'
      : 'end'
    : ...

That relation holds only while the container's inline direction is LTR. The
anchor reaches the DOM as SVG text-anchor (svg-renderer.ts), which resolves
against inline base direction, so under dir="rtl" a right-side axis labelled
start paints leftward, into the plot rather than away from it.

Everything downstream is then faithful to that. The measurement mirrors with it,
as it should: the same string measures actualBoundingBoxLeft = -1.2, Right = 35.3 under ltr and 34.5 / -0.4 under rtl. includeBoundsMargin
sees a box on the plot side of the axis line, so margin.right stays at the
inset, chart.width grows, the axis slides further right, and the layout
converges on the overlap above.

So the fix looks like it belongs at the anchor default rather than in the
margin pass: under an RTL container a right axis wants end and a left axis
wants start, the mirror of the current relation.

Scope

Confirmed for a y scale with side: 'right' in an RTL container. Not tested:
side: 'top', or an x axis with a rotated label, both of which pick their
anchor from the same physical-side reasoning and may share the defect.

Note on framing

This is about the physical side that shipped in 0.15.0 behaving differently in
the two directions, not about logical start / end semantics or automatic RTL
mirroring. Physical right should reserve the right gutter whatever the
container's direction is.

Happy to send the one-line patch plus the example above as a PR if that reading
is right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions