🎨 Transform Figma mockups into production-ready Dash apps. Join us November 10. Save your spot.

Chris Parmer

Chris Parmer

October 24, 2026

Ridgeline Plots - A Complete Guide

TL;DR

  • A ridgeline plot stacks one smoothed distribution per group on a shared x axis, each overlapping the row above, so a dozen or more distributions fit in one chart.
  • Ridgeline plots are also known as joyplots and ridge plots, and are similar to violin plots and histograms.
  • Use one when the groups have a natural order (hours, months, years) or can be sorted by a statistic, and the question is how the shape shifts from row to row.
  • Use violins or box plots instead for two or three groups or when exact quartiles matter, and a heatmap for hundreds of rows.
  • You can make a ridgeline plot with Plotly from half violins (go.Violin with side="positive") or from filled go.Scatter curves of a density computed in numpy, and any coding agent can build one in a minute.

What is a ridgeline plot?

Ridgeline plots look like this:

Ridgeline of daily high temperatures in Lincoln, Nebraska, 1996-2025, one row per month from January at the top to December, each ridge filled by the month's average high on a blue-to-red scale; January highs range from -5 to 70 °F while July ridges are narrow

Data: NOAA NCEI, GHCN-Daily, Lincoln Municipal Airport, NE (USW00014939), 1996-2025.

How do you read a ridgeline plot? In the example above, notice these things:

  • Each ridge is one month of daily highs in Lincoln, Nebraska, drawn as a density curve, which is a smoothed histogram. The taller the ridge at a temperature, the more days had that high.
  • All the rows share one x axis, so you can follow the peak as it moves right from January to July and back left by December.
  • The width of a ridge shows how much the weather varies in that month. The winter ridges are wide and the summer ridges narrow, so 90% of July highs fall within a 22-degree band while January's middle 90% spans more than twice that.
  • The fill color repeats the month's average high on a blue-to-red scale, which makes the summer months read as one red block in the middle of the chart.
  • Each ridge rises into the row above and covers part of it, and there is no scale for height. Compare the shapes and positions of the ridges, and leave exact day counts to a hover or a table.

It's often used for charts like these:

Ridgeline of hourly day-ahead power prices at the SP15 hub in 2025, one row per hour of the day. Night hours are tall blue ridges around 40 to 50 dollars per MWh; from 9 a.m. to 3 p.m. the ridges are yellow, wide and cross below zero, with 30% to 34% of days negative in each midday hour

Prices by hour of the day. At the SP15 hub in Southern California, 32% of day-ahead power prices from 9 a.m. to 3 p.m. in 2025 were negative, when solar floods the grid, while night prices sat in a tight band around $40-50 per MWh. Data: California ISO, OASIS day-ahead prices, TH_SP15_GEN-APND, 2025.

Ridgeline of patient age in FDA adverse event reports for 12 drugs, sorted by median age: isotretinoin 21, methylphenidate 28, levonorgestrel 30, adalimumab 53, montelukast and tirzepatide 56, pembrolizumab 66, lenalidomide 71, denosumab 72, tamsulosin 73, apixaban 74 and donepezil 79

Groups sorted by their median. Sorted by median patient age in FDA adverse event reports, 12 drugs read as a lifespan, from isotretinoin (21) to donepezil (79), and the shapes show what medians can't, like montelukast's second bump of children. Data: FDA FAERS via openFDA, 1,274,597 reports with an age in years.

Dark ridgeline of daily sea surface temperature between 60°S and 60°N, one row per year from 1982 at the top in blue to 2025 at the bottom in red, drifting right; a dotted line marks the coldest day of 2024 at 20.64 °C

A long record, one row per year. The coldest day of 2024 on the ocean surface between 60°S and 60°N was warmer than the warmest day of 29 of the 42 years from 1982 to 2023. Data: NOAA OISST v2.1, via Climate Reanalyzer (University of Maine).

A short history

