docs: add computeWaterfallData utility for waterfall charts#7011
docs: add computeWaterfallData utility for waterfall charts#7011ckifer merged 7 commits intorecharts:mainfrom
Conversation
Add a utility function that transforms raw data into range values
for rendering waterfall charts with the existing <Bar> component.
Recharts' Bar already supports range values ([low, high] arrays) as
dataKey values, which renders bars spanning exact Y-axis positions.
This utility computes the cumulative positions needed for waterfall
charts, handling positive, negative, mixed, and total bars.
Usage:
import { computeWaterfallData, BarChart, Bar, Cell } from 'recharts';
const data = computeWaterfallData({
data: rawData,
dataKey: 'value',
totalKey: 'isTotal',
});
<BarChart data={data}>
<Bar dataKey="waterfallRange">
{data.map((d, i) => (
<Cell key={i} fill={d.value >= 0 ? 'teal' : 'gray'} />
))}
</Bar>
</BarChart>
Closes recharts#7010
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR adds a new WaterfallExample component to the BarChart documentation gallery. The component renders a waterfall chart using Recharts with utilities to compute waterfallRange data from raw entries, custom coloring logic (green for positive, red for negative, blue for totals), and a custom tooltip display. Changes
Possibly related PRs
Suggested reviewers
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ❌ 4❌ Failed checks (4 warnings)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/util/waterfall.ts`:
- Around line 94-102: The function resolveBoolean uses an `as` cast to access a
keyed property on entry; remove the cast by constraining the generic type so
entry is indexable (e.g. change the signature to resolveBoolean<T extends
Record<string, unknown>>(entry: T, key: string | ((entry: T) => boolean) |
undefined): boolean) and then return Boolean(entry[key]) for the string-key
branch; keep the function behavior the same but eliminate the `as` assertion and
rely on the constrained generic type instead.
- Around line 87-92: The resolveValue function uses 'as' casts and can return
undefined cast to number; change its signature and implementation to avoid 'as'
by constraining the generic (e.g., T extends Record<string, number>) or by
runtime-narrowing the property access: if key is a function call it, and if key
is a string check that entry hasOwnProperty(key) and typeof value === 'number'
before returning; if the property is missing or not a number throw or return a
defined fallback to prevent NaN. Update the resolveValue definition and its
callers to match the tighter type or handle the thrown error.
In `@storybook/stories/Examples/BarChart/WaterfallChart.stories.tsx`:
- Around line 2-3: The file imports computeWaterfallData from an internal path;
instead, remove the separate import from '../../../../src/util/waterfall' and
import computeWaterfallData from the public API along with the other symbols
already imported from '../../../../src' (i.e., add computeWaterfallData to the
existing import list that currently contains Bar, BarChart, CartesianGrid, Cell,
ResponsiveContainer, Tooltip, XAxis, YAxis) so all non-test imports use the
public entry point.
🧹 Nitpick comments (3)
src/util/waterfall.ts (1)
132-134: Total bar semantics:cumulativeValueafter a total bar may surprise consumers.When
isTotalis true,runningTotalis not updated — which is correct for bar positioning. However, thecumulativeValueemitted for the total bar reflects the pre-total running sum, not the total'svalue. For a total bar withvalue: 70following items summing to 70, the cumulativeValue happens to match, but if a user intentionally sets a different total value (e.g., an adjusted figure), the cumulativeValue won't reflect it. Consider documenting this in the JSDoc forcumulativeValue, since it always tracks the running sum of non-total entries.storybook/stories/Examples/BarChart/WaterfallChart.stories.tsx (1)
83-86: Consider reusing thegetFillhelper for the BudgetaryImpact story.The color logic on line 85 duplicates the positive/negative coloring from
getFill(lines 29–32), minus the total-bar handling. SinceBudgetaryImpactalso has a total bar (line 61), it would benefit from usinggetFillto consistently color total bars withTOTAL_COLOR.test/util/waterfall.spec.ts (1)
89-97: Consider adding a test for property name collisions.Since
computeWaterfallDataspreads the original entry and then addswaterfallRange,value,cumulativeValue,isTotal, andentry, an input object that already has one of these keys (e.g.,{ name: 'A', value: 10, isTotal: false, waterfallRange: 'custom' }) will be silently overwritten. A test documenting this behavior would clarify the contract for consumers.
- Remove `as` casts in resolveValue/resolveBoolean, use constrained generics - Import computeWaterfallData from public API in storybook story - Reuse getFill helper in BudgetaryImpact story for consistent total bar coloring - Clarify cumulativeValue JSDoc for total bar behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/util/waterfall.ts`:
- Around line 89-95: resolveValue currently can return NaN when entry[key] is
undefined or non-coercible; change resolveValue(entry, key) to coerce the value
to a number then check Number.isFinite(result) and return 0 as a safe fallback
(or throw if you prefer) instead of returning NaN so downstream calculations
(runningTotal, waterfallRange) don't silently break; ensure you handle both
branches (when key is a function and when key is a string) and apply the same
finite-number check before returning.
🧹 Nitpick comments (2)
src/util/waterfall.ts (2)
37-57: Consider constrainingTonWaterfallOptionsto matchcomputeWaterfallData.
WaterfallOptions<T>leavesTunconstrained, butcomputeWaterfallDatarequiresT extends Record<string, unknown>. Adding the same bound to the interface prevents consumers from constructing aWaterfallOptions<T>that the function can never accept.♻️ Suggested change
-export interface WaterfallOptions<T> { +export interface WaterfallOptions<T extends Record<string, unknown>> {(Same for
WaterfallDatum<T>on line 59.)
153-160: Property name collision risk with spread.If
Talready has fields namedwaterfallRange,value,cumulativeValue,isTotal, orentry, the computed fields silently overwrite them. The originals are still accessible via the nestedentryproperty, so this is workable, but a brief JSDoc note warning consumers about reserved property names would prevent confusion.
src/util/waterfall.ts
Outdated
| function resolveValue<T extends Record<string, unknown>>(entry: T, key: string | ((entry: T) => number)): number { | ||
| if (typeof key === 'function') { | ||
| return key(entry); | ||
| } | ||
| const raw = entry[key]; | ||
| return typeof raw === 'number' ? raw : Number(raw); | ||
| } |
There was a problem hiding this comment.
resolveValue can silently produce NaN for missing or non-numeric properties.
When entry[key] is undefined (missing key) or a non-coercible value, Number(raw) returns NaN, which silently propagates into every downstream calculation (runningTotal, waterfallRange). Returning 0 (or throwing) would be safer than producing invisible/broken bars.
♻️ Suggested fix
function resolveValue<T extends Record<string, unknown>>(entry: T, key: string | ((entry: T) => number)): number {
if (typeof key === 'function') {
return key(entry);
}
const raw = entry[key];
- return typeof raw === 'number' ? raw : Number(raw);
+ if (typeof raw === 'number') {
+ return raw;
+ }
+ const coerced = Number(raw);
+ return Number.isNaN(coerced) ? 0 : coerced;
}🤖 Prompt for AI Agents
In `@src/util/waterfall.ts` around lines 89 - 95, resolveValue currently can
return NaN when entry[key] is undefined or non-coercible; change
resolveValue(entry, key) to coerce the value to a number then check
Number.isFinite(result) and return 0 as a safe fallback (or throw if you prefer)
instead of returning NaN so downstream calculations (runningTotal,
waterfallRange) don't silently break; ensure you handle both branches (when key
is a function and when key is a string) and apply the same finite-number check
before returning.
| /> | ||
| <Bar dataKey="waterfallRange" isAnimationActive={false}> | ||
| {budgetWaterfall.map((entry, index) => ( | ||
| <Cell key={`cell-${index}`} fill={getFill(entry)} /> |
There was a problem hiding this comment.
Please don't use Cell in new charts, we're going to remove it in 4.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
This kind of feels like user land code to me as a utility. Not that it's a bad thing, but not all waterfall implementations may want this same behavior. Once we export this we are responsible for it in a way that we are not if we just say, document it as a possible option |
- Add API example for computeWaterfallData, WaterfallDatum, and WaterfallOptions to satisfy omnidoc verifyExamples tests - Remove Cell from storybook story per maintainer review - Update JSDoc example in waterfall.ts to not use Cell Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
+1 to @ckifer. I think this would be a great example chart (as a sibling of https://recharts.github.io/en-US/examples/Candlestick/ and https://recharts.github.io/en-US/examples/PopulationPyramid/) but not as a general library export. It works for one specific data format but not others, we're not helping. Would you like to turn this into a website example? |
|
Alternatively we could start a second project, one where we're more opinionated about the data shapes, and provide some pre-made charts. Sort of competition to what shadcn and evilcharts are doing. If we put it in separate npm package then we have 100% freedom, if we put it in the main lib we need to double check that we can tree-shake the extra code. |
|
Makes sense — I'll convert this to a website example (like Candlestick/PopulationPyramid) rather than a library export. Will update the PR. |
Remove computeWaterfallData from library exports (src/index.ts, src/util/waterfall.ts, snapshot files) and omnidoc API examples. Instead, add a Waterfall example to the BarChart examples page (like Candlestick and PopulationPyramid) with the computation logic defined inline in the example code. This addresses maintainer feedback on PR recharts#7011 to keep waterfall as a demonstrated pattern rather than a library export. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Updated — removed the library export entirely and converted to a self-contained website example at |
PavelVanecek
left a comment
There was a problem hiding this comment.
A new VR test for the new example would be nice too
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7011 +/- ##
========================================
Coverage 90.09% 90.10%
========================================
Files 523 525 +2
Lines 38921 39162 +241
Branches 5353 5408 +55
========================================
+ Hits 35067 35285 +218
- Misses 3845 3868 +23
Partials 9 9 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Adds a
computeWaterfallData()utility function that transforms raw data into range values for rendering waterfall charts with the existing<Bar>component.src/util/waterfall.ts— pure function with full TypeScript typescomputeWaterfallData,WaterfallOptions,WaterfallDatumfrom package indexScreenshot
Motivation
Waterfall charts are commonly needed in financial, analytical, and reporting dashboards. Currently users must manually compute bar positions. This utility does that computation, outputting
[barBottom, barTop]range values that Recharts<Bar>already supports natively.Usage
Design decisions
dataKeyandtotalKey, matching Recharts conventionsTest plan
Related: #7010
Summary by CodeRabbit