
Chris Parmer
October 27, 2026
Small Multiples - A Complete Guide (with 13 Examples)
TL;DR
- Small multiples repeat the same small chart once per group, with the same axes and encoding, so the eye compares panels instead of untangling lines.
- Small multiples are also known as trellis charts, lattice charts, panel charts and facet plots, and are similar to ridgeline plots and scatter plot matrices.
- Use them when one chart would hold more than about five overlapping series, or when each group needs its own reference band, map position or distribution.
- Share the axes unless the sizes differ so much that the small groups go flat, then label every panel's scale, index each series to a common base, or draw every other group in gray behind each panel.
- In Python, px.line(df, x=..., y=..., facet_col="group", facet_col_wrap=4) makes one in one line of code, and make_subplots handles custom grids such as a map of US states.
What is a small multiple chart?
Small multiples look like this:

Data: BLS Local Area Unemployment Statistics via FRED.
How do you read small multiples? In the example above, notice these things:
- Each panel is the same small area chart, one state's monthly unemployment rate, and the panels sit roughly where the states are on a US map, so Alaska is in the top left and Maine in the top right.
- Every panel shares the same time axis and the same 0 to 32% scale, so a taller line means a higher rate and you can compare any two states by eye without reading their axes.
- Because the panels repeat one design, the eye picks up the shared shape first. Almost every state has a wide hump during the Great Recession and a sharp spike in spring 2020, so a state that breaks the pattern stands out, like South Carolina, whose earlier hump reached the same 11.7% as its pandemic spike.
- Color picks out the panels worth a second look. Nevada, Michigan and Hawaii are orange because their peaks went above 20%, and the number next to each state's name is its latest rate.
- The shared scale has a cost, since states with low rates such as North Dakota and South Dakota draw nearly flat lines whose small changes are hard to see. Switch to independent y axes when the shape within each state matters more than the comparison between them.
It's often used for charts like these:

Highlighting one series against all the others. Tech stocks fell 80% from March 2000 and took until July 2016 to recover, the longest drawdown of 12 US industries since 1990. Data: Kenneth R. French Data Library (CRSP).

Sparklines in a table. Brent crude rose 89.8% in the year to October 20, 2026, more than any other series on a 12-row market dashboard. Data: FRED.

Distributions by group. The 1930s had 532 trading days when US stocks moved 2% or more, and the 1950s had 25. Data: Kenneth R. French Data Library (CRSP).
A short history
Edward Tufte named and popularized small multiples in The Visual Display of Quantitative Information (1983). William Cleveland and colleagues at Bell Labs turned them into the trellis display, a grid of panels conditioned on one or two variables (Becker, Cleveland and Shyu, 1996). Cleveland's best-known example, in Visualizing Data (1993), used barley yields from six Minnesota sites (Immer, Hayes and Powers, 1934):

At Morris all 10 varieties yielded more in 1932 than in 1931, while at the other five sites at most one did. Laid out in panels, the exception is obvious, and Cleveland suggested the two years may have been swapped in the records.
Alternatives to small multiples
Here's the same monthly crude oil production of eight states drawn four ways. Some are better for the question, some worse, some just different:

- One line chart (works too, for a few series). Up to four or five lines it is the most compact choice. With eight states, the five smaller ones overlap below 1,000 thousand barrels a day and the colors become a legend lookup.
- Stacked area chart (different). Shows the total, about 11 million barrels a day for these eight states, but only the bottom band has a flat baseline, so the other states' trends are hard to read.
- Heatmap (better for many series and timing). Scaled to each state's peak, it shows when each state grew in one compact block, and it fits hundreds of rows. Color is less precise than position, so exact values are hard to read. See our heatmap guide.
- Small multiples (better for comparing shapes). Each state's line is readable on its own and comparable with the others on the shared axis. They take more room, and you lose the exact overlap of two series in one panel.
For distributions by group, a ridgeline plot is a compact alternative to faceted histograms (see our ridgeline plot guide), and a scatter plot matrix is small multiples of every pair of variables (see our scatter plot matrix guide).
How to make small multiples
With AI
ChatGPT, Claude Code, GitHub Copilot, Cursor and other AI tools are excellent at writing Plotly code. Name Plotly in the prompt, say what one panel shows, how to lay out and order the panels and whether the axes are shared:
Make plotly small multiples from production.csv (columns: date, state, kbd).- One line chart per state with px.line and facet_col="state", facet_col_wrap=4.- Order the panels by the latest value, largest first (category_orders).- Shared y axis starting at zero, labeled once as "Thousand barrels a day".- Panel titles show only the state name and its latest value, without "state=".- One color for every line; show year tick labels only on the bottom row.- A title that states the main takeaway and a muted subtitle with the source.- Save it as small_multiples.html and as a PNG at 2x scale.
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 small multiples the same way:
Show the weekly flu hospitalization rate for every state as a grid of small area charts, sorted by each state's peak, with the national rate as a dotted gray line in every panel. Add a dropdown to switch between flu, COVID-19 and RSV.
Make small multiples in Python or JavaScript
This is the code AI tools write under the hood. In Python, Plotly Express makes a faceted chart from a long table with one row per point:
import plotly.express as pxdf = px.data.stocks().melt("date", var_name="ticker", value_name="price")fig = px.line(df, x="date", y="price", facet_col="ticker", facet_col_wrap=3)fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))fig.show()
Plotly Express and make_subplots both build small multiples as a grid of ordinary subplots, one pair of axes per panel. For layouts px can't make, this helper places each panel at a grid position (a map of states, as in the first chart on this page) and can draw every other group in gray behind each panel:
import plotly.express as pximport plotly.graph_objects as gofrom plotly.subplots import make_subplotsdef make_small_multiples(df, facet, x, y, cols=4, positions=None, shared_y=True, context=False):"""One line chart per group, at positions={group: (row, col)} counted from 0."""if positions is None:positions = {g: divmod(i, cols) for i, g in enumerate(dict.fromkeys(df[facet]))}rows = max(r for r, c in positions.values()) + 1cols = max(c for r, c in positions.values()) + 1titles = [""] * (rows * cols) # one title per grid cell, blank for empty cellsfor g, (r, c) in positions.items():titles[r * cols + c] = gfig = make_subplots(rows=rows, cols=cols, shared_xaxes="all",shared_yaxes="all" if shared_y else False, subplot_titles=titles)for g, (r, c) in positions.items():if context: # every other group in light gray, as one trace with None between groupsxs, ys = [], []for _, d in df[df[facet] != g].groupby(facet):xs += [*d[x], None]ys += [*d[y], None]fig.add_trace(go.Scatter(x=xs, y=ys, mode="lines", line=dict(color="#d4d3cd", width=1)),row=r + 1, col=c + 1)d = df[df[facet] == g]fig.add_trace(go.Scatter(x=d[x], y=d[y], mode="lines", line=dict(color="#2a78d6", width=2)),row=r + 1, col=c + 1)fig.update_layout(template="simple_white", showlegend=False)return figdf = px.data.gapminder().query("country in ['Canada', 'United States', 'Mexico', 'Brazil', 'Chile']")grid = {"Canada": (0, 1), "United States": (1, 1), "Mexico": (2, 0), "Brazil": (3, 2), "Chile": (4, 1)}fig = make_small_multiples(df, "country", "year", "lifeExp", positions=grid, context=True)fig.show()
In JavaScript, plotly.js puts each trace on its own xaxis/yaxis pair, layout.grid lays the pairs out, and matches shares an axis:
<div id="chart"></div><script src="https://cdn.plot.ly/plotly-4.1.1.min.js"></script><script>// annual average crude oil production, thousand barrels a day (EIA)const years = [2005, 2010, 2015, 2020, 2025];const states = {"Texas": [1076, 1169, 3460, 4851, 5760],"New Mexico": [167, 180, 406, 1033, 2205],"North Dakota": [98, 308, 1177, 1199, 1171],"Alaska": [864, 600, 483, 448, 422],};const names = Object.keys(states);const traces = names.map((name, i) => ({x: years, y: states[name], mode: "lines", line: {color: "#2a78d6"},xaxis: "x" + (i + 1), yaxis: "y" + (i + 1)}));const layout = {grid: {rows: 1, columns: 4, pattern: "independent"},showlegend: false,annotations: names.map((name, i) => ({text: "<b>" + name + "</b>", showarrow: false, x: 0, y: 1.12,xref: "x" + (i + 1) + " domain", yref: "y" + (i + 1) + " domain"}))};for (let i = 2; i <= names.length; i++) layout["yaxis" + i] = {matches: "y"}; // shared y axisPlotly.newPlot("chart", traces, layout);</script>
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 loginplotly app publish
If your data has to stay in your own cloud or data center, publish to Dash Enterprise instead.
Dos and don'ts
Plotly Express gives you a working grid in one line, and most of the work for a published chart is in the axes, the order and the panel labels. Here is the crude oil production of eight states both ways, with the same color.
Don't hide the scales of independent y axes, sort the panels alphabetically, keep the "area=" prefixes or repeat the axis titles under every panel:

Do share and label the y axis, order the panels by size, and put the name and the latest value in each panel title:

Shared, independent or indexed axes
The axis choice decides what the panels compare. Here are four options on the same data.
Shared axes. Panels compare sizes, and it's the right default. Texas dwarfs everything, so the small states look flat.

Independent axes. Every shape is readable, but panels no longer compare by height, so every panel needs its own labeled scale. In Plotly Express, use fig.update_yaxes(matches=None, showticklabels=True).

Indexed to a common base. Each state as a percent of its own peak shares one axis again and compares timing: New Mexico and Wyoming are at their peaks now, Texas is 1% below its peak, North Dakota peaked in 2019 and Alaska in 2005.

Indexed, with the others in gray. Adds every other state behind each panel, so you can see whether a state is typical or an outlier.

Checklist
Do
- Write a title that states the takeaway, and put the panel's key number in each panel title.
- Share the axes by default, and say so when they aren't shared.
- Order the panels by something meaningful: size, peak, change or geography.
- Label each axis once, and show tick labels only on the outer panels.
- Use one color for the main series, and save other colors for a highlight.
Don't
- Hide the scale of independent axes.
- Leave "column=value" in the panel titles.
- Sort panels alphabetically when the ranking matters.
- Add a legend for something the panel titles already say.
- Make panels so small that the lines can't show a shape.
Options to customize
These are the Plotly Express and make_subplots options that matter most for small multiples. The full lists are in the Plotly facet plots documentation and the subplots documentation.
- facet_col, facet_row (px): one column or row of panels per value of a column; use both for a grid of two variables.
- facet_col_wrap (px): wraps a single facet variable into rows of N panels.
- category_orders (px): the panel order, for example {"state": ["Texas", "New Mexico", ...]} sorted by size.
- facet_col_spacing, facet_row_spacing (px): gaps between panels, as a fraction of the figure; lower them for big grids.
- fig.for_each_annotation(...): rewrites the panel titles, for example to drop the "column=" prefix.
- fig.update_yaxes(matches=None): gives every panel its own y range; add showticklabels=True so each scale is labeled.
- make_subplots(rows, cols): the grid for custom layouts; place each trace with row= and col=.
- shared_xaxes, shared_yaxes: True shares within a row or column, "all" across the whole grid.
- subplot_titles: one title per grid cell, in row order, with "" for empty cells.
- horizontal_spacing, vertical_spacing: gaps between make_subplots panels.
- specs: cells that span several columns or rows, or that hold a map, a table or a polar chart.
- row="all", col="all": add the same reference line or shape to every panel with add_hline or add_shape.
- xref="x2 domain": places an annotation relative to one panel, for labels inside the panel.
- layout.grid (plotly.js): lays out axis pairs in rows and columns without Python.
More small multiple examples
Nine more small multiples built from real public data, each with its source, the technique that makes it work and a prompt to rebuild it.
1. Daily electricity demand for seven US grids

