fix(chart): el margen izquierdo era fijo, asi que toda etiqueta mas ancha que "100k" se salia del SVG

El usuario reporto que los valores solapan al eje, y en el commit anterior arregle
otra cosa —el formato del tooltip— sin tocar esto. Tenia razon: seguia igual.

const margins = { ..., left: margin?.left ?? 44 }

44px fijos, sin relacion con lo que midan las etiquetas del eje Y. Con "100k"
(28px) cabe por 7px y por eso la demo se salvaba; con cualquier cosa mas ancha se
sale por el borde IZQUIERDO del SVG. Medido con el formateador de la demo puesto
en toLocaleString('es-ES'): de seis etiquetas, CINCO desbordaban —"100.000"
empezaba en x=-11— y se veian cortadas.

Solo el eje conoce su `format`, asi que es el eje quien declara cuanto necesita:
nuevo `reserveYGutter(px)` en el contexto, con el mismo patron de disposer que
enableTooltip/enableLegend. El frame toma Math.max(44, gutter) y un `margin.left`
explicito sigue ganando sobre ambos.

El ancho se ESTIMA por numero de caracteres (~7px) en vez de medirse con getBBox:
mantiene esto fuera de la ruta de lecturas de layout, solo puede HACER CRECER el
margen porque el frame lo suela en 44, y el texto de las etiquetas depende del
dominio Y y nunca del margen, asi que no puede realimentarse.

Verificado en Chrome con el formateador ancho de verdad —no reescribiendo el DOM
despues, que fue mi primer intento y no probaba nada porque el componente nunca
veia las etiquetas largas—: clipped 5 -> 0, "100.000" entero dentro del SVG y el
eje corrido para dejarle sitio. Y con el formato corto restaurado, axisX sigue
en 44: cero cambio en el caso actual.

check 74 = linea base exacta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-dir-prefs
dev 2 months ago
parent 8086f21d6e
commit ea88414f3e

@ -7,6 +7,19 @@
const ctx = getChartContext();
const fmt = $derived(format ?? ((v: number) => ctx.formatNumber(v)));
const items = $derived(ctx.yScale.ticks(ticks).map((t) => ({ y: ctx.yScale(t), label: fmt(t) })));
// Claim a gutter wide enough for the longest label, so the frame stops
// clipping anything wider than `100k` against its flat 44px margin.
//
// Estimated from the character count rather than measured with `getBBox`:
// the labels are digits and separators in a tabular-ish face, ~7px at the
// axis font size, and an estimate keeps this off the layout-read path
// entirely. It only ever GROWS the margin (the frame floors it at 44), so
// erring high costs a few px of plot and erring low is bounded by the floor.
// The label text depends on the y DOMAIN, never on the margin, so this
// cannot feed back into itself.
const longestLabel = $derived(items.reduce((n, it) => Math.max(n, it.label.length), 0));
$effect(() => ctx.reserveYGutter(Math.ceil(longestLabel * 7 + 12)));
</script>
<g data-chart-axis="y">

@ -83,11 +83,17 @@
let measured = $state(0);
const width = $derived(fixedWidth ?? measured);
/** Gutter the y axis claims for its widest label (see `reserveYGutter`). */
let yGutter = $state(0);
const margins = $derived({
top: margin?.top ?? 12,
right: margin?.right ?? 16,
bottom: margin?.bottom ?? 28,
left: margin?.left ?? 44
// An explicit `margin.left` wins; otherwise take whichever is larger, the
// 44px floor or what the axis says its labels need. A flat 44 clipped any
// label wider than `100k`.
left: margin?.left ?? Math.max(44, yGutter)
});
const series = $derived(y === undefined ? [] : Array.isArray(y) ? [...y] : [y as string]);
@ -259,6 +265,12 @@
if (tooltipConfig === config) tooltipConfig = null;
};
},
reserveYGutter: (px: number) => {
yGutter = px;
return () => {
if (yGutter === px) yGutter = 0;
};
},
enableLegend: () => {
const token = {};
legendToken = token;

@ -138,6 +138,16 @@ export interface ChartContext {
enableTooltip(config?: ChartTooltipConfig): () => void;
/** Enable the HTML legend (called by `<Chart.Legend>`). Returns a disposer. */
enableLegend(): () => void;
/**
* Claim a left gutter (px) wide enough for the y axis's longest label
* (called by `<Chart.YAxis>`). Returns a disposer.
*
* The frame's left margin used to be a flat 44px, which fits `100k` by 7px
* and nothing wider: a `1.234.567` or a locale with thousands separators ran
* straight off the left edge of the SVG. Only the axis knows its `format`, so
* the axis is what declares the width; an explicit `margin.left` still wins.
*/
reserveYGutter(px: number): () => void;
/** Stacked `[y0, y1]` (data units) for a series at a datum index, or `null` when not stacking. */
stackOffset(seriesKey: string, datumIndex: number): readonly [number, number] | null;
/** Content-domain enter for the marks: a preset name (all marks use it), or `true` for each

Loading…
Cancel
Save

Powered by TurnKey Linux.