Home/Chart Types/Scientific/Poincaré plot
ScientificAdvanced

Poincaré Plot

A scatter diagram in which each value of a time series is plotted against the next value, exposing short- and long-term variability through the shape of the resulting cloud. The clinical workhorse of heart rate variability analysis.

// 01The chart

What it looks like

Example — RR intervals (heart rate variability)300 beats
RR(n) msRR(n+1) ms60011006001100

A Poincaré plot of RR intervals showing a tight elliptical cloud along the line of identity, indicating healthy heart rate variability. The dashed ellipse marks the SD1 / SD2 cloud envelope.

// 02Definition

What is a Poincaré plot?

A Poincaré plot (also called a return map or lag plot) is a scatter diagram in which each data point x(n) in a time series is plotted against the next value x(n+1). The x-axis shows the current value, and the y-axis shows the immediately following value. This simple transformation converts a one-dimensional time series into a two-dimensional picture that reveals the relationship between consecutive measurements.

In cardiology, where the technique is most widely used, the time series consists of RR intervals (the time between successive heartbeats). The resulting scatter cloud’s shape, size, and orientation encode clinically meaningful information about heart rate variability (HRV).

The cloud’s width perpendicular to the line of identity (the 45° diagonal where x = y) measures short-term variability (SD1), while its length along the diagonal measures long-term variability (SD2). Together, they provide a compact, intuitive summary of autonomic nervous system function.

Beyond cardiology, Poincaré plots are used in nonlinear-dynamics work to visualize the qualitative behavior of dynamical systems — detection of stationarity, regime changes, and weak periodicities. In every case, the geometry around the identity line is what carries the meaning.

Origin: Named after Henri Poincaré, the French mathematician who pioneered the qualitative study of dynamical systems. The specific application to heart rate variability was popularized in the 1990s by Tulppo, Huikuri, and colleagues studying autonomic cardiac control.

// 03When to use

When a Poincaré plot is the right call

Reach for a Poincaré plot whenever the question is about how the next sample relates to the current one. In cardiology that is the canonical short-term HRV question; in dynamics it is a quick visual stationarity check.

✓Use a Poincaré plot when…
  • Analyzing heart rate variability from RR intervals
  • Studying short- and long-term variability of any time series
  • Investigating the qualitative dynamics of a measured process
  • Looking for regime changes or non-stationarity
  • Comparing variability across patients, sessions, or experimental conditions
  • Quantifying autonomic balance through SD1 / SD2 ratios
  • You want a single picture that summarizes both noise and trend

// 04When not to use

When a Poincaré plot is the wrong call

The chart only makes sense when the data is genuinely a time series with a stable lag. Outside that, simpler charts almost always communicate better.

×Avoid a Poincaré plot when…
  • Your data is not ordered in time
  • Sampling intervals are highly irregular and you can’t justify pairing samples
  • The series is too short (under ~50 points) for the cloud to develop
  • A simple line chart over time would tell the same story to a less specialized audience
  • You need to see absolute timing or frequency content — use a spectrogram instead
  • There are too many ectopic beats / artifacts and the cloud is dominated by noise
  • You’re trying to communicate to a non-numerate audience

// 05Data requirements

What your data needs to look like

Before building the chart, your dataset needs to fit a specific shape. Use this checklist to confirm yours does.

Shape

One row per sample, ordered in time, with a numeric value column. The chart is built from the lag-1 pairing of consecutive values.

Minimum rows

50 samples. Below that, the cloud is too sparse to see structure.

Maximum rows

No effective upper bound. Long Holter recordings produce hundreds of thousands of pairs without trouble.

Required fields
trequired
integer / timestamp

The sample index or time at which each value was recorded. Used to lag the series correctly; ordering is what matters, not the absolute timestamp.

valuerequired
number

The measurement at each time step. In cardiology this is the RR interval (ms); in dynamics it is any state variable measured at regular intervals.

Example data
trr_ms
1868
2871
3880
4892
5884
6879