The look comes from radio astronomy. In his 1970 Cornell PhD thesis, Harold D. Craft Jr. plotted successive pulses of CP 1919, the first known pulsar (Hewish et al., 1968, Nature), as stacked lines, and Peter Saville put the plot on the cover of Joy Division's Unknown Pleasures in 1979 (Christiansen, Scientific American, 2015). Here it is redrawn with Plotly from approximate values read off Craft's figure:

80 successive radio pulses of pulsar CP 1919 redrawn in Plotly as white lines on black, each line hiding the ones behind it, the image on the cover of Unknown Pleasures

Jenny Bryan named the chart a "joyplot" in April 2017, after the album, and Claus Wilke's R package ggjoy made it popular. Wilke renamed it the ridgeline plot that September because the band's name comes from Nazi concentration camps (Wilke, 2017).

Alternatives to a ridgeline plot

Here's the same nine years of bank profitability drawn six ways. Some are better for the question, some worse, some just different:

The return on assets of every FDIC-insured bank from 2005 to 2013 drawn six ways: a ridgeline, violins, box plots, a heatmap of the share of banks per 0.25-point bin, nine overlaid density curves, and a line of the share of banks losing money, which peaks at 31% in 2009
  • Violin plot (works too, for a few groups). A violin mirrors each shape and gives every row its own space, which reads well for up to about eight groups but gets thin past that. See our violin plot guide.
  • Box plot (worse when the shape matters, better for exact quartiles). Box plots show the median, quartiles and outliers precisely, but hide two peaks or a swelling tail. See our box plot guide.
  • Heatmap (better for hundreds of rows). Binned shares as color scale to hundreds of rows in the same space, but color is harder to read than height. See our heatmap guide.
  • Overlaid densities or histograms (worse past three groups). On one baseline you can compare two or three shapes directly, but nine curves turn into a tangle. See our histogram guide.
  • Line of one statistic (better when one number is the story). The share of banks losing money, 8% in 2006 and 31% in 2009, reads exactly from a line, but the rest of each distribution disappears.

How to make a ridgeline plot

With AI

ChatGPT, Claude Code, GitHub Copilot, Cursor and other AI tools are excellent at writing Plotly code. Name Plotly in the prompt, and spell out the rows, the order, the overlap, the bandwidth and the checks you want:

