Skip to content

fix: compute position stacking per-facet-panel instead of globally#245

Open
cpsievert wants to merge 4 commits intomainfrom
fix/facet-stacking
Open

fix: compute position stacking per-facet-panel instead of globally#245
cpsievert wants to merge 4 commits intomainfrom
fix/facet-stacking

Conversation

@cpsievert
Copy link
Collaborator

@cpsievert cpsievert commented Mar 19, 2026

Summary

Fixes #244.

Position stacking (cumulative sums) was computed globally across all facet panels, causing bars in later panels to "float" above 0 by stacking on top of cumulative values from earlier panels.

Before: Male panel bars start where female panel bars ended:
penguins_facet_bar_bug

After: Each panel stacks independently from 0:

penguins_facet_bar_fixed

Query:

SELECT * FROM ggsql:penguins WHERE sex IS NOT NULL
VISUALISE island AS x, species AS fill
DRAW bar
FACET sex

Changes

  • src/plot/layer/position/stack.rs: The .over() partition for cum_sum and sum now includes facet columns (extracted from layer.partition_by) in addition to the group column. This ensures stacking resets within each facet panel. Applies to all three stacking modes: Normal, Fill, and Center.
  • src/execute/position.rs: New test test_stack_resets_per_facet_panel verifies that stacking starts from 0 in each facet panel.

Root cause

In apply_stack, the cumulative sum used .over([col(&group_col)]) — partitioning only by the x-axis position. Facet columns were not included, so the cumsum accumulated across all panels as if they were one.

Test plan

  • New test test_stack_resets_per_facet_panel passes
  • All 1221 existing tests pass
  • No compiler warnings
  • End-to-end verified with penguins dataset: all facet panels start from pos2end=0

🤖 Generated with Claude Code

@thomasp85
Copy link
Collaborator

Can you update this to always add facet variables to the grouping when stacking.

It might be that it makes sense to add these to partition_by early so it is there be default for both stat and position handling

@cpsievert
Copy link
Collaborator Author

cpsievert commented Mar 20, 2026

add facet variables to the grouping when stacking

Done in ff2622f— stacking now always reads facet variables from spec.facet.layout.internal_facet_names() instead of filtering partition_by for facet-prefixed columns.

add these to partition_by early so it is there be default for both stat and position handling

Claude considered that but went with reading from spec.facet directly because stacking can't use all of partition_by uniformly. partition_by includes fill/color columns (via add_discrete_columns_to_partition_by), and stacking needs to stack across those groups, not within them. So the stacking over_cols need to be [group_col, facet_cols] specifically — not all of partition_by. Reading from spec.facet makes this intent explicit without needing to re-filter partition_by for facet columns.

(Facet columns do already end up in partition_by via add_discrete_columns_to_partition_by, which stat transforms consume through group_by — so that path is already covered.)

cpsievert and others added 4 commits March 20, 2026 10:37
)

Stacking `.over()` partitions now include facet columns, so cumulative
sums reset within each facet panel. Previously, bars in later panels
stacked on top of cumulative values from earlier panels.

Also applies to Fill and Center stacking modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of filtering partition_by for facet-prefixed columns, read
facet variables directly from spec.facet.layout.internal_facet_names().
This ensures facet variables are always included in the stacking group
when a facet exists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dodge and jitter used all partition_by columns (including facet columns)
when computing group indices via compute_group_indices(). This inflated
n_groups — e.g., 2 fill groups across 2 facet panels were seen as 4
composite groups, making bars too narrow and offsets incorrect.

Add non_facet_partition_cols() helper that filters facet columns out of
partition_by using spec.facet, and use it in both dodge and jitter.

Closes #254

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cpsievert cpsievert force-pushed the fix/facet-stacking branch from 46da14e to 7c5011d Compare March 20, 2026 15:38
@cpsievert
Copy link
Collaborator Author

The latest commit (7c5011d) extends the facet-panel fix to dodge and jitter.

The problem: compute_group_indices() in dodge/jitter was called with all of layer.partition_by, which includes facet columns (added by add_discrete_columns_to_partition_by). This creates composite groups across facet panels — e.g., fill=["X","Y"] × facet=["F1","F2"] produces 4 groups instead of the correct 2 per panel. For dodge, this halves adjusted_width (0.225 instead of 0.45), making bars too narrow.

The fix: Added a shared non_facet_partition_cols() helper in position/mod.rs that filters facet columns out of partition_by using spec.facet.layout.internal_facet_names(). Both dodge and jitter call this before compute_group_indices().

This is the same pattern as the stacking fix (ff2622f) — position adjustments read facet info from spec.facet rather than relying on naming conventions in partition_by. Filed #254 to track the dodge/jitter issue separately.

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.

Position stacking is computed globally instead of per-facet-panel

2 participants