Tip: in cardiology you almost always want to filter ectopic and artifact beats before plotting. Tools like Kubios, RHRV’s FilterNIHR(), and Python’s hrv-analysis all do this. Document the filter; aggressive cleaning deflates SD1.

// 06Anatomy

Parts of a Poincaré plot

Five elements make up every Poincaré plot: two equal-scale axes, the line of identity, the point cloud, and the SD1 / SD2 ellipse drawn over it.

ABCDE
A — Y-axis (RR(n+1)): The next value in the time series — same scale as the x-axis
B — X-axis (RR(n)): The current value in the time series
C — Line of identity: The 45-degree dashed line where x equals y
D — Point cloud: Each point pairs one sample with the next; cloud shape encodes variability
E — SD1 / SD2 ellipse: Rotated ellipse with semi-axes equal to SD1 and SD2 — the geometric variability summary

// 07Step-by-step

Step-by-step: how to build a good Poincaré plot

Eight steps that work regardless of tool. The artifact filter and the SD1 / SD2 ellipse are the two non-trivial moves; the rest is mechanical once the data is in shape.

  1. 1

    Confirm the series is genuinely a time series

    Order matters. If the rows are unordered or the time gaps are irregular, the lag transform is meaningless. Sort by time and either verify constant sampling or document the gap distribution before plotting.
  2. 2

    Choose the lag

    The default lag is 1 — plot x(n) against x(n+1). Larger lags expose periodicity (lag = period gives a tight diagonal). Stick with lag 1 for the canonical ‘Poincaré plot’ unless you have a reason to deviate.
  3. 3

    Filter or annotate non-stationary segments

    Drift, regime changes, and ectopic beats produce off-diagonal clusters that swamp the variability story. Either filter them or color them differently so readers can see what’s signal vs artifact.
  4. 4

    Draw the line of identity

    Add a 45-degree dashed line where x = y. Without it, the cloud has no anchor and SD1 / SD2 mean nothing geometrically.
  5. 5

    Compute SD1 and SD2

    SD1 = std(x − y) / sqrt(2) and SD2 = std(x + y) / sqrt(2). They project the point cloud onto the perpendicular and parallel directions of the identity line.
  6. 6

    Overlay the SD1 / SD2 ellipse

    Draw an ellipse centered at the cloud mean with semi-axes equal to SD1 (perpendicular to identity) and SD2 (along identity), rotated 45 degrees. This is the classical Poincaré ellipse.
  7. 7

    Use partial transparency for overplotting

    Even moderate-sized series produce overplotting. Use alpha around 0.3–0.5 so density becomes visible without losing the underlying scatter.
  8. 8

    Title the takeaway

    Replace “Poincaré plot of RR intervals” with “Tight elliptical cloud, SD1 = 38 ms, SD2 = 92 ms — healthy heart rate variability” so the chart announces the conclusion.

// 08Real-world examples

Where you’ll see Poincaré plots used

Cardiology is the headline application but Poincaré geometry shows up wherever a single time series needs a quick visual summary of short-term variability.

01

Cardiology: HRV from a Holter recording

A 24-hour Holter monitor yields ~100,000 RR intervals. The Poincaré plot summarizes them in one figure; SD1 / SD2 are reported alongside time- and frequency-domain HRV metrics in a clinical report.

Cardiology
02

Sleep medicine: autonomic state during sleep stages

Sleep researchers split a recording into REM / non-REM segments and produce one Poincaré plot per segment, color-coded. The contrast in cloud size visualizes the autonomic state shifts directly.

Sleep
03

Dynamics: stationarity check for a sensor signal

Engineering teams use Poincaré plots as a quick visual stationarity check on vibration / acoustic sensor data. Off-diagonal smearing is the immediate red flag for drift or fault onset.

Engineering
04

Sports science: training load monitoring

Athletes record morning HRV over weeks. Poincaré plots stacked by week reveal recovery patterns, with a shrinking SD1 indicating accumulated fatigue.

Sports science

// 09Variations

Variants of the Poincaré plot

The basic chart has a few common variants — lag choice, density encoding, and segment overlay — each suited to a slightly different question.