Make a plotly ridgeline plot from readings.csv (columns: sensor_id, site, timestamp, value).
- One row per site, sorted by median value (lowest at the top).
- Compute a Gaussian kernel density per row in numpy on a 300-point grid
with ONE shared bandwidth; draw each ridge as a go.Scatter with fill="toself",
lifted to its row's baseline. Overlap = 1.8 rows; heights on one shared scale.
- Draw the top row first so each lower ridge sits in front; white 1.2 px outlines.
- One color (#2a78d6); print each row's median and sample size at the right end.
- Cut the grid at the data's min and max; no curve beyond the data.
- A title that states the takeaway, source in a subtitle.
- Check: print each row's sample size and warn if any row has fewer than 30 values.
- Save it as ridgeline.html and as a PNG at 2x scale.

Keep the sample-size check in every ridgeline prompt, because a smooth curve on 12 values looks as confident as one on 12,000.

With Plotly Studio

Plotly Studio is a desktop app where you point an AI at a dataset and it builds charts and a Dash app around your questions. Ask for a ridgeline plot the same way:

Make a ridgeline plot of daily electricity prices with one row per hour of the day, midnight at the top. Use an overlap of about 2 rows, color each ridge by its median price and draw a line at zero. Add a dropdown to switch between years.

Make a ridgeline plot in Python or JavaScript

This is the code AI tools write under the hood. In Python, this helper computes a density per row in numpy and draws each one as a filled go.Scatter, lifted to its row and drawn back to front. It takes a shared or per-row bandwidth, weights for pre-counted data and an optional fill by each row's mean or median, and the figure stays small however many values go in:

import numpy as np
import pandas as pd
import plotly.colors as pc
import plotly.graph_objects as go
def kde(values, grid, bandwidth=None, weights=None):
v = np.asarray(values, float)
w = np.ones_like(v) if weights is None else np.asarray(weights, float)
w = w / w.sum()
if bandwidth is None: # Silverman's rule, per row
sd = np.sqrt(np.sum(w * (v - np.sum(w * v)) ** 2))
bandwidth = 0.9 * sd * (1 / np.sum(w ** 2)) ** -0.2
z = (grid[:, None] - v[None, :]) / bandwidth
return (np.exp(-0.5 * z ** 2) * w).sum(axis=1) / (bandwidth * np.sqrt(2 * np.pi))
def make_ridgeline(df, x, row, order=None, weights=None, overlap=1.5, bandwidth=None,
color="#2a78d6", fill_by=None, colorscale="RdBu_r", points=200):
order = list(order if order is not None else df[row].drop_duplicates()) # top to bottom
grid = np.linspace(df[x].min(), df[x].max(), points)
curves, stats = {}, {}
for r in order:
d = df[df[row] == r]
w = d[weights] if weights else None
curves[r] = kde(d[x], grid, bandwidth, w)
stats[r] = np.average(d[x], weights=w) if fill_by == "mean" else d[x].median()
top = max(c.max() for c in curves.values())
lo, hi = min(stats.values()), max(stats.values())
fig, n = go.Figure(), len(order)
for i, r in enumerate(order): # the top row is drawn first, so lower ridges sit in front
base = n - 1 - i
y = base + curves[r] / top * overlap
fill = pc.sample_colorscale(colorscale, (stats[r] - lo) / (hi - lo or 1))[0] if fill_by else color
fig.add_trace(go.Scatter(x=np.r_[grid, grid[::-1]], y=np.r_[y, np.full(points, base)],
fill="toself", fillcolor=fill, line=dict(color="white", width=1.2),
mode="lines", name=str(r), hoveron="fills", showlegend=False))
fig.update_layout(template="simple_white", yaxis=dict(
tickvals=list(range(n)), ticktext=[str(r) for r in order][::-1],
range=[-0.2, n - 1 + overlap + 0.1], showline=False, ticks=""))
return fig
weather = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/2016-weather-data-seattle.csv")
weather["month"] = pd.to_datetime(weather.Date).dt.strftime("%b")
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
fig = make_ridgeline(weather, x="Max_TemperatureC", row="month", order=months, fill_by="mean", overlap=1.8)
fig.show()

In JavaScript, plotly.js builds the same kind of chart from half violins: one violin trace per row with side: "positive" and a width above 1, so each ridge overlaps the row above. The browser computes the densities:

<div id="chart"></div>
<script src="https://cdn.plot.ly/plotly-4.1.1.min.js"></script>
<script>
fetch("https://raw.githubusercontent.com/plotly/datasets/master/violin_data.csv")
.then((r) => r.text())
.then((csv) => {
const [head, ...lines] = csv.trim().split("\n").map((l) => l.split(","));
const bill = head.indexOf("total_bill"), day = head.indexOf("day");
const days = ["Thur", "Fri", "Sat", "Sun"]; // top row first, so lower ridges sit in front
Plotly.newPlot("chart", days.map((d) => ({
type: "violin", orientation: "h", side: "positive", width: 2,
x: lines.filter((l) => l[day] === d).map((l) => +l[bill]), y0: d, name: d,
points: false, spanmode: "hard", line: {color: "white"}, fillcolor: "#2a78d6"
})), {showlegend: false, violinmode: "overlay", violingap: 0,
yaxis: {categoryorder: "array", categoryarray: [...days].reverse()}});
});
</script>

Half violins are the shortest way, and Python's go.Violin takes the same settings. For big data, compute the density in Python instead, because violins send every raw value to the browser (see the FAQ for timings).

Share it

Every Plotly chart has a Share chart button (the cloud icon in its toolbar) that uploads it to Plotly Cloud and gives you a link. Only you can see it until you change its visibility.

For a live app, put the chart in a Dash app and publish it with the Plotly Cloud CLI. Coding agents can run these commands for you:

pip install "dash[cloud]"
plotly user login
plotly app publish

If your data has to stay in your own cloud or data center, publish to Dash Enterprise instead.

Dos and don'ts

Most of the work in a ridgeline is ordering the rows, setting the overlap and deciding which number to print. Here is the same emergency department data for the 15 states with the most hospitals both ways, in the same blue.

Don't leave the rows in alphabetical order, set the overlap so high that tall ridges hide the rows behind them, clip the top ridge with the axis range, or leave every number off the chart:

The median minutes patients spend in the emergency department, one value per hospital, for 15 states as a blue ridgeline in alphabetical order with an overlap of 4 rows, so tall ridges such as Iowa and Kansas hide the rows behind them, California's ridge is cut off at the top, and no medians are shown

Do sort the rows by the median, keep the overlap near 1.5 rows with room above the top ridge, and print the number that matters on each row. Here the medians run from 117 minutes in Kansas to 204 in New York:

The same data as a ridgeline in the same blue, sorted from Kansas at 117 minutes at the top to New York at 204 at the bottom, with each state's median drawn as a white line and printed at the right

Use color for one thing

One color always works. When you want color to say something, pick one thing for it to say. Here are four options on the same MLB fastball speeds for six seasons.

One color. The safe default, because the rows are already labeled.

MLB four-seam fastball speeds in the first week of June for 2008, 2012, 2016, 2021, 2024 and 2026 as a ridgeline in one blue, drifting right from top to bottom

Color by a value. Each ridge is filled by the season's average speed, which went from 92.0 mph in 2008 to 95.0 mph in 2026.

The same fastball ridgeline with each season filled by its average speed on a blue to amber to red scale, from blue in 2008 to red in 2026, with a colorbar

Highlight the rows you compare. Gray everything except 2008 in blue and 2026 in orange, so the comparison reads first and the gray rows show the steady drift between them.

The same fastball ridgeline in light gray except 2008 in blue at the top and 2026 in orange at the bottom

Outlines on black. White lines over a dark fill give the Unknown Pleasures look, which works for a cover image more than for reading values.

The same fastball ridgeline drawn as white outlines on a black background, each ridge hiding the ones behind it

Checklist

Do

  • Write a title that states the takeaway with a number, and put the source in a muted subtitle.
  • Order rows by time or by the statistic that matters (a median, or a tail share such as flights over an hour late).
  • Set the overlap, the height of the tallest ridge in rows, to about 1.5 to 2, and up to 3 for many rows of similar height.
  • Draw the top row first with white outlines, so each lower ridge sits in front.
  • Use one shared bandwidth when rows are compared, and print the sample size when rows differ in size.
  • Use a log axis for prices, flows and salaries, with the density computed on the log values.

Don't

  • Sort rows alphabetically, or set the overlap so high that tall ridges hide whole rows.
  • Use a rainbow of colors that mean nothing for rows that are already labeled.
  • Draw tails past the data: cut the curve at the data range (spanmode="hard" for violins).
  • Smooth tiny samples without showing the raw points.
  • Clip the top ridge: leave one overlap of room above the top row.

Options to customize

These are the options that matter most for building a ridgeline from go.Violin or from go.Scatter. The full references are in the Plotly violin plot and filled area documentation.

  1. go.Violin(orientation="h", side="positive"): horizontal half violins, one per y0, for the violin method.
  2. width: the height of each violin in rows; above 1 makes the rows overlap (2 to 3 is typical).
  3. bandwidth: the kernel bandwidth; set the same value on every trace for a shared bandwidth.
  4. spanmode="hard": stops the curve at the data's range, for bounded values such as ages.
  5. violinmode="overlay", violingap=0 (layout): let the rows overlap; always use them with violins.
  6. scalemode="count": makes each violin's size follow its sample size, when row sizes should show.
  7. go.Scatter(fill="toself"): fills a precomputed curve down to its baseline, for the numpy method.
  8. fillcolor, line.color: fill and outline, for one color, a highlight or a scale.
  9. fillgradient: a gradient fill along x or y, for color that follows the x value.
  10. line.simplify=False: keeps every outline point, for smooth curves in static images.
  11. hoveron="fills": hover over the whole ridge to show a row's median or count.
  12. Trace order: later traces draw on top, so add the top row first.
  13. yaxis.tickvals, ticktext, range: row labels and headroom for the numpy method; leave one overlap above the top row.
  14. xaxis.type="log": a log x axis, for prices, flows and salaries.

More ridgeline plot examples

Ten more ridgeline plots built from real public data, each with its source, the technique that makes it work and a prompt to rebuild it.

1. Henry Hub natural gas, 18 years of daily prices

Ridgeline of daily Henry Hub natural gas spot prices by year from 2008 to 2025 on a log scale. 2008 and 2022 are orange with medians above $5; 2020 sits lowest around $1.9; a dot marks each year's highest price, with $23.86 on February 17, 2021 annotated

The median Henry Hub price was $8.38 per million Btu in 2008 and $1.91 in 2020, and only 2008 and 2022 had medians above $5. The dots show how far single days jump: on February 17, 2021, during the Texas freeze, gas reached $23.86. Data: U.S. EIA Henry Hub spot price via FRED (DHHNGSP), 2008-2025.

Tip: compute the density on log10(price) and draw it on a log axis, so $2 to $4 looks as big a move as $4 to $8.

Get the data from the U.S. Energy Information Administration's Henry Hub natural gas spot price series
(daily, $ per million Btu; FRED series DHHNGSP) for 2008-2025 and shape it into a table
(columns: date, year, price).
Build a plotly ridgeline plot with one row per year, 2008 at the top.
- Kernel density on log10(price) (bandwidth 0.03), drawn on a log x axis from $1 to $30.
- Overlap 1.7; years with a median above $5 in orange, others in blue.
- A small black dot at each year's highest price; annotate the overall maximum with its date.
- Title with the highest and lowest yearly medians; cite EIA via FRED.

2. Every new mortgage in Colorado, 2018-2025

Ridgeline of interest rates on conventional 30-year fixed home-purchase loans in Colorado from 2018 to 2025, drawn with a narrow bandwidth so each year is a comb of spikes at every eighth of a point. The median moved from 3.000% in 2021 to 6.600% in 2023

The median rate on a new 30-year fixed loan in Colorado went from 3.00% in 2021 to 6.60% in 2023, across 513,050 loans. A narrow bandwidth turns each year into a comb, because at least 76% of loans sit exactly on an eighth of a point. Data: HMDA, FFIEC / CFPB HMDA Data Browser, 2018-2025.

Tip: count loans per exact rate and pass the counts as KDE weights, so half a million loans become a few hundred rows.

Get the data from the FFIEC / CFPB HMDA Data Browser (Home Mortgage Disclosure Act loan records) for Colorado,
2018-2025: originated, first-lien, conventional, 30-year fixed home-purchase loans, and shape it into a table
(columns: year, rate in percent, loans = number of loans at that exact rate).
Build a plotly ridgeline plot with one row per year, 2018 at the top.
- Weighted Gaussian kernel density (weights = loans), shared bandwidth 0.03 points, x from 2% to 9.3%.
- Overlap 2.2 rows, one blue fill, white outlines.
- Print "<median>% median · <n> loans" at the right end of each row.
- Title with the 2021 and 2023 medians and the share of loans on an eighth of a point; cite HMDA.

3. Every US bank's profitability since 2000

Ridgeline of return on assets for every FDIC-insured bank from 2000 to 2025, with the part of each ridge below zero shaded red. The red tails swell in 2008 to 2010, and the share of banks with a loss printed on the right peaks at 31% in 2009

In 2009, 31% of FDIC-insured banks lost money, up from 8% in 2006, while the median bank still earned 0.46% on its assets. The middle barely moves; the crisis is the left tail swelling in 2008-2010. Data: FDIC BankFind Suite (Call Reports), December 31 of each year.

Tip: draw each ridge twice from the same curve, in blue and then clipped to x < 0 in red, so losses read as a shaded tail.

Get the data from the FDIC BankFind Suite API (financials endpoint, Call Report data) for every FDIC-insured
bank at each December 31 from 2000 to 2025, and shape it into a table
(columns: year, roa = full-year return on assets in percent).
Build a plotly ridgeline plot with one row per year, 2000 at the top, x cut at -3% to +3%.
- Kernel density per year (bandwidth 0.08), overlap 2.2, blue fill.
- Redraw the part of each ridge left of zero in red on top.
- Print the share of banks with roa < 0 at the right of each row, in red when above 15%.
- Title with the worst year's share and the median that year; cite FDIC.

Another way to see it: a line of the share of banks losing money gives exact values, but drops the shape of everything else.

Line chart of the share of FDIC-insured banks with a negative return on assets, 2000 to 2025, peaking at 31% in 2009 and falling to 3% in 2021

4. How long emergency room visits take, hospital by hospital

Ridgeline of the median emergency department time at 3,797 hospitals in 38 states, sorted from North Dakota (105 minutes) to Maryland (241 minutes), with the middle half of each state's hospitals shaded darker and the median as a white line

The typical emergency visit takes 105 minutes at a North Dakota hospital and 241 at a Maryland one. The dark band, the middle half of each state's hospitals, spans about 30 minutes in North Dakota and 69 in New York. Data: CMS Care Compare, measure OP_18b, October 2024 to September 2025.

Tip: redraw the 25th-75th percentile part of each ridge in a darker shade, which builds a box plot into every ridge.

Get the data from CMS Care Compare, Timely and Effective Care - Hospital (data.cms.gov dataset yv7e-xc69),
measure OP_18b (median minutes patients spend in the emergency department before leaving), and shape it
into a table (columns: facility_id, state, minutes, sample).
Build a plotly ridgeline plot with one row per state, keeping states with at least 40 hospitals.
- Kernel density per state (bandwidth 12 minutes), x from 50 to 400; overlap 1.5.
- Sort by state median, fastest at the top; light blue ridges.
- Redraw the part between each state's 25th and 75th percentile in dark blue, and a white line at the median.
- Title with the fastest and slowest state medians; cite CMS.

5. The Colorado River before and after Glen Canyon Dam

Ridgeline of daily flow of the Colorado River at Lees Ferry, Arizona, by decade from 1922 on a log axis. Gray ridges before 1963 are wide with spring floods above 50,000 cubic feet per second; blue ridges after the dam are narrow around 10,000

Before Glen Canyon Dam, the wettest 5% of days at Lees Ferry ran above 62,200 cubic feet per second; since 1964, above 24,000. Wide gray ridges of winter lows and spring floods give way to narrow, regulated releases. Data: U.S. Geological Survey NWIS, site 09380000, 1922-2025.

Tip: split the decade where the regime changes (1960-62 and 1963-69) and leave a labeled half-row gap.

Get the data from the U.S. Geological Survey's National Water Information System, daily discharge
(parameter 00060, cubic feet per second) at site 09380000, Colorado River at Lees Ferry, Arizona,
1922-2025, and shape it into a table (columns: date, cfs).
Build a plotly ridgeline plot with one row per decade, splitting the 1960s into 1960-62 and 1963-69.
- Kernel density on log10(cfs) (bandwidth 0.05), log x axis from 700 to 150,000; overlap 1.6.
- Gray before 1963, blue after; a half-row gap and a label for 1963, when Glen Canyon Dam started storing water.
- Title with the 95th percentile before and after; cite USGS.

6. When New Yorkers call 311

Ridgeline of 2025 NYC 311 requests by hour of the day for 10 complaint types, on an axis from 6 a.m. to 6 a.m. Daytime types like missed collection and street condition are orange; residential, street and commercial noise are purple and peak around 10 p.m.; a black line shows weekends

59% of commercial noise complaints to NYC 311 in 2025 arrived between 10 p.m. and 4 a.m., against 2% of missed-collection reports. The types range from 42,977 to 577,253 requests, so each row is scaled to its own peak. Data: NYC Open Data 311 Service Requests, 2,004,957 requests in 2025.

Tip: start a time-of-day axis where activity is lowest (6 a.m. here), so the night isn't split at midnight.

Get the data from NYC Open Data's 311 Service Requests (dataset erm2-nwe9), requests created in 2025 for 10
complaint types, counted on the server by hour created and weekday/weekend, and shape it into a table
(columns: complaint_type, hour, weekend, requests).
Build a plotly ridgeline plot with one row per complaint type and an x axis from 6 a.m. to 6 a.m.
- Each row: the share of the type's requests per hour, smoothed with a circular Gaussian (sigma 0.6 hours),
scaled to the row's own peak; weekdays filled, weekends as a black line on the same row.
- Purple for types with more than 30% of requests between 10 p.m. and 4 a.m., orange otherwise;
print that share at the right. Sort rows by peak hour. Cite NYC Open Data.

7. How US counties voted, 2008-2024

Ridgeline of the Republican share of the two-party presidential vote in about 3,100 US counties for 2008, 2012, 2016, 2020 and 2024, each ridge filled with a horizontal blue-gray-red gradient; the share of counties over 70% Republican rises from 20% to 53%

Counties that voted more than 70% Republican went from 20% of US counties in 2008 to 53% in 2024, with the biggest jump in 2016. Each county counts once, so the mass on the right is many small counties. Data: county results compiled by Tony McGovern (tonmcg/US_County_Level_Election_Results_08-24), Alaska left out.

Tip: fillgradient=dict(type="horizontal", start=0.2, stop=0.8, colorscale=...) colors each ridge by its own x position.

Get the data from Tony McGovern's county-level US presidential election results
(github.com/tonmcg/US_County_Level_Election_Results_08-24) for 2008-2024, dropping Alaska, and shape it into a
table (columns: year, fips, gop_share = Republican / (Republican + Democratic votes), total_votes).
Build a plotly ridgeline plot with one row per election, 2008 at the top, x from 0 to 100%.
- Kernel density per year (bandwidth 0.015), overlap 1.7, white outlines.
- Fill every ridge with fillgradient type "horizontal", start 0.2, stop 0.8, blue #2a78d6 to gray to red #e34948.
- Dotted line at 50%; print the share of counties over 70% Republican at the right. Cite the source.

Another way to see it: a scatter of each county's 2008 share against 2024, sized by votes, shows that 90% of counties moved right, and that the big blue metro counties sit below the diagonal.

Scatter of 3,103 US counties, Republican two-party share in 2008 on x and 2024 on y, dot area by 2024 votes; 90% of counties sit above the diagonal in red, and large blue metro counties sit below it

8. MLB fastballs got faster

Ridgeline of MLB four-seam fastball speeds in the first week of June for each season from 2008 to 2026, gray except 2008 in blue and 2026 in orange, drifting right past a dotted line at 95 mph; the share at 95 mph or faster rises from 18% to 51%

51% of four-seam fastballs in the first week of June 2026 reached 95 mph, up from 18% in 2008, and the average rose from 92.0 to 95.0 mph. The gray rows show a steady drift, not one jump. Data: MLB Statcast and PITCHf/x via Baseball Savant, 164,264 pitches.

Tip: gray every row except the two you compare, and print the share at 95 mph at the end of each row.

Get the data from Baseball Savant's Statcast search export (MLB pitch-by-pitch data): four-seam fastballs
(pitch type FF) in regular-season games June 1-7 of each season 2008-2026, counted per 0.1 mph, and shape it
into a table (columns: year, mph, pitches).
Build a plotly ridgeline plot with one row per season, 2008 at the top, x from 84 to 103 mph.
- Weighted kernel density (weights = pitches, bandwidth 0.45 mph), overlap 2.2.
- Light gray ridges except the first season (blue) and the last (orange).
- Dotted line at 95 mph; print each season's share at 95 mph or faster at the right. Cite Baseball Savant.

9. How late US airlines landed

Ridgeline of arrival delays for 612,811 US domestic flights in July 2025 by airline, sorted by the share more than an hour late, from Hawaiian at 4% to JetBlue at 17%, with lines at zero and at 60 minutes

17% of JetBlue flights in July 2025 arrived more than an hour late, against 4% for Hawaiian. Every airline's median is within a few minutes of on time, so the rows are sorted by the tail, where they differ. Data: U.S. Bureau of Transportation Statistics, On-Time Performance, July 2025.

Tip: sort by the statistic people care about, here the share more than an hour late, instead of a median every row shares.

Get the data from the U.S. Bureau of Transportation Statistics' Reporting Carrier On-Time Performance file
for July 2025 (every domestic flight by reporting airline), dropping cancelled and diverted flights, and
shape it into a table (columns: carrier, airline, delay_min = arrival delay in minutes, flights).
Build a plotly ridgeline plot with one row per airline, x cut at -45 to 180 minutes.
- Weighted kernel density (weights = flights, bandwidth 3 minutes), overlap 1.8, purple fill.
- Sort rows by the share of flights more than 60 minutes late, lowest at the top; print it at the right.
- Lines at 0 and 60 minutes; say in the subtitle how many flights are beyond 3 hours. Cite BTS.

Another way to see it: 100% stacked bars of delay buckets give exact shares per airline, at the cost of the shape inside each bucket.

Stacked horizontal bars of the share of each airline's July 2025 flights that arrived early or on time, 1-15 minutes late, 16-60 minutes, 1-3 hours and over 3 hours; Delta has the most early or on-time flights at 62% and Hawaiian the fewest at 48%

10. What US developers earn, by role

Ridgeline of annual pay of 2,035 employed US respondents to the Stack Overflow Developer Survey 2026 by role on a log axis, sorted by median from desktop developers to engineering managers at $212,500, with a rug of individual respondents under each ridge and the sample size in each label

US engineering managers reported a median $212,500 in the 2026 Stack Overflow survey, and full-stack developers $140,000. Some rows are small (53 DevOps engineers against 895 full-stack developers), so ticks under each ridge show every respondent. Data: Stack Overflow Developer Survey 2026, employed US respondents.

Tip: for small rows, add a rug of raw values (symbol="line-ns-open" markers) and put n in the label.

Get the data from the Stack Overflow Annual Developer Survey 2026 public results file, employed respondents in
the United States with ConvertedCompYearly between $10,000 and $1,000,000, the 10 most common roles, and shape it
into a table (columns: devtype, salary_usd).
Build a plotly ridgeline plot with one row per role on a log x axis ($10k to $1M).
- Kernel density on log10(salary) with a per-row Silverman bandwidth; overlap 1.4; green fill.
- Sort rows by median, lowest at the top; label rows "<role> (n=<count>)".
- Under each ridge, a rug of every respondent as thin vertical tick markers. Cite Stack Overflow.

FAQ

bluesly
x
instagram
youtube
medium
fcaebook

Product

© 2026
Plotly. All rights reserved.
Cookie Preferences
aicpa/soc2