Skip to content

Commit e7b98f9

Browse files
committed
fix: text width ratio, brand reserve, top legend spacing, and breakpoint annotations
Bump AVG_CHAR_WIDTH_RATIO from 0.55 to 0.57 across all three text measurement sites for more accurate width estimates. Increase brand reserve width to 130px. Only reserve brand space for bottom-positioned legends since the watermark renders at the bottom. Force inline annotation positioning when users explicitly provide breakpoint annotations.
1 parent 2d56b53 commit e7b98f9

6 files changed

Lines changed: 14 additions & 9 deletions

File tree

packages/core/src/layout/text-measure.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* Inter is slightly wider than Helvetica, narrower than Courier.
1313
* This is a reasonable middle ground.
1414
*/
15-
const AVG_CHAR_WIDTH_RATIO = 0.55;
15+
const AVG_CHAR_WIDTH_RATIO = 0.57;
1616

1717
/** Narrower characters (i, l, t, etc.) bring the average down. */
1818
const WEIGHT_ADJUSTMENT: Record<number, number> = {
@@ -65,7 +65,7 @@ export function estimateTextWidth(text: string, fontSize: number, fontWeight = 4
6565
* so adjacent text doesn't crowd it. Used by chrome and legend layout to avoid
6666
* overlapping the brand.
6767
*/
68-
export const BRAND_RESERVE_WIDTH = 110;
68+
export const BRAND_RESERVE_WIDTH = 130;
6969

7070
/** Font size of the brand watermark (px). Shared between layout and renderer. */
7171
export const BRAND_FONT_SIZE = 12;

packages/engine/src/annotations/__tests__/compute.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -866,8 +866,8 @@ describe('computeAnnotations', () => {
866866

867867
// Curve should start from right edge of text, not top edge
868868
// Right edge x ≈ label.x + textWidth
869-
// "Curve test" = 10 chars * 12 * 0.55 = 66
870-
expect(connector.from.x).toBeCloseTo(label.x + 66, 1);
869+
// "Curve test" = 10 chars * 12 * 0.57 = 68.4
870+
expect(connector.from.x).toBeCloseTo(label.x + 68.4, 1);
871871
});
872872
});
873873

packages/engine/src/compile.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
240240
// Responsive strategy
241241
const breakpoint = getBreakpoint(options.width);
242242
const heightClass = getHeightClass(options.height);
243-
const strategy = getLayoutStrategy(breakpoint, heightClass);
243+
let strategy = getLayoutStrategy(breakpoint, heightClass);
244244

245245
// Apply breakpoint-conditional overrides from the expanded spec
246246
const rawSpec = expandedSpec as Record<string, unknown>;
@@ -286,6 +286,9 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
286286
...chartSpec,
287287
annotations: bp.annotations as NormalizedChartSpec['annotations'],
288288
};
289+
// User explicitly provided annotations at this breakpoint — override the
290+
// responsive strategy so they render inline instead of being stripped.
291+
strategy = { ...strategy, annotationPosition: 'inline' };
289292
}
290293
}
291294

packages/engine/src/legend/compute.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,11 @@ export function computeLegend(
289289
}
290290

291291
// Top/bottom-positioned legend: horizontal flow with overflow protection.
292-
// Reserve space on the right so legend entries don't overlap the brand watermark.
292+
// Reserve space on the right for bottom legends so they don't overlap the brand
293+
// watermark. Top legends don't need this since the brand renders at the bottom.
294+
const reserveBrand = watermark && resolvedPosition === 'bottom';
293295
const availableWidth =
294-
chartArea.width - LEGEND_PADDING * 2 - (watermark ? BRAND_RESERVE_WIDTH : 0);
296+
chartArea.width - LEGEND_PADDING * 2 - (reserveBrand ? BRAND_RESERVE_WIDTH : 0);
295297

296298
// Apply symbolLimit first if set (minimum 1), then fit remaining entries to available rows.
297299
if (spec.legend?.symbolLimit != null) {

packages/vanilla/src/sankey-renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function stampAnimationAttrs(
8585
function wrapText(text: string, fontSize: number, fontWeight: number, maxWidth: number): string[] {
8686
if (maxWidth <= 0) return [text];
8787

88-
const AVG_CHAR_WIDTH = 0.55;
88+
const AVG_CHAR_WIDTH = 0.57;
8989
const WEIGHT_FACTORS: Record<number, number> = {
9090
100: 0.9,
9191
200: 0.92,

packages/vanilla/src/svg-renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function wrapText(
178178
}
179179

180180
// Heuristic character width matching text-measure.ts
181-
const AVG_CHAR_WIDTH = 0.55;
181+
const AVG_CHAR_WIDTH = 0.57;
182182
const WEIGHT_FACTORS: Record<number, number> = {
183183
100: 0.9,
184184
200: 0.92,

0 commit comments

Comments
 (0)