Lag-k Poincaré plot

Plot x(n) against x(n+k) for k > 1. Tight diagonal alignment at a specific k reveals periodicity at period k.

Density Poincaré plot

Replace points with a 2D density / hexbin to handle very long recordings. Reveals the same SD1 / SD2 envelope without overplotting.

Segmented Poincaré plot

Color-code points by experimental segment (rest, exercise, recovery) so the contrast in cloud shape is immediately visible.

3D Poincaré plot

Plots x(n), x(n+1), x(n+2) on three axes for higher-order embedding. Used in nonlinear dynamics rather than clinical HRV.

// 10Comparisons

How it compares

Poincaré plots sit at the intersection of scatter plots and time-series visualization. The most useful comparisons are with general scatter plots, the broader lag-plot family, and recurrence plots from nonlinear dynamics.

Poincaré plot vs scatter plot

A scatter plot pairs two different variables; a Poincaré plot pairs one variable with itself, lagged. The chart geometries look identical but the interpretation is completely different — Poincaré reveals temporal structure that a scatter cannot.

Poincaré plot

Lag-1 scatter of one variable against itself. Reveals short- and long-term variability of a time series.

  • Single variable, lagged by 1
  • Identity line is the reference
  • SD1 / SD2 quantify variability

Scatter plot

Two different variables plotted against each other. Reveals correlation and bivariate structure, not temporal structure.

  • Two variables, no lag
  • No special reference line
  • Correlation, not variability

Poincaré plot vs lag plot

A Poincaré plot is a lag plot at lag 1, with the line of identity and the SD1 / SD2 ellipse drawn explicitly. A general lag plot is a family of charts at lags 1, 2, 3, … used for periodicity discovery.

Poincaré plot

Lag = 1 specifically, with identity line and SD1 / SD2 ellipse. Used for variability and stationarity assessment.

  • Lag = 1 only
  • Identity line and ellipse drawn
  • Cardiology and dynamics convention

Lag plot (general)

Family of lag-k plots used to discover periodicity and autocorrelation structure across multiple lags.

  • Variable lag (1, 2, 3, ...)
  • Used for periodicity scanning
  • Often shown as a small-multiples grid

Poincaré plot vs recurrence plot

Both come from nonlinear-dynamics tradition, but they answer different questions. Poincaré asks ‘how similar is each value to the next?’; recurrence asks ‘when does this state recur in time?’ The visuals look nothing alike.

Poincaré plot

Lag-1 scatter showing short-term variability. Cloud-shaped, anchored on the identity line.

  • Lag-1 scatter
  • Compact cloud near identity
  • Local variability summary

Recurrence plot

Time × time matrix marking pairs of timestamps with similar state. Looks like a checkerboard / textile pattern.

  • Time × time grid
  • Reveals periodicity globally
  • Used for chaos and signal classification

// 11Common mistakes

Common Poincaré plot mistakes

The chart’s simplicity is deceptive. Most failures come from forgetting that order matters, that artifacts dominate, or that the geometry depends on equal axis scales.

Forgetting equal axis scales

If the x and y axes have different scales, the line of identity is no longer 45 degrees and SD1 / SD2 stop projecting onto perpendicular and parallel directions.

Skipping artifact correction

Even one ectopic beat creates two dramatic off-diagonal points that swamp the cloud. Always filter and document the filter.

Using mismatched axis ranges across patients

When comparing recordings, lock both axes to the same range. Otherwise the visual cloud size lies.

Omitting SD1 / SD2 numbers

Most readers can’t back out variability from the visual alone. Print the numbers in the title or as annotations.

Using a non-1 lag without saying so

If you compute the chart at lag = 2 or higher, label it explicitly. The default convention is lag = 1.

Treating the chart as a recurrence plot

Poincaré and recurrence plots are different charts answering different questions. Don’t use the names interchangeably.

// 12Accessibility

Accessibility checklist

