Skip to content

Fix missing BoxPlot markers: preserve zero-height bars with custom shape prop#7067

Merged
PavelVanecek merged 4 commits intomainfrom
copilot/fix-missing-error-bars-boxplot
Mar 3, 2026
Merged

Fix missing BoxPlot markers: preserve zero-height bars with custom shape prop#7067
PavelVanecek merged 4 commits intomainfrom
copilot/fix-missing-error-bars-boxplot

Conversation

Copy link
Contributor

Copilot AI commented Feb 28, 2026

Description

PR #6800 introduced zero-dimension bar filtering for perf (skip rendering width === 0 || height === 0 bars). This broke the BoxPlot pattern, which uses zero-height stacked bars with a custom shape prop to render horizontal line markers at stack positions.

Changes

  • src/state/types/BarSettings.ts: Added hasCustomShape: boolean to BarSettings
  • src/cartesian/Bar.tsx (registration): Passes hasCustomShape={props.shape != null} into Redux state when Bar mounts
  • src/cartesian/Bar.tsx (computeBarRectangles): Zero-dimension filter now skips bars with a custom shape — they may still produce visible output even at zero height/width
// BoxPlot pattern: zero-height bar, but shape renders a line at that y position
<Bar stackId="a" dataKey="marker" shape={<HorizonBar />} />
// Previously filtered out by width===0||height===0; now preserved when shape is set
  • Updated all BarSettings snapshots across 8+ test files to include hasCustomShape: false
  • Added test case in Bar.zeroDimensionFiltering.spec.tsx asserting custom-shape zero-height bars are not filtered

Related Issue

https://github.com/recharts/recharts/issues/

Motivation and Context

The BoxPlot storybook example went blank after #6800. Zero-height bars are a legitimate pattern when a custom shape handles rendering — the bar coordinates are still needed for positioning.

How Has This Been Tested?

  • New unit test: should not filter out zero-height bars that have a custom shape (BoxPlot use case) — verifies all data points produce a BarRectangleItem with height === 0 instead of being dropped
  • Full test suite: 286 test files, 14 105 tests pass
  • TypeScript: tsc --noEmit clean across src/, test/, and storybook/

Screenshots (if appropriate):

N/A (storybook visual regression covers this via Chromatic)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • I have added a storybook story or VR test, or extended an existing story or VR test to show my changes
Original prompt

This section details on the original issue you should resolve

<issue_title>[Unreleased] Missing error bars in BoxPlot example</issue_title>
<issue_description>https://www.chromatic.com/test?appId=63da8268a0da9970db6992aa&id=69a18bb251ba943c7ece0737

Must be from some recent PR</issue_description>

Comments on the Issue (you are @copilot in this section)

@PavelVanecek Hm it's https://github.com//pull/6800

Perf improvement is nice but it erases zero-height bars which we used to fake boxplot.</comment_new>
<comment_new>@ckifer
Hmmmmm. Gotta fix before release I suppose. Revert the filter? Something else?</comment_new>
<comment_new>@ckifer
Probably revert and add explicit box plot support later? Then can filter when not doing box plot?</comment_new>
<comment_new>@PavelVanecek
Or render bars that have nonzero stroke width </comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Summary by CodeRabbit

  • New Features

    • Custom-shaped bars now render correctly with zero dimensions, enabling expanded visualization customization options for bar charts.
  • Tests

    • Updated test coverage for custom-shaped bar rendering behavior.

Copilot AI and others added 3 commits February 28, 2026 12:20
… custom shapes

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
… custom shapes

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
…ecks

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix missing error bars in BoxPlot example Fix missing BoxPlot markers: preserve zero-height bars with custom shape prop Feb 28, 2026
@PavelVanecek PavelVanecek marked this pull request as ready for review March 3, 2026 11:51
@PavelVanecek
Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Walkthrough

This PR introduces a hasCustomShape flag to Bar rendering that prevents filtering of zero-dimension bars when a custom shape is present. The flag is derived from props.shape != null and propagated through BarSettings to control zero-width/height bar filtering logic.

Changes

Cohort / File(s) Summary
Core Bar shape handling
src/cartesian/Bar.tsx, src/state/types/BarSettings.ts
Adds hasCustomShape property to BarSettings and updates computeBarRectangles to conditionally filter zero-dimension bars only when hasCustomShape is false, allowing custom shapes to render at zero size.
Test expectations
test/cartesian/Bar.truncateByDomain.spec.tsx, test/cartesian/Bar.zeroDimensionFiltering.spec.tsx, test/cartesian/Bar/Bar.spec.tsx, test/cartesian/Brush.stacked.spec.tsx, test/chart/BarChart.spec.tsx, test/state/graphicalItemsSlice.spec.ts, test/state/selectors/axisSelectors.spec.tsx, test/state/selectors/selectStackGroups.spec.tsx
Updates test expectations to include hasCustomShape: false on bar items and adds a new test case verifying that zero-height bars with custom shapes are preserved during rendering.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

