2026, Jan 04 11:00

How to Get Excel-like Rounded Curves in Matplotlib: Centripetal Catmull–Rom Spline That Passes Through Your Points

Make Excel-like rounded line charts in Python using a centripetal Catmull–Rom spline in Matplotlib. Interpolates through points and fixes endpoint behavior.

Excel can draw pleasantly rounded line charts from sparse, noisy points. If you try to replicate that look in matplotlib with basic line plots or by fitting polynomials, the result often has sharp corners or unwanted wiggles. The practical question is how to get a similarly smooth, rounded curve in Python while keeping the curve passing through the original points.

Baseline: smoothing with a spline that still doesn’t look like Excel

The following snippet demonstrates a straightforward approach using a smoothing spline. It does produce a smooth line, but the visual character can differ from the “rounded” look many expect from Excel’s smoothed lines.

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import UnivariateSpline

# Sample data (replace with your own x, y data)
x_data = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
y_data = np.array([1, 2, 0, 3, 2, 5, 4, 6, 4, 5])  # Noisy or scattered data

# Sort data (required for spline)
order_idx = np.argsort(x_data)
x_ord = x_data[order_idx]
y_ord = y_data[order_idx]

# Create spline
uni_spline = UnivariateSpline(x_ord, y_ord, s=0.9)  # s controls smoothness (lower = smoother)
xs_dense = np.linspace(min(x_ord), max(x_ord), 100)  # More points for smooth curve
ys_dense = uni_spline(xs_dense)

# Plot original and smoothed data
plt.figure(figsize=(8, 6))
plt.plot(x_data, y_data, color='red', label='Original Data')  # Original points
plt.plot(xs_dense, ys_dense, color='blue', label='Smoothed Spline')  # Smooth curve
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Smooth Plot with Spline Interpolation')
plt.legend()
plt.grid(True)
plt.show()

In visual comparisons, the curve from this approach may still feel different from Excel’s smoothed line, especially around corners where you want gentle rounding without obvious flattening or sharp turns.

What actually gives the “Excel-like” rounded look

A centripetal Catmull–Rom spline produces a curve that passes through the given points and, with its centripetal parameterization, tends to form rounded transitions between them. This approach matches the sought “rounded corners” feel without resorting to high-degree polynomials. One practical detail is endpoints: the standard formulation doesn’t define the first and last segments out of the box. The fix is to add a virtual point at the beginning and at the end, chosen so that the first three and last three points are collinear. That provides well-behaved segments at the boundaries.

Solution: centripetal Catmull–Rom spline with boundary handling

The code below builds a centripetal Catmull–Rom chain through your points. It augments the ends with two additional collinear points and then evaluates each segment densely to get a smooth curve. The parameter alpha controls the type: 0.5 yields the centripetal variant; 0.0 gives uniform and 1.0 chordal.

import numpy as np
import matplotlib.pyplot as plt

BLOCK_SIZE: int = 4

def segment_qty(node_chain: tuple) -> int:
  # One segment per 4-point block; subtract 3 from the count of points
  return len(node_chain) - (BLOCK_SIZE - 1)

def flatten_lists(nested):
  # Flatten e.g. [[1,2],[3],[4,5]] into [1,2,3,4,5]
  return [item for sub in nested for item in sub]

def romcat_segment(
  p0: tuple,
  p1: tuple,
  p2: tuple,
  p3: tuple,
  samples_per_seg: int,
  alpha: float = 0.5,
):
  """
  Compute sample points for a centripetal Catmull–Rom segment between p1 and p2.
  :param p0, p1, p2, p3: Control points (x, y)
  :param samples_per_seg: Number of sample points on this segment
  :param alpha: 0.5 centripetal, 0.0 uniform, 1.0 chordal
  :return: Array of sampled (x, y) points
  """
  def tstep(t_prev: float, a: tuple, b: tuple) -> float:
    ax, ay = a
    bx, by = b
    dx, dy = bx - ax, by - ay
    dist = (dx ** 2 + dy ** 2) ** 0.5
    return t_prev + dist ** alpha

  t0: float = 0.0
  t1: float = tstep(t0, p0, p1)
  t2: float = tstep(t1, p1, p2)
  t3: float = tstep(t2, p2, p3)
  tau = np.linspace(t1, t2, samples_per_seg).reshape(samples_per_seg, 1)

  A1 = (t1 - tau) / (t1 - t0) * p0 + (tau - t0) / (t1 - t0) * p1
  A2 = (t2 - tau) / (t2 - t1) * p1 + (tau - t1) / (t2 - t1) * p2
  A3 = (t3 - tau) / (t3 - t2) * p2 + (tau - t2) / (t3 - t2) * p3
  B1 = (t2 - tau) / (t2 - t0) * A1 + (tau - t0) / (t2 - t0) * A2
  B2 = (t3 - tau) / (t3 - t1) * A2 + (tau - t1) / (t3 - t1) * A3
  seg_pts = (t2 - tau) / (t2 - t1) * B1 + (tau - t1) / (t2 - t1) * B2
  return seg_pts

def romcat_path(nodes: tuple, samples: int) -> list:
  """
  Build a centripetal Catmull–Rom polyline from a sequence of points.
  :param nodes: Input points
  :param samples: Points per segment in the output
  :return: List of sampled points across all segments
  """
  # Add start/end helpers so the first three and last three are collinear
  xs = nodes[0][0] - (nodes[1][0] - nodes[0][0])
  ys = nodes[0][1] - (nodes[1][1] - nodes[0][1])
  xe = nodes[-1][0] - (nodes[-2][0] - nodes[-1][0])
  ye = nodes[-1][1] - (nodes[-2][1] - nodes[-1][1])
  ctrl: tuple = tuple([(xs, ys)] + list(nodes) + [(xe, ye)])

  quad_gen = (
    (ctrl[start + d] for d in range(BLOCK_SIZE))
    for start in range(segment_qty(ctrl))
  )
  segs = (romcat_segment(*quad, samples) for quad in quad_gen)
  return flatten_lists(segs)

if __name__ == "__main__":
  DATA_KNOTS: tuple = ((0, 1.5), (2, 2), (3, 1), (4, 0.5), (5, 1), (6, 2), (7, 3))
  SAMPLES: int = 100

  path_pts: list = romcat_path(DATA_KNOTS, SAMPLES)
  assert len(path_pts) == (segment_qty(DATA_KNOTS) + 2) * SAMPLES

  plt.plot(*zip(*path_pts), c="blue")
  plt.plot(*zip(*DATA_KNOTS), c="red", linestyle="none", marker="o")
  plt.show()

Why this is useful

When you need a curve that goes exactly through your data points but looks more like a rounded, continuous stroke than a polyline, the centripetal Catmull–Rom construction is a practical choice. It helps avoid the “pointy corner” look of straight segments while keeping control over the shape through your original points and a single alpha parameter for the flavor of the spline.

Wrap-up

If your goal is the kind of smooth, rounded appearance people often associate with Excel’s smoothed lines, move from generic smoothing to a centripetal Catmull–Rom spline that interpolates your data. Add two collinear helper points at the ends so the endpoints behave nicely, sample each segment densely, and plot the resulting chain. This delivers a natural-looking curve, preserves your original points, and gives you a predictable way to control the smoothness.