Run through this list before publishing. The chart should still communicate its message to readers using assistive technology, color-blind users, keyboard navigation, and reduced-motion settings.

  • ✓

    Color contrast for points and reference lines

    WCAG 1.4.3
    The point cloud should reach 3:1 contrast against the chart background; the line of identity, ellipse outline, and any annotations should reach 4.5:1 so they remain visible at thumbnail size.
  • ✓

    Don’t encode meaning by color alone

    WCAG 1.4.1
    When you split the cloud (e.g., normal vs. ectopic beats), use both color and a marker shape so color-vision-deficient readers can still distinguish the groups.
  • ✓

    Provide a meaningful text alternative

    WCAG 1.1.1
    An accessible name should describe the takeaway: “Tight elliptical cloud along the identity line; SD1 = 38 ms, SD2 = 92 ms, indicating healthy short- and long-term heart rate variability.”
  • ✓

    Expose the underlying time series

    WCAG 1.3.1
    Offer a downloadable CSV of the original RR intervals plus the computed SD1 / SD2. The Poincaré representation is essentially impossible to experience without sight.
  • ✓

    Label SD1 and SD2 explicitly

    WCAG 3.3.2
    Caption the chart with both the values and the units (typically ms). Numbers without labels are meaningless to anyone unfamiliar with HRV conventions.
  • ✓

    Respect prefers-reduced-motion

    WCAG 2.3.3
    Avoid animated unfolding of the cloud unless the animation is gated behind a prefers-reduced-motion: no-preference media query.
  • ✓

    Resizable and zoomable

    WCAG 1.4.4
    Use a responsive viewBox so the chart remains legible at 200% browser zoom. Make sure the ellipse outline and identity line stay visible at every container width.

// 13Best practices

Design and craft tips

A short list of dos and don’ts that consistently separate publication-quality Poincaré plots from the demo-grade ones produced by default in most scripting environments.

Do

Always draw the identity line

Without the 45-degree dashed line, SD1 / SD2 lose their geometric meaning. The line is what makes the chart a Poincaré plot rather than just a lag scatter.
×Don’t

Use a lag other than 1 without saying so

Lag = 1 is the canonical Poincaré plot. If you use a longer lag for a specific reason, say so explicitly in the title or caption.
Do

Report SD1 and SD2 numerically

Most readers can’t back out SD1 / SD2 from the visual. Print the numbers in the title or as annotations on the chart.
×Don’t

Hide artifact beats silently

Ectopic / artifact beats produce dramatic off-diagonal points. Filter them, but document the filter — don’t pretend they were never there.
Do

Use partial transparency for the points

Overplotting is the norm. Alpha 0.3–0.5 lets density become visible without losing individual outliers.
×Don’t

Compare two Poincaré plots with different axes

When comparing patients, recordings, or time periods, lock the axes to the same range. Otherwise the eye reads the comparison wrong.
Do

Annotate regime changes

If the recording spans rest, exercise, and recovery, color or facet the cloud by regime. The contrast is usually the headline.
×Don’t

Confuse Poincaré plot with recurrence plot

Recurrence plots are a different chart entirely. They visualize when a state recurs in time, not the lag-1 scatter. Don’t use the names interchangeably.

// 15Tool instructions

How to build it in your tool of choice

Recipes for the libraries and clinical tools that ship a Poincaré implementation. RHRV and Kubios are the closest things to canonical references.

Python (Matplotlib)

Code — ~5 min
  1. 01Install Matplotlib and NumPy with pip install matplotlib numpy.
  2. 02Load the time series into a one-dimensional NumPy array.
  3. 03Lag by one: x = series[:-1] and y = series[1:].
  4. 04Scatter with plt.scatter(x, y, alpha=0.4, s=12).
  5. 05Add the identity line: ax.plot([lo, hi], [lo, hi], '--', alpha=0.4).
  6. 06Compute SD1 / SD2 and overlay an ellipse with matplotlib.patches.Ellipse, rotated 45 degrees, with width=2*SD2 and height=2*SD1.

If you only need the metrics and the visual is secondary, hrv-analysis (PyPI) computes SD1 / SD2 directly and ships a Poincaré helper.

