diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/chart-with-watermark-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/chart-with-watermark-visual-darwin.png index 8decd39e..5d65abd6 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/chart-with-watermark-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/chart-with-watermark-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/compact-line-320px-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/compact-line-320px-visual-darwin.png index deee4cdc..946b3d2e 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/compact-line-320px-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/compact-line-320px-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/dark-mode-line-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/dark-mode-line-visual-darwin.png index caadc3cd..299959e1 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/dark-mode-line-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/dark-mode-line-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-bottom-right-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-bottom-right-visual-darwin.png index f7a53ad3..6c33d678 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-bottom-right-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-bottom-right-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-top-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-top-visual-darwin.png index d162ac02..2f525bf5 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-top-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/legend-top-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/line-multi-series-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/line-multi-series-visual-darwin.png index 95b83a53..14fc5df7 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/line-multi-series-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/line-multi-series-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-overlap-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-overlap-visual-darwin.png index 852907d0..ea656574 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-overlap-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-overlap-visual-darwin.png differ diff --git a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-stacked-visual-darwin.png b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-stacked-visual-darwin.png index 51822087..16009e8f 100644 Binary files a/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-stacked-visual-darwin.png and b/e2e/visual/__screenshots__/stories.spec.ts-snapshots/multi-series-area-stacked-visual-darwin.png differ diff --git a/packages/engine/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap b/packages/engine/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap index bebedf1b..ed3bf066 100644 --- a/packages/engine/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +++ b/packages/engine/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap @@ -231,6 +231,7 @@ exports[`compileChart snapshot (Step 7 oracle) > clipped-domain bar chart (data "fill": "#ffffff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 11, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "end", @@ -264,6 +265,7 @@ exports[`compileChart snapshot (Step 7 oracle) > clipped-domain bar chart (data "fill": "#ffffff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 11, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "end", @@ -297,6 +299,7 @@ exports[`compileChart snapshot (Step 7 oracle) > clipped-domain bar chart (data "fill": "#ffffff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 11, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "end", @@ -868,6 +871,7 @@ exports[`compileChart snapshot (Step 7 oracle) > legend-heavy multi-series line "fill": "#71717a", "fontFamily": ""Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif", "fontSize": 12, + "fontVariant": "tabular-nums", "fontWeight": 400, "lineHeight": 1.25, }, @@ -1800,6 +1804,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g "fill": "#0000ff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 10, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "middle", @@ -1844,6 +1849,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g "fill": "#0000ff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 10, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "middle", @@ -1888,6 +1894,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g "fill": "#0000ff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 10, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "middle", @@ -1932,6 +1939,7 @@ exports[`compileChart snapshot (Step 7 oracle) > watermarked column chart with g "fill": "#0000ff", "fontFamily": "system-ui, -apple-system, sans-serif", "fontSize": 10, + "fontVariant": "tabular-nums", "fontWeight": 600, "lineHeight": 1.2, "textAnchor": "middle", diff --git a/packages/engine/src/__tests__/axes.test.ts b/packages/engine/src/__tests__/axes.test.ts index a1e28c92..01ace255 100644 --- a/packages/engine/src/__tests__/axes.test.ts +++ b/packages/engine/src/__tests__/axes.test.ts @@ -668,9 +668,10 @@ describe('axis config properties', () => { const scales = computeScales(lineSpec, chartArea, lineSpec.data); const axes = computeAxes(scales, chartArea, fullStrategy, theme); - // All new properties should be undefined when not set + // All new properties should be undefined when not set (except domainLine, + // which defaults to false for non-grounded marks like line) expect(axes.x!.orient).toBeUndefined(); - expect(axes.x!.domainLine).toBeUndefined(); + expect(axes.x!.domainLine).toBe(false); expect(axes.x!.tickMarks).toBeUndefined(); expect(axes.x!.offset).toBeUndefined(); expect(axes.x!.titlePadding).toBeUndefined(); diff --git a/packages/engine/src/barlist/compile-barlist.ts b/packages/engine/src/barlist/compile-barlist.ts index 2e47688b..60d32e23 100644 --- a/packages/engine/src/barlist/compile-barlist.ts +++ b/packages/engine/src/barlist/compile-barlist.ts @@ -258,6 +258,7 @@ export function compileBarList(spec: unknown, options: CompileOptions): BarListL fontWeight: VALUE_FONT_WEIGHT, fill: valueColor, lineHeight: 1.4, + fontVariant: 'tabular-nums', }; const rowMark: BarListRowMark = { diff --git a/packages/engine/src/charts/bar/labels.ts b/packages/engine/src/charts/bar/labels.ts index 19b34b79..bdbc3011 100644 --- a/packages/engine/src/charts/bar/labels.ts +++ b/packages/engine/src/charts/bar/labels.ts @@ -227,6 +227,7 @@ export function computeBarLabels( lineHeight: 1.2, textAnchor, dominantBaseline: 'central', + fontVariant: 'tabular-nums', }, }); } diff --git a/packages/engine/src/charts/column/labels.ts b/packages/engine/src/charts/column/labels.ts index e0070ce6..89e196c3 100644 --- a/packages/engine/src/charts/column/labels.ts +++ b/packages/engine/src/charts/column/labels.ts @@ -123,6 +123,7 @@ export function computeColumnLabels( fill: labelColor ?? getRepresentativeColor(mark.fill), lineHeight: 1.2, textAnchor: 'middle', + fontVariant: 'tabular-nums', }, }); } diff --git a/packages/engine/src/charts/dot/labels.ts b/packages/engine/src/charts/dot/labels.ts index ed6e27be..2cab926d 100644 --- a/packages/engine/src/charts/dot/labels.ts +++ b/packages/engine/src/charts/dot/labels.ts @@ -99,6 +99,7 @@ export function computeDotLabels( lineHeight: 1.2, textAnchor: 'start', dominantBaseline: 'central', + fontVariant: 'tabular-nums', }, }); } diff --git a/packages/engine/src/charts/pie/labels.ts b/packages/engine/src/charts/pie/labels.ts index a82a93ca..4b3674ea 100644 --- a/packages/engine/src/charts/pie/labels.ts +++ b/packages/engine/src/charts/pie/labels.ts @@ -99,6 +99,7 @@ export function computePieLabels( lineHeight: 1.2, textAnchor: isRight ? 'start' : 'end', dominantBaseline: 'central', + fontVariant: 'tabular-nums', }, }); diff --git a/packages/engine/src/endpoint-labels/compute.ts b/packages/engine/src/endpoint-labels/compute.ts index f0c73671..8655b04f 100644 --- a/packages/engine/src/endpoint-labels/compute.ts +++ b/packages/engine/src/endpoint-labels/compute.ts @@ -68,6 +68,7 @@ function emptyLayout(theme: ResolvedTheme): EndpointLabelsLayout { fontWeight: ENDPOINT_VALUE_FONT_WEIGHT, fill: theme.colors.annotationText ?? theme.colors.text, lineHeight: ENDPOINT_LINE_HEIGHT, + fontVariant: 'tabular-nums', }; return { entries: [], @@ -271,6 +272,7 @@ export function computeEndpointLabels( fontWeight: ENDPOINT_VALUE_FONT_WEIGHT, fill: theme.colors.annotationText ?? theme.colors.text, lineHeight: ENDPOINT_LINE_HEIGHT, + fontVariant: 'tabular-nums', }; // First pass: build entries with wrapped labels and dataY (no positions yet). diff --git a/packages/engine/src/layout/axes.ts b/packages/engine/src/layout/axes.ts index 7bbb87fe..d7b935ac 100644 --- a/packages/engine/src/layout/axes.ts +++ b/packages/engine/src/layout/axes.ts @@ -73,6 +73,9 @@ const WIDTH_REDUCED_THRESHOLD = 300; /** Ordered densities from most to fewest ticks. */ const DENSITY_ORDER: AxisLabelDensity[] = ['full', 'reduced', 'minimal']; +/** Marks grounded on the x baseline that warrant a domain line. */ +const GROUNDED_MARKS = new Set(['bar', 'tick', 'lollipop']); + /** * Compute effective axis tick density by considering available space. * @@ -460,9 +463,12 @@ export function computeAxes( // because tick labels need horizontal room around their x position). const xTickPosition = axisConfig?.tickPosition ?? 'gutter'; + const xDomainLine = + axisConfig?.domain ?? (GROUNDED_MARKS.has(dataContext?.markType ?? '') ? undefined : false); + result.x = { ticks, - gridlines: axisConfig?.grid ? gridlines : [], + gridlines: (axisConfig?.grid ?? dataContext?.markType === 'point') ? gridlines : [], label: axisTitle, labelStyle: xLabelColor ? { ...axisLabelStyle, fill: xLabelColor } : axisLabelStyle, tickLabelStyle: xLabelColor ? { ...tickLabelStyle, fill: xLabelColor } : tickLabelStyle, @@ -470,7 +476,7 @@ export function computeAxes( start: { x: chartArea.x, y: chartArea.y + chartArea.height }, end: { x: chartArea.x + chartArea.width, y: chartArea.y + chartArea.height }, orient: axisConfig?.orient, - domainLine: axisConfig?.domain, + domainLine: xDomainLine, tickMarks: axisConfig?.ticks, offset: axisConfig?.offset, titlePadding: axisConfig?.titlePadding, diff --git a/packages/vanilla/src/__tests__/svg-renderer.test.ts b/packages/vanilla/src/__tests__/svg-renderer.test.ts index 08c230a4..208aacad 100644 --- a/packages/vanilla/src/__tests__/svg-renderer.test.ts +++ b/packages/vanilla/src/__tests__/svg-renderer.test.ts @@ -525,16 +525,22 @@ describe('axis rendering', () => { expect(labels.length).toBeGreaterThan(0); }); - it('x-axis has a baseline line element', () => { - const { svg } = renderSpec(lineSpec); + it('x-axis has a baseline line element for grounded marks', () => { + const { svg } = renderSpec(barSpec); const xAxis = svg.querySelector('.oc-axis-x'); const line = xAxis!.querySelector('line'); - // The renderer draws an axis line for x-axis expect(line).not.toBeNull(); }); + it('x-axis hides baseline for non-grounded marks', () => { + const { svg } = renderSpec(lineSpec); + const xAxis = svg.querySelector('.oc-axis-x'); + const line = xAxis!.querySelector('line'); + expect(line).toBeNull(); + }); + it('x-tick labels hang below the axis line by the label padding (no hugging)', () => { - const { svg, layout } = renderSpec(lineSpec); + const { svg, layout } = renderSpec(barSpec); const xAxis = svg.querySelector('.oc-axis-x')!; const axisLineY = Number(xAxis.querySelector('line')!.getAttribute('y2')); const labels = Array.from(xAxis.querySelectorAll('text.oc-axis-tick'));