Each grid's demand is divided by its own 2025 average, so PJM (96 GW) and ISO New England (13 GW) share one axis and the panels compare shapes. In July the daily peak ran 30% to 49% above each grid's average, the most in New England. Data: EIA Hourly Electric Grid Monitor (Form EIA-930), 2025.
Tip: indexing every series to its own average lets very different sizes share one axis, so you don't need independent scales.
Get the data from the EIA Hourly Electric Grid Monitor (Form EIA-930) six-month balance files for 2025(eia.gov/electricity/gridmonitor) for the balancing authorities CISO, ERCO, ISNE, MISO, NYIS, PJM and SWPPand shape it into a table(columns: iso, month, hour, demand_gw, pct_of_annual_mean; mean hourly demand by month and local hour,and that mean as a percent of the grid's 2025 average).Make plotly small multiples with px.line and facet_col="iso", facet_col_wrap=4.- One line per month for January, April, July and October in four colors.- Shared y axis in percent of the annual average, with a dotted line at 100%.- Order the panels by the July peak and put it in each panel title, without "iso=".- Hour ticks at 12am, 6am, 12pm and 6pm; the legend in the empty eighth cell.- Title with the takeaway; subtitle with each grid's size in GW and the source.
2. Crude oil production by state, on independent axes

With each panel on its own scale, every state's shape is readable, and the latest value and the ratio to 2005 in each title carry the size. New Mexico now pumps 14 times as much crude as in January 2005, while Alaska and California pump less than half. Data: EIA Petroleum Supply Monthly.
Tip: fig.update_yaxes(matches=None, showticklabels=True) frees and labels every y axis, and rangemode="tozero" keeps each one honest from zero.
Get the data from the EIA Petroleum Supply Monthly crude oil production by state(eia.gov/dnav/pet, PET_CRD_CRPDN_ADC_MBBLPD_M)and shape it into a table(columns: date, area, kbd; thousand barrels a day for each state and the federal offshore Gulf of America).Make plotly small multiples with px.line, facet_col="area", facet_col_wrap=4, for the 12 largest areas.- Independent y axes from zero, each with tick labels (matches=None, showticklabels=True).- Panels ordered by the latest month's production; each title gives it and the ratio to January 2005.- Blue for areas above their 2005 level, orange for those below; no legend, explain it in the subtitle.- Say in the subtitle that each panel has its own scale.
3. A cycle plot of US natural gas use

A cycle plot gives each calendar month its own panel with the years from left to right, so the seasons and the trend show up at once. July consumption rose 70% from 2001 to 2025, the most of any month, while January rose 46% and March 23%. Data: EIA Natural Gas Monthly.
Tip: make_subplots(rows=1, cols=12, shared_yaxes=True) with year labels only under the first panel keeps twelve narrow panels readable.
Get the data from the EIA Natural Gas Monthly, US total natural gas consumption (series N9140US2, eia.gov)and shape it into a table(columns: date, year, month, bcf_per_day; monthly consumption in billion cubic feet a day, 2001-2025).Make a plotly cycle plot with make_subplots(rows=1, cols=12, shared_yaxes=True).- One panel per calendar month with that month's values from 2001 to 2025 as a line.- A gray horizontal line at each month's 25-year average.- Panel titles: the month and its change from 2001 to 2025 in percent.- Summer months (June to September) in orange, the rest in blue.- Year labels only under the first panel; y axis titled once.
Another way to see it: the same data as one line chart shows the seasons as a zigzag, and the faster growth of summer consumption is hard to see.

4. Every Treasury yield curve since 2019

Each panel draws every trading day's yield curve in a year, orange when the 10-year yield was below the 3-month yield. The curve was inverted on all 250 trading days of 2023 and 238 of 250 in 2024, and on none so far in 2026. Data: US Treasury, Daily Treasury Par Yield Curve Rates.
Tip: all the days of one color go into one trace with None between curves, so 1,955 curves need 24 traces instead of 1,955.
Get the data from the US Treasury's Daily Treasury Par Yield Curve Rates for 2019 to 2026 (home.treasury.gov)and shape it into a table(columns: date, maturity, years, yield_pct; maturities 3 Mo, 6 Mo, 1, 2, 3, 5, 7, 10, 20 and 30 Yr).Make plotly small multiples with make_subplots(rows=2, cols=4), one panel per year.- Every day's curve as a thin line; days with the 10-year below the 3-month in translucent orange,the rest in translucent blue. Put all the days of one color in one trace with None between days.- The last trading day of each year as a black line with small markers.- Maturities as evenly spaced categories; shared y axis from 0 to 6%.- Panel titles: the year and the number of inverted days out of trading days.
5. Clinical trial starts by phase and sponsor

Phases run down the rows and sponsor classes across the columns, so you can compare along either. Industry started 44% more Phase 1 trials in 2025 than in 2010, but only 3% more Phase 3 trials. Data: ClinicalTrials.gov API v2.
Tip: facet_row and facet_col together make a two-way grid, and moving the row labels to the left side makes it read like a table.
Get the data from the ClinicalTrials.gov API v2 (clinicaltrials.gov/api/v2/studies, countTotal=true):interventional trials by start year 2010-2025, phase (PHASE1, PHASE2, PHASE3) and lead sponsor class(INDUSTRY, OTHER)and shape it into a table (columns: year, phase, sponsor, trials).Make a plotly grid with px.line, facet_row="phase" and facet_col="sponsor", markers on.- Shared y axis from zero; one color per sponsor class.- Print the 2010 value at the start of each line and the 2025 value with its percent change at the end.- Column titles on top, row titles as horizontal labels on the left, both without "phase=" or "sponsor=".- Note in the subtitle that a Phase 1/2 trial counts in both rows.
6. Flu hospitalizations in every state, ranked

Sorting 51 sparklines by their peak turns the grid into a ranking. Rhode Island peaked at 26.1 admissions per 100,000, six times Washington's 4.3, and the five orange states peaked in February, weeks after the national peak in the week ending January 3. Data: CDC National Healthcare Safety Network (NHSN), 2025-26 season.
Tip: the national series as a dotted gray line in every panel gives each state a reference without a second axis.
Get the data from the CDC NHSN Weekly Hospital Respiratory Data by jurisdiction (data.cdc.gov, dataset ua7e-t2fy),column totalconfflunewadmper100k, weeks ending 2025-10-04 to 2026-05-30and shape it into a table (columns: state, week, per_100k; the 50 states, DC and USA).Make plotly small multiples with px.area, facet_col="state", facet_col_wrap=9.- Order the panels by each state's peak (category_orders) and put the peak in each title.- Same y scale everywhere, no tick labels inside the grid.- Add the national series as a dotted gray line to every panel that has a state.- Color states that peaked in February orange, the rest blue.
Another way to see it: a heatmap with the states in the same order shows the timing of each peak more clearly, but the size of each peak is harder to judge from color.

7. Daily highs in eight cities against their normal range

Each city gets its own band of normal daily highs, the 10th to 90th percentile for 1991-2020, so a hot day means hot for that city. By chance about 10% of days fall above the band, but Houston had 79 such days in 2025, more than twice that. Data: NOAA GHCN-Daily.
Tip: two invisible lines with fill="tonexty" draw the band, and the 2025 line goes on top so it stays visible.
Get the data from NOAA GHCN-Daily (ncei.noaa.gov) daily maximum temperature, 1991-2025, for the stationsUSW00094728, USW00094846, USW00012960, USW00023183, USW00003017, USW00024233, USW00012839, USW00014922and shape it into a table(columns: city, doy, date, tmax_2025, p10, p50, p90; percentiles of 1991-2020 highs within 7 days of each date).Make plotly small multiples with make_subplots(rows=2, cols=4, shared axes).- A gray band from p10 to p90 (fill="tonexty"), the 2025 daily high as a thin black line.- Orange markers on days above p90, blue on days below p10.- Order panels by the number of days above p90 and put it in each title.
8. CO2 and income by continent

One scatter panel per continent with the same log axes shows each region's spread without five colors piled on top of each other. Americans emit 14.9 tonnes of CO2 a person and the Swiss 3.7, at about the same income. Data: Our World in Data (Global Carbon Budget, Maddison Project Database, UN), 2022.
Tip: log_x=True, log_y=True in px.scatter applies to every facet, and labels added with xref="x3" need log10 positions on a log axis.
Get the data from Our World in Data's CO2 and Greenhouse Gas Emissions dataset (github.com/owid/co2-data),year 2022, with continents from Our World in Dataand shape it into a table(columns: country, iso3, continent, population, gdp_per_capita, co2_per_capita).Make plotly small multiples with px.scatter and facet_col="continent" (Oceania merged into Asia).- Log x and log y axes, shared; markers sized by population.- Order panels by population-weighted CO2 per person and put it in each title.- Label about 15 well-known countries, nudging labels that overlap.
9. Unemployment maps for four months

A series of maps on one color scale is the classic small multiple, used in the Statistical Atlas of the United States for the 1870 census. Four maps of the state unemployment rate show the spread after the Great Recession and the spike of April 2020, when the median state's rate reached 13.3%. Data: BLS Local Area Unemployment Statistics via FRED.
Tip: px.choropleth takes facet_col and facet_col_wrap like the other Plotly Express functions, and range_color keeps one color scale across every map. See our choropleth map guide for more.
Get the data from FRED (the <ST>UR series for every state and DC, e.g. TXUR)and shape it into a table (columns: state, date, rate).Make plotly small multiple maps with px.choropleth: locationmode="USA-states", scope="usa",facet_col for four months (January 2007, January 2010, April 2020, September 2026), facet_col_wrap=2,one color scale from 0 to the highest rate, and the median state's rate in each panel title.