R (RHRV)

Code — ~6 min
  1. 01Install RHRV and ggplot2 with install.packages(c('RHRV', 'ggplot2')).
  2. 02Build the HRV data structure: hrv <- CreateHRVData(); LoadBeatRR(hrv, RR=rr_intervals_seconds); BuildNIHR(hrv).
  3. 03Filter artifacts: hrv <- FilterNIHR(hrv) to remove ectopic and impossible beats.
  4. 04Run the non-linear analysis pipeline: hrv <- CreateNonLinearAnalysis(hrv).
  5. 05Call PoincarePlot(hrv, indexNonLinearAnalysis=1) to compute SD1 / SD2 and draw the plot.
  6. 06Re-render with ggplot2 if you want finer control over color, theme, and annotation.

RHRV is the reference R package for HRV; PoincarePlot() is the canonical implementation, including the SD1 / SD2 ellipse overlay.

JavaScript (D3)

Code — ~8 min
  1. 01Install with npm i d3 or include via CDN.
  2. 02Build pairs = series.slice(0, -1).map((v, i) => [v, series[i + 1]]).
  3. 03Set up x and y scales with d3.scaleLinear() over the same domain so the identity line remains a true 45-degree diagonal.
  4. 04Render circles for each pair with low fill-opacity to handle overplotting.
  5. 05Draw the identity line as an SVG line element from [domain.min, domain.min] to [domain.max, domain.max].
  6. 06Compute SD1 = std(x[i] - y[i]) / sqrt(2) and SD2 = std(x[i] + y[i]) / sqrt(2); render the ellipse rotated 45 degrees centered on (mean, mean).

Observable Plot can do the scatter and the rule-line in two marks, but it has no built-in helper for the rotated SD1 / SD2 ellipse.

MATLAB

Code — ~5 min
  1. 01Place the time series in a column vector rr.
  2. 02Build x = rr(1:end-1); y = rr(2:end).
  3. 03Use scatter(x, y, 12, 'filled', 'MarkerFaceAlpha', 0.4).
  4. 04Hold on; plot the identity line with plot([lo hi], [lo hi], '--').
  5. 05Compute SD1 = std(x - y) / sqrt(2) and SD2 = std(x + y) / sqrt(2).
  6. 06Overlay the ellipse with rectangle('Position', [mu_x-SD2, mu_y-SD1, 2*SD2, 2*SD1], 'Curvature', [1 1]) inside an hgtransform rotated 45 degrees.

MATLAB’s Signal Processing Toolbox doesn’t ship a poincare() function out of the box; the recipe above is the standard manual implementation.

Kubios HRV

GUI — ~3 min
  1. 01Open Kubios HRV and import your raw recording (ECG, .edf, or pre-extracted RR series).
  2. 02Mark the analysis period and use the artifact-correction tool to filter ectopic beats.
  3. 03Switch to the ‘Non-linear’ results tab; Kubios automatically draws the Poincaré plot and reports SD1 / SD2 / SD1/SD2 ratio.
  4. 04Use the ‘Sample report’ export to embed the figure in a PDF that includes the time-domain and frequency-domain summaries alongside.
  5. 05Double-check that the artifact-correction strength is documented in your methods — strong correction can deflate SD1.
  6. 06Save the analysis session so the same recording can be reopened with the same correction settings later.

Kubios is the de-facto clinical HRV tool; the Standard edition is free for academic use and produces journal-ready Poincaré figures with no scripting.

// 16Code examples

Working code in the most common stacks

Three runnable snippets that produce equivalent Poincaré plots in Python, R, and JavaScript. Each computes SD1 and SD2 alongside the visual.

poincare.py
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse

rng = np.random.default_rng(42)
n = 600

# Simulated RR intervals (ms): mean ~880 with realistic short-term variability.
rr = 880 + np.cumsum(rng.normal(0, 6, n))           # slow drift
rr += rng.normal(0, 18, n)                          # short-term variability

x = rr[:-1]
y = rr[1:]

