Skip to content

Fix stacked category charts stacking Line/Scatter series and mixing axis groups (#906)#994

Merged
timmolter merged 3 commits into
developfrom
issue-906-stacked-secondary-axis-line
Jul 6, 2026
Merged

Fix stacked category charts stacking Line/Scatter series and mixing axis groups (#906)#994
timmolter merged 3 commits into
developfrom
issue-906-stacked-secondary-axis-line

Conversation

@timmolter

Copy link
Copy Markdown
Member

Fixes #906.

Problem

With CategoryChart + setStacked(true), a Line series assigned to a secondary Y-axis group was (1) drawn stacked on top of the bars, and (2) caused both axis groups to be ranged from the combined stack of all series (bars + line), so neither axis reflected only its own data.

Two root causes:

  • PlotContent_Category applied the stack offset to every series regardless of render style, so Line/Scatter participated in the bar stack.
  • AxisPair.overrideMinMaxForYAxis recomputed the stacked min/max by summing every series in the chart, ignoring both getYAxisGroup() and render style — even though it is called once per axis group.

Fix

  • Add CategorySeriesRenderStyle.isStackable()false for Line/Scatter, true for Bar/Area/Stick/SteppedBar.
  • PlotContent_Category: only stackable render styles accumulate into the per-category stack offsets (single seriesStacked flag gates all stacking branches).
  • AxisPair: recompute the stacked range per axis group, summing only stackable series belonging to that group; skip the override entirely when a group has no stackable series (a line-only group keeps its raw range), and keep any non-stacked series on the same group in view via Math.max/Math.min.
  • Add BarChart13 demo reproducing the reporter's exact scenario.

Behavior for single-group stacked charts is unchanged (verified: full xchart suite, 122 tests green).

Result

Left axis ranges to the stacked bar sums (0–55), right axis to the line values (0–180), and the line is drawn at its own values instead of on top of the bars — matching the "Expected behavior" in the issue.

Note: rendered PNG verification done locally; drop the image in if desired.

🤖 Generated with Claude Code

timmolter and others added 2 commits July 6, 2026 13:37
…xis groups (#906)

When a CategoryChart had setStacked(true) with a Line (or Scatter) series
on a secondary Y-axis group, the line was stacked on top of the bars and
both axis groups were ranged from the combined stack of every series.

- Add CategorySeriesRenderStyle.isStackable() (false for Line/Scatter).
- PlotContent_Category: only stackable render styles accumulate into the
  per-category stack offsets.
- AxisPair: recompute stacked min/max per Y-axis group, summing only
  stackable series in that group, and only override when the group has
  stackable series (so a line-only group keeps its raw range).
- Add BarChart13 demo reproducing the issue scenario.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@timmolter timmolter marked this pull request as ready for review July 6, 2026 11:46
@timmolter timmolter requested a review from Copilot July 6, 2026 12:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes stacked CategoryChart behavior so stacking applies only to stackable category render styles and stacked-axis ranging is computed per Y-axis group, preventing secondary-axis Line/Scatter series from inflating or participating in the bar stack.

Changes:

  • Add CategorySeriesRenderStyle.isStackable() to distinguish stackable (Bar/Area/Stick/SteppedBar) vs non-stackable (Line/Scatter) styles.
  • Update PlotContent_Category to apply stack offsets only when the series is both stacked and stackable.
  • Update AxisPair.overrideMinMaxForYAxis to compute stacked min/max per Y-axis group using only stackable series in that group; add a demo reproducer.

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/PlotContent_Category.java Gates stacking offsets so Line/Scatter render at their own values.
xchart/src/main/java/org/knowm/xchart/internal/chartpart/AxisPair.java Recomputes stacked Y-range per axis group using only stackable series for that group.
xchart/src/main/java/org/knowm/xchart/CategorySeries.java Introduces CategorySeriesRenderStyle.isStackable() to formalize stacking participation.
xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart13.java Adds a demo for stacked bars + secondary-axis line scenario.

Comment on lines +51 to +53
chart.getStyler().setYAxisGroupPosition(1, YAxisPosition.Right); // secondary axis on the right
chart.getStyler().setYAxisMax(0, 200.0); // primary (bars) axis
chart.getStyler().setYAxisMax(1, 200.0); // secondary (line) axis
Comment on lines +307 to 313
// If stacked, recalculate min and max from the per-category stack sums. Only series belonging
// to this Y-axis group are summed, and only stackable render styles (bar, stick, stepped bar,
// area) contribute — Line and Scatter series are drawn at their own values, so they neither
// stack nor inflate another axis group's range. Stacking otherwise sums the series values the
// same way regardless of style, and covers charts where individual series override the default
// render style.
if (categoryStyler.isStacked() && !chart.getSeriesMap().isEmpty()) {
Comment on lines +14 to +18
/**
* Stacked Bars with a Line on a secondary Y-Axis
*
* <p>Demonstrates the following:
*
Comment on lines +137 to +140
public boolean isStackable() {

return this != Line && this != Scatter;
}
- CategorySeriesRenderStyle.isStackable() now enumerates the stackable
  styles (Bar/Area/Stick/SteppedBar) so any future render style defaults
  to non-stackable until intentionally opted in, instead of a Line/Scatter
  deny-list.
- Add StackedSecondaryAxisTest covering the #906 scenario: stacked bars on
  group 0 + Line on group 1. Asserts each group's computed Y-axis range
  (primary 0..55 stack sums, secondary max 180 not inflated to 235), a
  line-only stacked group, and the single-group-bars baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@timmolter

Copy link
Copy Markdown
Member Author

Thanks for the review. Addressed in cdfa8b4:

  • isStackable() deny-list → allow-list (comment on CategorySeries.java): now explicitly enumerates Bar/Area/Stick/SteppedBar via a switch, so any future render style defaults to non-stackable until intentionally opted in.
  • Missing regression test (comment on AxisPair.java): added StackedSecondaryAxisTest (in-package, reads chart.axisPair.getYAxis(group) after BitmapEncoder.getBitmapBytes, in the style of MergedYAxisTest). It asserts the primary group ranges to the stack sums (0..55), the secondary line group tops out at the raw line max (180, not the pre-fix 55+180=235), a line-only stacked group is not stacked, and the single-group stacked-bars baseline is unchanged. Full xchart suite: 126 green, fmt:check clean.

Not changed, by maintainer decision:

  • Demo axis caps / TestForIssue906 convention (comments on BarChart13.java): keeping BarChart13 with the explicit setYAxisMax(group, 200.0) caps as the intended gallery demo; not adding a separate uncapped standalone/issues/TestForIssue906. The new StackedSecondaryAxisTest provides the auto-ranging regression coverage instead.

@timmolter timmolter merged commit 1203cef into develop Jul 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stacked Bar With Secondary Line

2 participants