refactor

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: preserving zero-height bars with custom shape props for BoxPlot patterns, directly addressing the core issue.
Description check ✅ Passed The PR description covers all required sections: clear problem statement, detailed changes, motivation, testing approach, and appropriate type classifications. All template sections are addressed comprehensively.
Linked Issues check ✅ Passed The code changes fully address issue #7064 by restoring zero-dimension bar rendering when custom shapes are provided, enabling BoxPlot markers to render while preserving performance optimizations for bars without custom shapes.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the BoxPlot marker regression: adding hasCustomShape tracking, conditional filtering logic, and corresponding test updates. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copilot/fix-missing-error-bars-boxplot

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
test/cartesian/Bar.truncateByDomain.spec.tsx (1)

155-205: Use expectLastCalledWith in this modified selector assertion block.

This block was touched and still uses toHaveBeenLastCalledWith, which diverges from the test helper convention used elsewhere.

Suggested update
 import { describe, expect, it, test } from 'vitest';
 import { createSelectorTestCase } from '../helper/createSelectorTestCase';
+import { expectLastCalledWith } from '../helper/expectLastCalledWith';
@@
-      expect(spy).toHaveBeenLastCalledWith({
+      expectLastCalledWith(spy, {
         date: {
           graphicalItems: [
@@
-      });
+      });
As per coding guidelines: Use the `expectLastCalledWith` helper function instead of `expect(spy).toHaveBeenLastCalledWith(...)` for better typing and autocompletion.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cartesian/Bar.truncateByDomain.spec.tsx` around lines 155 - 205, The
test 'selectStackGroups' currently calls
expect(spy).toHaveBeenLastCalledWith(...) — update this assertion to use the
project test-helper expectLastCalledWith helper instead; locate the test block
that invokes selectStackGroups and replace the
expect(spy).toHaveBeenLastCalledWith call with a call to
expectLastCalledWith(spy, <same expected object>) so the assertion uses the
helper while keeping the same expected payload (references: test name
'selectStackGroups', selector function 'selectStackGroups', and the local 'spy'
variable).
src/cartesian/Bar.tsx (1)

1116-1122: Narrow custom-shape detection to actual custom renderers.

Line 1185 marks any non-null shape as custom, which can disable zero-dimension filtering more broadly than necessary. Consider flagging only function/element shapes so optimization stays active for default-rectangle variants.

Proposed refinement
 function BarFn(outsideProps: Props) {
   const props = resolveDefaultProps(outsideProps, defaultBarProps);
+  const hasCustomShape = React.isValidElement(props.shape) || typeof props.shape === 'function';
   // stackId may arrive from props or from BarStack context
   const stackId = useStackId(props.stackId);
   const isPanorama = useIsPanorama();
@@
-            hasCustomShape={props.shape != null}
+            hasCustomShape={hasCustomShape}
           />

Also applies to: 1185-1185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cartesian/Bar.tsx` around lines 1116 - 1122, The guard that skips
zero-dimension filtering uses hasCustomShape derived from a non-null check on
shape; change the detection in the Bar component so hasCustomShape is set only
when shape is an actual custom renderer (e.g., typeof shape === 'function' or
React.isValidElement(shape) / React.isValidElement-like check) instead of any
non-null value, so the early-return condition in the null/zero check (the if
block referencing x, y, width, height and hasCustomShape) continues to filter
out width===0/height===0 for default rectangle shapes but allows true custom
renderers to bypass it.
test/cartesian/Bar.zeroDimensionFiltering.spec.tsx (1)

272-307: Add a DOM assertion for rendered marker lines, not only selector output.

Great selector-level check. I’d also assert the custom shape is actually rendered to guard the visual regression end-to-end.

Suggested extension
   it('should not filter out zero-height bars that have a custom shape (BoxPlot use case)', () => {
     const HorizonBar = (props: { x?: number; y?: number; width?: number; height?: number }) => {
       const { x, y, width } = props;
       if (x == null || y == null || width == null) {
         return null;
       }
-      return <line x1={x} y1={y} x2={x + width} y2={y} stroke="#000" strokeWidth={3} />;
+      return <line className="horizon-marker" x1={x} y1={y} x2={x + width} y2={y} stroke="#000" strokeWidth={3} />;
     };
@@
-    const { spy } = renderTestCase(state => selectBarRectangles(state, 'bar-marker', false, undefined));
+    const { container, spy } = renderTestCase(state => selectBarRectangles(state, 'bar-marker', false, undefined));
     const rectangles = spy.mock.lastCall?.[0] ?? [];
@@
     rectangles.forEach((rect: { height: number }) => {
       expect(rect.height).toBe(0);
     });
+    expect(container.querySelectorAll('.horizon-marker')).toHaveLength(data.length);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/cartesian/Bar.zeroDimensionFiltering.spec.tsx` around lines 272 - 307,
The test currently only asserts selector output (selectBarRectangles) for the
custom HorizonBar but doesn't verify the DOM rendering; update the spec to also
query the rendered output from renderTestCase (the BarChart with Bar id
"bar-marker" and shape HorizonBar) and assert that the expected <line> elements
for each data row are present and have the expected attributes (e.g., y/x/width
or stroke/strokeWidth) so the custom shape is actually rendered end-to-end; use
the same renderTestCase helper to get the rendered container and
querySelectorAll('line') (or a scoped selector) and assert its length equals
data.length and attribute values match the marker positions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cartesian/Bar.tsx`:
- Around line 1116-1122: The guard that skips zero-dimension filtering uses
hasCustomShape derived from a non-null check on shape; change the detection in
the Bar component so hasCustomShape is set only when shape is an actual custom
renderer (e.g., typeof shape === 'function' or React.isValidElement(shape) /
React.isValidElement-like check) instead of any non-null value, so the
early-return condition in the null/zero check (the if block referencing x, y,
width, height and hasCustomShape) continues to filter out width===0/height===0
for default rectangle shapes but allows true custom renderers to bypass it.

In `@test/cartesian/Bar.truncateByDomain.spec.tsx`:
- Around line 155-205: The test 'selectStackGroups' currently calls
expect(spy).toHaveBeenLastCalledWith(...) — update this assertion to use the
project test-helper expectLastCalledWith helper instead; locate the test block
that invokes selectStackGroups and replace the
expect(spy).toHaveBeenLastCalledWith call with a call to
expectLastCalledWith(spy, <same expected object>) so the assertion uses the
helper while keeping the same expected payload (references: test name
'selectStackGroups', selector function 'selectStackGroups', and the local 'spy'
variable).

In `@test/cartesian/Bar.zeroDimensionFiltering.spec.tsx`:
- Around line 272-307: The test currently only asserts selector output
(selectBarRectangles) for the custom HorizonBar but doesn't verify the DOM
rendering; update the spec to also query the rendered output from renderTestCase
(the BarChart with Bar id "bar-marker" and shape HorizonBar) and assert that the
expected <line> elements for each data row are present and have the expected
attributes (e.g., y/x/width or stroke/strokeWidth) so the custom shape is
actually rendered end-to-end; use the same renderTestCase helper to get the
rendered container and querySelectorAll('line') (or a scoped selector) and
assert its length equals data.length and attribute values match the marker
positions.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6ab58e and 0b0c0c2.

📒 Files selected for processing (10)
  • src/cartesian/Bar.tsx
  • src/state/types/BarSettings.ts
  • test/cartesian/Bar.truncateByDomain.spec.tsx
  • test/cartesian/Bar.zeroDimensionFiltering.spec.tsx
  • test/cartesian/Bar/Bar.spec.tsx
  • test/cartesian/Brush.stacked.spec.tsx
  • test/chart/BarChart.spec.tsx
  • test/state/graphicalItemsSlice.spec.ts
  • test/state/selectors/axisSelectors.spec.tsx
  • test/state/selectors/selectStackGroups.spec.tsx

@codecov
Copy link

codecov bot commented Mar 3, 2026

Bundle Report

Changes will increase total bundle size by 1.38kB (0.03%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.27MB 432 bytes (0.03%) ⬆️
recharts/bundle-es6 1.1MB 440 bytes (0.04%) ⬆️
recharts/bundle-umd 540.91kB 85 bytes (0.02%) ⬆️
recharts/bundle-treeshaking-cartesian 633.29kB 421 bytes (0.07%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 85 bytes 540.91kB 0.02%
view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/Bar.js 264 bytes 28.81kB 0.92%
cartesian/CartesianAxis.js 168 bytes 18.75kB 0.9%
view changes for bundle: recharts/bundle-treeshaking-cartesian

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js 421 bytes 633.29kB 0.07%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/Bar.js 264 bytes 27.06kB 0.99%
cartesian/CartesianAxis.js 176 bytes 17.43kB 1.02%

@codecov
Copy link

codecov bot commented Mar 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.21%. Comparing base (d6ab58e) to head (0b0c0c2).
⚠️ Report is 15 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7067   +/-   ##
=======================================
  Coverage   90.20%   90.21%           
=======================================
  Files         526      526           
  Lines       39648    39657    +9     
  Branches     5440     5447    +7     
=======================================
+ Hits        35766    35775    +9     
  Misses       3873     3873           
  Partials        9        9           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@PavelVanecek PavelVanecek merged commit eb30863 into main Mar 3, 2026
47 of 48 checks passed
@PavelVanecek PavelVanecek deleted the copilot/fix-missing-error-bars-boxplot branch March 3, 2026 13:35
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.

[Unreleased] Missing error bars in BoxPlot example

2 participants