mu_x, mu_y = x.mean(), y.mean()
sd1 = np.std(x - y, ddof=1) / np.sqrt(2)
sd2 = np.std(x + y, ddof=1) / np.sqrt(2)

fig, ax = plt.subplots(figsize=(6.5, 6.5))
ax.scatter(x, y, s=12, alpha=0.4, color="#c94a2e")

lo, hi = rr.min() - 30, rr.max() + 30
ax.plot([lo, hi], [lo, hi], "--", color="#6b6b67", alpha=0.5, lw=1)

ellipse = Ellipse(
    (mu_x, mu_y),
    width=2 * sd2, height=2 * sd1,
    angle=45,
    fill=False, edgecolor="#1a1a18", lw=1.2, linestyle=":",
)
ax.add_patch(ellipse)

ax.set_xlim(lo, hi); ax.set_ylim(lo, hi); ax.set_aspect("equal")
ax.set_xlabel("RR(n) [ms]")
ax.set_ylabel("RR(n+1) [ms]")
ax.set_title(
    f"Healthy HRV: SD1 = {sd1:.0f} ms, SD2 = {sd2:.0f} ms, "
    f"ratio = {sd1 / sd2:.2f}",
    loc="left",
)
ax.spines[["top", "right"]].set_visible(False)
plt.tight_layout()
plt.savefig("poincare.png", dpi=200, bbox_inches="tight")
plt.show()
$ python poincare.py

// 17 — FAQs

Frequently asked questions

What is a Poincaré plot?+

A Poincaré plot (also called a return map or lag-1 plot) is a scatter diagram in which each data point x(n) in a time series is plotted against the next value x(n+1). The simple transformation converts a one-dimensional time series into a two-dimensional picture that reveals the relationship between consecutive measurements.

When should you use a Poincaré plot?+

Use a Poincaré plot when analyzing time-series data for short- and long-term variability, especially in cardiology (RR intervals), nonlinear dynamics, or any domain where short-term predictability of the next sample matters. It excels at exposing periodicity, drift, and abrupt regime changes.

When should you avoid a Poincaré plot?+

Avoid a Poincaré plot when your data is not a time series, when there is no clear lag relationship to interpret, when the series is too short (under ~50 points) for the cloud to develop, or when a simple line chart over time would tell the same story to a less specialized audience.

What are SD1 and SD2?+

SD1 is the standard deviation of the projection of the points onto the line perpendicular to the line of identity (y = x). It quantifies short-term variability — beat-to-beat fluctuation in cardiology. SD2 is the standard deviation of the projection onto the line of identity itself; it quantifies long-term variability.

What is the line of identity?+

The line of identity is the 45-degree diagonal where x(n) equals x(n+1). Points falling exactly on this line indicate consecutive samples that did not change. The width of the cloud perpendicular to this line measures short-term variability.

Is a Poincaré plot suitable for dashboards?+

Yes — Poincaré plots work well in clinical or operational dashboards as long as the panel is large enough to show the cloud, the SD1 / SD2 ellipse is overlaid for quick interpretation, and the time period summarized is clearly stated in the title.

What category of chart is a Poincaré plot?+

Poincaré Plot belongs to the Scientific family of charts. Charts in that family are designed to answer the same kind of question — how a system’s state evolves over time — so they often work as alternatives when one doesn’t quite fit your data.

How do you read a Poincaré plot?+

Start with the line of identity, then look at the cloud’s shape and orientation. A tight cloud along the diagonal means consecutive values are similar; a wider cloud means consecutive values vary more. Elongation along the diagonal is long-term variability (SD2); width perpendicular to it is short-term variability (SD1).

What’s the best library for building Poincaré plots?+

For Python, matplotlib’s scatter() with a manual lag transform is the standard. For R, the RHRV package builds Poincaré plots and computes SD1 / SD2 automatically. For the web, D3 with a simple lag-shift on the data array does the job in a dozen lines.

// 18References

References and further reading

Primary sources, official library documentation, and the international HRV standards cited throughout this guide.