@visx/curve
@visx/curve is a small set of named exports for choosing how SVG lines interpolate between data points. It re-exports D3's linear, step, basis, bundle, cardinal, Catmull-Rom, monotone, and natural curve factories through @visx/vendor. You normally pass one of them to the curve prop on @visx/shape components such as LinePath. It does not draw a chart, scale data, render axes, handle tooltips, or animate anything by itself, and despite the visx name its own API contains no React components.
Worth installing beside @visx/shape when you want visx-aligned D3 curve imports. Outside a visx chart stack, install d3-shape directly or choose a complete charting library if interpolation is only one small part of the job.
Use it if
- You already use @visx/shape and want a supported named import for line or area interpolation
- You need to switch among linear, stepped, monotone, basis, cardinal, Catmull-Rom, or natural curves without importing D3 directly
- You are building a custom chart system from low-level visx pieces and want curve factories aligned with visx's vendored D3 version
- You want named ESM exports from a package marked side-effect-free so a modern bundler can retain only the curve code your chart uses
- You are not using visx: the source file simply re-exports curve factories from @visx/vendor/d3-shape, so direct d3-shape imports are clearer and avoid a visx-specific wrapper
- You expect a ready-made line chart with axes, scales, labels, legends, responsive sizing, or tooltips: this package supplies interpolation functions only
- You need animation or transitions: the visx README explicitly leaves animation out and expects you to choose a separate React animation library
- You need a smoothing mode that can never misrepresent peaks: basis, cardinal, natural, and Catmull-Rom interpolation can visually bend between samples; use linear or the correctly oriented monotone curve for sensitive data
- You support IE11 or other legacy browsers: the visx 4 migration guide says the build now targets modern browsers and directs IE11 users to stay on visx 3
Setup reality
Installing npm install @visx/curve gives you the curve factories and bundled TypeScript declarations with no peer dependency. The package does bring @visx/vendor 4.0.0, which in visx 4 wraps d3-shape 3 and d3-path 3. In a normal React chart you will separately install @visx/shape and whichever scale, axis, group, tooltip, or responsive packages you actually use. The root visx README says its v4 React components require React 18 or 19, even though @visx/curve itself has no React peer and its exports are plain functions. Choose the curve based on data meaning, not visual softness: curveMonotoneX assumes x is the independent ordered dimension, curveMonotoneY is for the opposite orientation, and stepBefore versus stepAfter changes which sample owns the interval. Closed and open variants change endpoint behavior and need enough points to look sensible. Parameterized factories use calls such as curveCardinal.tension(0.4), curveCatmullRom.alpha(0.5), and curveBundle.beta(0.85). The bundle curve is for lines, not areas. Curves only change path interpolation; sorting data, handling missing values, clipping overshoot, scales, rendering, animation, and accessible descriptions remain your responsibility. Prefer named imports so bundlers can use the package's sideEffects: false metadata.
Patterns
Connect points with straight segmentsdraw-linear-line
import { LinePath } from '@visx/shape';
import { curveLinear } from '@visx/curve';
<LinePath
data={points}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={curveLinear}
stroke="currentColor"
/>Linear interpolation is the safest default when each sample should remain visibly exact.
Smooth an x-ordered series without vertical overshootsmooth-time-series
import { curveMonotoneX } from '@visx/curve';
<LinePath
data={[...points].sort((a, b) => a.time - b.time)}
x={(d) => xScale(d.time)}
y={(d) => yScale(d.value)}
curve={curveMonotoneX}
/>Sort by x first; curveMonotoneX assumes x is monotonic and preserves monotonicity in y between neighboring samples.
Use monotone interpolation along the y axissmooth-vertical-series
import { curveMonotoneY } from '@visx/curve';
<LinePath
data={[...points].sort((a, b) => a.rank - b.rank)}
x={(d) => xScale(d.value)}
y={(d) => yScale(d.rank)}
curve={curveMonotoneY}
/>Use this only when y is the ordered independent dimension; ordinary time series normally need curveMonotoneX.
Draw a natural cubic splinedraw-natural-spline
import { curveNatural } from '@visx/curve';
<LinePath
data={points}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={curveNatural}
/>Natural splines are visually soft but can bend beyond sampled values, so they are a poor fit for strict threshold or financial charts.
Hold each value until the next sampledraw-step-after
import { curveStepAfter } from '@visx/curve';
<LinePath
data={events}
x={(d) => xScale(d.at)}
y={(d) => yScale(d.value)}
curve={curveStepAfter}
/>curveStepAfter changes y after reaching the next x coordinate, which fits values that remain active until the next event.
Apply the next value before its sample pointdraw-step-before
import { curveStepBefore } from '@visx/curve';
<LinePath
data={events}
x={(d) => xScale(d.at)}
y={(d) => yScale(d.value)}
curve={curveStepBefore}
/>The ownership of each horizontal interval is opposite curveStepAfter; check the domain meaning before choosing one.
Switch values halfway between samplesdraw-midpoint-steps
import { curveStep } from '@visx/curve';
<LinePath
data={points}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={curveStep}
/>curveStep moves the vertical transition to the midpoint, which looks balanced but may imply a transition time not present in the data.
Tune cardinal spline tensionadjust-cardinal-tension
import { curveCardinal } from '@visx/curve';
const curve = curveCardinal.tension(0.35);
<LinePath
data={points}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={curve}
/>Tension must be between 0 and 1; increasing it pulls the curve toward straighter segments.
Configure Catmull-Rom parameterizationadjust-catmull-rom-alpha
import { curveCatmullRom } from '@visx/curve';
const centripetal = curveCatmullRom.alpha(0.5);
<LinePath
data={points}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={centripetal}
/>Alpha 0.5 selects centripetal parameterization; unevenly spaced points still deserve visual tests for loops or overshoot.
Close a polyline back to its first pointdraw-closed-loop
import { curveLinearClosed } from '@visx/curve';
<LinePath
data={polygonPoints}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={curveLinearClosed}
/>The closed factory adds the closing segment, so do not duplicate the first point at the end unless your data model requires it.
Use an open basis splinedraw-open-basis-curve
import { curveBasisOpen } from '@visx/curve';
<LinePath
data={controlPoints}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={curveBasisOpen}
/>Open spline variants do not pass through or connect the outer control points and need enough points to produce a useful segment.
Tune a bundled linetune-bundle-curve
import { curveBundle } from '@visx/curve';
const bundled = curveBundle.beta(0.85);
<LinePath
data={routePoints}
x={(d) => xScale(d.x)}
y={(d) => yScale(d.y)}
curve={bundled}
/>curveBundle is intended for lines and does not implement the area boundary methods needed by area generators.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| d3-shape | npm | Use it directly when you are outside visx or also need line, area, arc, pie, stack, and symbol generators. |
| @nivo/line | npm | Use it when you want an opinionated React line chart with axes, legends, tooltips, responsiveness, and animation included. |
| recharts | npm | Use it when a component-level React chart API is more valuable than composing low-level visualization primitives. |