Issue #892: fix horizontal legend layout with mixed render styles#999
Conversation
A horizontal legend mixing render styles (e.g. Bar + Line) rendered the markers/boxes and text misaligned, and the whole entry group sat too low in the legend box. Two root causes: 1. Legend_Marker centered each entry against its own graphic height, so a Bar's 20px box and a Line's smaller marker landed on different vertical baselines. In a horizontal layout every entry shares one row, so a common row height (max graphic/text height across all shown series) is now used to vertically center the box, line, marker, and text uniformly. Vertical layout keeps its per-entry height (each entry is its own row). 2. Legend_.getBoundsHintHorizontal computed the legend-box height by assignment inside the series loop, so the box was sized to the LAST series' height instead of the tallest. With the Line series added last, the box was too short for the 20px Bar box and the content overflowed downward. Now it accumulates the max across all series. Added RegressionTestIssue892 (asserts entries align to each other and are centered in the container; fails on either bug in isolation) and demo TestForIssue892. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes horizontal legend layout issues when mixing render styles (e.g., Bar + Line) by (1) aligning legend entry graphics/text to a shared row height and (2) sizing the horizontal legend container based on the tallest entry, and adds a regression test + demo reproducer for Issue #892.
Changes:
- Compute a common row height for horizontal legends so mixed render-style entries align vertically (
Legend_Marker). - Fix horizontal legend bounds calculation to use the max entry height across all series (
Legend_). - Add regression coverage and a runnable demo reproducer for Issue #892.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_Marker.java |
Centers legend graphics/text against a shared row height for horizontal legends. |
xchart/src/main/java/org/knowm/xchart/internal/chartpart/Legend_.java |
Fixes horizontal legend height calculation to accumulate the maximum across series. |
xchart/src/test/java/org/knowm/xchart/regressiontests/RegressionTestIssue892.java |
Adds an image-based regression test asserting alignment + centering for the reported scenario. |
xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue892.java |
Adds a headless-safe demo reproducer for Issue #892. |
| CategoryChart chart = | ||
| new CategoryChartBuilder().width(800).height(600).title("issue 892").build(); | ||
| chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS); | ||
| chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal); | ||
|
|
| // The OutsideS legend sits at the very bottom. Its background matches the (white) plot | ||
| // background higher up, so locate the legend container as the bottom-most contiguous block of | ||
| // background-colored rows (below it is only the chart-background margin). Then measure the | ||
| // entries strictly within its rows, so plot content can never leak into the measurement. |
| + chart.getStyler().getLegendSeriesLineLength() | ||
| + chart.getStyler().getLegendPadding(); | ||
| paintSeriesText(g, seriesTextBounds, axesChartStyler.getMarkerSize(), x, starty); | ||
| paintSeriesText(g, seriesTextBounds, (int) rowHeight, x, starty); |
| * <p>Expected: in a horizontal legend, the markers/boxes and their text should line up uniformly | ||
| * across mixed render styles. Currently a Bar entry centers its text within the 20px legend box | ||
| * while a Line entry centers within the (smaller) marker size, so the two entries sit at different | ||
| * vertical baselines. |
- Legend_Marker: instead of passing the shared row height into paintSeriesText() (which altered its per-line centering math for multi-line labels and was inconsistent with other Legend_* impls), center each entry's natural block within the shared row by shifting a per-entry vertical origin (entryStarty). All per-element math is now identical to the vertical layout, just drawn from entryStarty. - RegressionTestIssue892: clarify how the legend container is located (default XChartTheme uses a grey chart background, so the white legend is the bottom-most white band) and add a guard that fails loudly if the detected container is implausibly tall (background detection degenerated into the plot area). - TestForIssue892: reword Javadoc to past tense since the fix ships with it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @copilot — addressed in 0410740:
Re-verified: |
Fixes #892.
Problem
With a horizontal legend (
LegendLayout.Horizontal, e.g.LegendPosition.OutsideS) mixing render styles such as Bar + Line, the legend markers/boxes and text were misaligned, and the whole entry group sat too low inside the legend box.Reported by @JamesPaceOnTailwind.
Root causes
Two independent bugs, both only visible when entries in one horizontal row differ in height:
Entries not aligned to each other —
Legend_Markercentered each entry against its own graphic height, so a Bar's 20px box and a Line's smaller marker landed on different vertical baselines. In a horizontal layout every entry shares one row, so the fix computes a common row height (the tallest graphic/text across all shown series) and centers the box, line, marker, and text against it uniformly. Vertical layout is unchanged (each entry is its own row).Group sat too low —
Legend_.getBoundsHintHorizontal()computed the legend-box height by assignment inside the series loop, so the container was sized to the last series' height instead of the tallest. With the Line series added last, the box was too short for the 20px Bar box and the content overflowed downward. Now it accumulates the max across all series.Before / After
Before: the line/marker and its label sit higher than the box and its label, and the group is low in the box.
After: box, line, marker, and both labels share one vertical center, centered in the container.
Testing
RegressionTestIssue892— renders the reporter's chart and asserts (1) the Bar box center and Line marker center align, and (2) the entry group is vertically centered in the legend container. Each assertion fails independently if its respective fix is reverted.TestForIssue892(the reporter's example, headless-safegetChart()).xchartsuite: 130 pass.fmt:checkclean.🤖 Generated with Claude Code