
Chris Parmer
October 24, 2026
Ternary Plots - A Complete Guide
TL;DR
- A ternary plot draws items that split into three parts adding up to 100% as points in a triangle. Each corner is 100% of one part, so a point's position shows the whole split at once.
- Ternary plots are also known as ternary diagrams, ternary graphs, triangle plots and Gibbs triangles, and are similar to 100% stacked bar charts and scatter plots.
- Use one for many items with a three-way split (electricity mixes, vote shares, soil, diets, budgets) or for one item's split changing over time, drawn as a trail.
- Use 100% stacked bars instead when you have about a dozen items and readers need exact values, and a line chart when only one of the shares matters.
- Any coding agent can make one with Plotly in a minute. In Python it's px.scatter_ternary(df, a=..., b=..., c=...).
What is a ternary plot?
Ternary plots look like this:

Data: Ember, Yearly Electricity Data, 2024.
How do you read a ternary plot? In the example above, notice these things:
- Each bubble is one country, placed by how its electricity splits between fossil fuels, nuclear and renewables. Its area is how much electricity it generated, which is why China is by far the biggest bubble.
- Each corner is 100% of one source, so the closer a bubble sits to a corner, the more of its power comes from that source. France sits high up toward the nuclear corner, while Brazil and Norway sit near the renewables corner at the bottom right.
- The bottom edge means no nuclear at all. Most countries generate no nuclear power, so they line up along that edge, from Saudi Arabia near the fossil corner to Norway at the renewables corner, with Germany at 59% renewables in between.
- The dotted lines mark 50%, which splits the triangle into regions where one source makes most of a country's power. Only France and Slovakia are above the nuclear line, while the United States and China are in the fossil region.
- To read exact values, follow the gridline through a point to the edge that carries its tick labels instead of dropping straight down to the nearest edge, because ternary gridlines meet the edges at 60 degrees.
It's often used for charts like these:

A classification drawn as regions. Soil scientists name soils by their share of sand, silt and clay, and weighted by area, three loams cover 69% of the mapped soil in the United States. Data: USDA NRCS SSURGO, via Soil Data Access (398,308 soil horizons, 1.45 billion acres).

Sediment classified by grain size. Geologists sort seafloor and lake sediments by their share of sand, silt and clay, usually with Shepard's 1954 classification, and half of the 21,463 samples in the USGS East Coast database are sand. Data: USGS East Coast Sediment Texture Database (Poppe and others, 2014).

A split changing over time. The UK's trail shows fossil fuels falling from 75% to 36% of its electricity between 2000 and 2025, while Japan's jumps toward the fossil corner after its reactors shut down in 2012. Data: Ember, Yearly Electricity Data, 2000-2025.
Reading exact values takes one more step. Follow the gridline through a point to the edge that carries the matching tick labels, as in this reading of the world's electricity in 2024 (59% fossil, 9% nuclear and 32% renewables):

A short history
J. Willard Gibbs described a mixture of three substances as a point in an equilateral triangle in "On the Equilibrium of Heterogeneous Substances" (1876), which is why chemists still call it the Gibbs triangle and use it for three-component phase diagrams. In 1926 the mathematician Bruno de Finetti used the same triangle for genotype frequencies in a paper in Metron, and showed that every population in Hardy-Weinberg equilibrium falls on one curve:

Geologists later classified igneous rocks with Albert Streckeisen's QAP triangle (Streckeisen, 1976), soil scientists adopted the texture triangle shown above, and John Aitchison's work on compositional data (Aitchison, 1982) explained why shares that must add up to 100% need their own statistics.
Alternatives to a ternary plot
Here's the same electricity data for the 12 largest generators drawn six ways. Some are better for the question, some worse, some just different:

- 100% stacked bar chart (better for a dozen items and exact values). One bar per country, sorted by fossil share, makes it easy to read that Saudi Arabia gets 98% of its electricity from fossil fuels and France 5%. Past about 20 items the bars turn into a wall, and showing change over time needs one chart per item.
- Marimekko chart (better when size matters too). The same stacked bars with widths set by total generation show that China's 10,087 TWh dwarfs everyone else, which shares alone hide. See our Marimekko chart guide.
- Scatter plot of two shares (works too). Fossil against renewables uses ordinary x and y axes, and nuclear is the gap below the diagonal. It works when one of the three parts matters less than the others.
- Small-multiple pie charts (worse). One pie per country shows each split, but people compare angles across separate pies poorly, and 12 pies take far more space than 12 points.
- Dot plot (different). Three dots per country on one shared percent scale give exact values for every part, at the cost of the clusters a ternary plot shows.
The ternary plot is the only one of these that still works with 154 countries, or with one country's split over 25 years.
How to make a ternary plot
With AI
ChatGPT, Claude Code, GitHub Copilot, Cursor and other AI tools are excellent at writing Plotly code. Name Plotly in the prompt, say which part goes in which corner and what size and color mean, and list the checks you want:
Make a plotly ternary plot from plant_mix.csv (columns: plant, region, coal_mwh, gas_mwh, clean_mwh).- Normalize each row to percent; assert every row has a positive total and print rows that don't.- Clean power at the top (a), coal bottom left (b), gas bottom right (c), sum=100.- Axis titles in plain words, ticksuffix="%", light gray gridlines, white background.- Bubble area = total MWh (sizemode="area"), one color per region. Label the 10 largest plants.- Shade the region where coal is at least 50% with fill="toself" in light gray.- If all points sit in one part of the triangle, set the axis minimums to zoom and say so in the subtitle.- Hover shows all three shares. A title that states the main takeaway.- Save it as ternary.html and as a PNG at 2x scale.
The normalization check is worth keeping in every ternary prompt, because Plotly places any row in the triangle, even one with missing parts.
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 ternary plot the same way:
Show a ternary plot of each country's electricity mix with nuclear at the top, fossil at the bottom left and renewables at the bottom right, in percent. Size the bubbles by total generation, color them by continent and label the 12 largest countries. Add a year slider.
Make a ternary plot in Python or JavaScript
This is the code AI tools write under the hood. In Python, Plotly Express takes one row per item and a column for each part, and divides each row by its total, so raw amounts work:
import plotly.express as pxdf = px.data.election() # Montreal 2013 mayoral election by districtfig = px.scatter_ternary(df, a="Joly", b="Coderre", c="Bergeron",color="winner", size="total", hover_name="district")fig.update_ternaries(sum=100, aaxis_ticksuffix="%", baxis_ticksuffix="%", caxis_ticksuffix="%")fig.show()
In JavaScript, plotly.js takes a scatterternary trace with the three parts in a, b and c. These are 2024 TWh from Ember:
<div id="chart"></div><script src="https://cdn.plot.ly/plotly-4.1.1.min.js"></script><script>Plotly.newPlot("chart", [{type: "scatterternary",mode: "markers+text",text: ["France", "Germany", "Brazil"],textposition: "top center",a: [380, 0, 16], // nuclearb: [29, 205, 79], // fossilc: [153, 291, 651], // renewablesmarker: {size: 14}}], {ternary: {sum: 100,aaxis: {title: {text: "Nuclear"}, ticksuffix: "%"},baxis: {title: {text: "Fossil"}, ticksuffix: "%"},caxis: {title: {text: "Renewables"}, ticksuffix: "%"}}});</script>
To draw a region, such as a soil texture class or "more than half fossil", add another trace with the region's corners in a, b and c, mode: "lines" and fill: "toself". For a trail, sort by time and use mode: "lines+markers".
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
Most of the work on a published ternary plot is in the axes and the labels. Here are the 30 largest electricity generators of 2024 both ways, with the same bubbles and colors.
Don't leave column names such as nuclear_twh as axis titles, show the shares as fractions from 0 to 1, or force a label onto every point until they overlap:

Do show shares in percent with plain axis titles, and label only the countries readers will look for, leaving the rest to the hover:

Use size and color for one other thing
Position already shows the three shares, so size and color are free to say something else. Here are four options on the same data.
Plain points. Positions only, which is enough when every item matters equally.

Size by amount. Bubble area adds the total generation that shares hide, so China (10,087 TWh) and Norway no longer look alike.

Color by group. One hue per continent, which works for five or six groups and shows that most of Europe sits away from the fossil corner.

Highlight one story. The 8 countries that made most of their electricity from renewables are green, the rest gray, and the majority-fossil and majority-renewables regions are shaded.

Checklist
Do
- Write a title that states the takeaway, such as "27 US states made at least half their electricity from coal in 2001, and 5 did in 2025".
- Use sum=100 and ticksuffix="%" so ticks read 20% instead of 0.2.
- Name the axes in plain words, and color-code them when the audience is new to ternary plots.
- Zoom in with aaxis.min, baxis.min and caxis.min when the data uses a small part of the triangle, and say so in the subtitle.
- Draw reference lines and regions (50% lines, ties, classes) with a Scatterternary trace and fill="toself".
- Put all three values in the hover with hovertemplate.
- Use trails (mode="lines+markers") for change over time, with a hollow marker at the start.
Don't
- Label every point, because labels pile up along the edges where items with a zero share sit.
- Give every item its own color, because thirty colors can't be told apart.
- Read values by dropping a perpendicular to an edge; follow the gridlines.
- Plot three parts that don't add up to the whole without saying what was left out.
- Use a ternary plot for a few items that readers need to compare exactly.
Options to customize
These are the go.Scatterternary (and px.scatter_ternary, px.line_ternary) options and layout.ternary settings that matter most. The full list is in the Plotly ternary plots documentation.
- a, b, c: the three parts for the top, bottom-left and bottom-right corners. Raw amounts are fine, Plotly normalizes them.
- ternary.sum: the total the axes show (1 by default). Set it to 100 for percent ticks.
- sum (trace): the total each point is normalized to; match it to ternary.sum when your values are already shares.
- aaxis, baxis, caxis .title and .ticksuffix: axis titles and tick labels, in plain words and with %.
- aaxis.min, baxis.min, caxis.min: raise a minimum to zoom into a smaller triangle when the data sits in one part.
- color, linecolor, gridcolor, tickvals (on each axis): color-code axes for new readers and hide the corner ticks.
- ternary.bgcolor, ternary.domain: the background and the triangle's place in the figure, for several triangles side by side.
- mode: markers, lines, text or a combination; lines+markers for trails, text for labels.
- marker.size, sizemode, sizeref: bubble size, to add the absolute amount that shares hide.
- marker.color, colorscale, opacity: color by group or by a value such as the year, and transparency for dense data.
- marker.symbol, marker.angleref: marker shape; angleref="previous" points arrows along a line for before and after.
- fill="toself", fillcolor: fill a closed polygon for regions and classifications.
- line.dash, line.width: reference lines such as ties and 50% lines.
- text, textposition, cliponaxis: direct labels, and whether they can spill past the triangle.
- hovertemplate, customdata: hover text with %{a}, %{b} and %{c}.
- subplot: which ternary, ternary2 and so on a trace belongs to, for small multiples.
More ternary plot examples
Eleven more ternary plots built from real public data, each with its source, the technique that makes it work and a prompt to rebuild it.
1. US states moving off coal

In 2001, 27 US states made at least half their electricity from coal, and in 2025, 5 did. The arrows show where they went: Iowa from 85% coal to 63% clean power, mostly wind, and Pennsylvania from 57% coal to 59% gas. Data: Ember, US Electricity Data (from EIA-923), 2001 and 2025.
Tip: marker.symbol=["circle", "arrow"] with angleref="previous" turns a two-point line into an arrow that points along the move.
Get the data from Ember's US Electricity Data (ember-energy.org, compiled from EIA-923), 2001 and 2025and shape it into a table(columns: state, state_code, year, total_twh, coal_twh, gas_twh, clean_twh; 50 states and DC;gas includes other fossil; clean = nuclear + renewables).Build a plotly ternary plot with one arrow per state from 2001 to 2025: clean at the top, coal bottom left, gas bottom right.- Each state is a Scatterternary line with marker.symbol ["circle", "arrow"] and angleref="previous".- Gray for all states, six states in color with labels (Iowa, Pennsylvania, Ohio, Texas, West Virginia, Kentucky).- Shade the region where coal >= 50% with fill="toself".- Title with the number of majority-coal states in each year.
2. Germany, month by month

Fossil fuels made 55% of Germany's electricity in 2015 and 40% in 2025. Month by month, the cloud of 138 points drifts toward wind and solar, which peaked at 59% of generation in June 2025. Data: Ember, Monthly Electricity Data, January 2015 to June 2026.
Tip: setting aaxis.min, baxis.min and caxis.min zooms into a smaller triangle, and a sequential color by year turns 138 points into a visible drift.
Get the data from Ember's Monthly Electricity Data (ember-energy.org) for Germany since January 2015and shape it into a table(columns: date, total_twh, fossil_twh, wind_solar_twh, wind_twh, solar_twh, other_twh;other = nuclear + hydro + bioenergy + other renewables).Build a plotly ternary plot of every month: other low-carbon at the top, fossil bottom left, wind and solar bottom right.- Markers colored by year on a light-to-dark blue scale with a colorbar; a thin gray line joining months.- Zoom: fossil axis min 20%, the other two min 5%; say so in the subtitle.- Label the month with the highest wind and solar share.- Title with the fossil share in 2015 and 2025 (full years).
3. Who holds US federal debt

Foreign investors held 49% of US federal debt held by the public in 2008 and 30% in early 2026, while the debt grew from $5.3 trillion to $31.5 trillion. The Federal Reserve's share peaked at 26.5% in late 2021, after its pandemic purchases. Data: US Treasury via FRED (FYGFDPUN, FDHBFRBN, FDHBFIN), 1970-2026.
Tip: label only the turning points on a long trail with a hollow ring marker and text, and let the color scale carry the years.
Get the data from FRED (fred.stlouisfed.org) series FYGFDPUN (federal debt held by the public), FDHBFRBN(held by Federal Reserve Banks) and FDHBFIN (held by foreign and international investors), quarterly since 1970and shape it into a table(columns: date, public_bn, fed_bn, foreign_bn, domestic_other_bn = public - fed - foreign; US$ billions).Build a plotly ternary trail: Fed at the top, foreign bottom left, other US holders bottom right.- Markers colored by year on a sequential scale with a colorbar; thin gray connecting line.- Zoom with caxis.min=30; say so in the subtitle.- Ring and label the first quarter, the foreign peak, the Fed peak and the latest quarter.- Title with the foreign share at its peak and now.
4. What the rich and the rest own

The top 1% of US households now hold 68% of their assets in stocks and private businesses, up from 46% in 1989. For the bottom half, that share is 5%, and real estate and everything else (cars, deposits, pensions) make up the rest. Data: Federal Reserve, Distributional Financial Accounts, 1989 Q3 to 2026 Q2.
Tip: four trails in four parts of the triangle tell a story by position alone, so labels can replace the legend.
Get the data from the Federal Reserve's Distributional Financial Accounts (federalreserve.gov, dfa.zip,dfa-networth-levels.csv), quarterly since 1989and shape it into a table(columns: date, group [Top 1%, Next 9%, Next 40%, Bottom 50%], assets_bn, real_estate_bn,business_bn = corporate equities and mutual funds + unincorporated businesses, other_bn = the rest).Build a plotly ternary plot with one trail per wealth group: stocks and businesses at the top,real estate bottom left, everything else bottom right.- One color per group, hollow marker at the first quarter, label with the latest share at the end.- Check that the three parts add up to assets for every row.- Title with the top 1%'s stocks and businesses share then and now.
5. The federal budget since 1962

Discretionary spending fell from 68% of federal outlays in 1962 to 27% in 2025, as mandatory programs such as Social Security, Medicare and Medicaid grew to 59%. Net interest climbed back to 13.8% in 2025, from 5.2% in 2021. Data: OMB Historical Tables, FY2027 budget, Table 8.1.
Tip: year labels every decade along the trail are enough to anchor the timeline without a colorbar.
Get the data from the OMB Historical Tables, FY2027 budget, Table 8.1 (whitehouse.gov/omb), actual years 1962-2025and shape it into a table(columns: year, total_bn, discretionary_bn, mandatory_bn, interest_bn; US$ billions).Build a plotly ternary trail: net interest at the top, discretionary bottom left, mandatory bottom right.- Markers colored by year on a light-to-dark blue scale; label 1962, 1970, 1980, 1991, 2000, 2010, 2021 and 2025.- Zoom: discretionary and mandatory axes start at 20%.- Assert the three categories add up to the total. Title with discretionary's share in 1962 and 2025.
Another way to see it: a 100% stacked area chart reads more easily as a timeline and shows that net interest's 13.8% in 2025 is the highest share since 1998, but the thin interest band at the top is harder to compare across decades than its distance from the ternary plot's edge.

6. What people die of, by country income

In low-income countries, communicable, maternal, perinatal and nutritional conditions fell from 69% of deaths in 2000 to 48% in 2023. The loops are COVID-19, which WHO counts as communicable: in high-income countries the communicable share went from 6% in 2019 to 18% in 2021. Data: WHO Global Health Estimates, 2000-2023.
Tip: an ordered blue scale for the income groups and a dashed black line for the world keep five trails readable without a legend.
Get the data from the WHO Global Health Estimates (who.int, GHE_REGIONS on the xMart API), 2000-2023and shape it into a table(columns: region [Low income, Lower-middle income, Upper-middle income, High income, World], year,communicable, noncommunicable, injuries; deaths).Build a plotly ternary plot with one trail per group: injuries at the top, communicable bottom left, noncommunicable bottom right.- Light-to-dark blue by income, the world as a dashed black line; hollow marker at 2000, label at 2023.- Zoom with caxis.min=20. Mention the COVID-19 loops in the subtitle.- Title with the low-income communicable share in 2000 and 2023.
7. Population aging since 1960

Japan went from 30% children and 6% over-65s in 1960 to 12% and 30% in 2023, and 48 countries now have more people over 65 than under 15. Nigeria and Niger have barely moved. Data: World Bank, World Development Indicators (UN World Population Prospects 2024), 1960-2023.
Tip: gray bubbles for every country in the latest year give the trails context without competing with them.
Get the data from the World Bank's World Development Indicators (SP.POP.0014.TO.ZS, SP.POP.1564.TO.ZS,SP.POP.65UP.TO.ZS, SP.POP.TOTL), 1960-2023and shape it into a table(columns: country, iso3, region, year, pop, age_0_14, age_15_64, age_65_plus; percent of population).Build a plotly ternary plot: 65+ at the top, 0-14 bottom left, 15-64 bottom right; zoom with caxis.min=45.- All countries in 2023 as light gray bubbles sized by population.- Trails since 1960 for Japan, South Korea, Italy, China, United States, Brazil, India, Nigeria and Niger, labeled at the end.- A dashed line where 0-14 equals 65+. Title with Japan's 1960 and 2023 shares.
8. What 7,244 foods are made of

Of 7,244 foods in USDA's SR Legacy database, 3,182 get at least half their calories from carbohydrate, 1,822 from fat and 1,650 from protein. Meat and fish line the protein-fat edge with almost no carbohydrate, and cod is 92% protein. Data: USDA FoodData Central, SR Legacy.
Tip: small markers with opacity=0.45 turn overlap into density, so 7,244 points still read as shapes.
Get the data from USDA FoodData Central, SR Legacy (fdc.nal.usda.gov), nutrients protein (1003), fat (1004)and carbohydrate by difference (1005)and shape it into a table(columns: fdc_id, food, category, protein_g, fat_g, carb_g, kcal_macro = 4*protein + 9*fat + 4*carb;grams per 100 g; drop foods under 5 kcal and the Baby Foods and Restaurant Foods categories).Build a plotly ternary plot of calorie shares: protein at the top, carbohydrate bottom left, fat bottom right.- Group the 23 USDA categories into 8 food groups, one color each, "other" in gray drawn first.- Marker size 5, opacity 0.45; label 7 everyday foods with ring markers.- Title with the number of foods that get at least half their calories from each macronutrient.
9. The shape of big pharma's pipelines

Among the 30 biggest industry sponsors of trials started in 2015-2025, the share in phase 1 ranges from 21% for Novartis to 80% for South Korea's Chong Kun Dang. Across all 41,391 industry trials, 40% were phase 1. Data: ClinicalTrials.gov, 2015-2025.
Tip: a cross for the all-industry average gives every sponsor a reference point, and only the extremes need labels.
Get the data from the ClinicalTrials.gov API v2 (clinicaltrials.gov/api/v2/studies): interventional trialswith an industry lead sponsor, start date 2015-2025, in phase 1, 2 or 3and shape it into a table(columns: sponsor, hq, phase1, phase2, phase3, trials; phase 1/2 counts as phase 2, phase 2/3 as phase 3;merge subsidiaries into parents; keep the 30 sponsors with the most trials).Build a plotly ternary plot: phase 3 at the top, phase 1 bottom left, phase 2 bottom right.- Bubble area = trials, color by headquarters region; label the extremes, all sponsors in the hover.- An "x" marker for the share of all industry trials in each phase.- Title with the lowest and highest phase 1 share.
Another way to see it: 100% stacked bars sorted by phase 1 share give each sponsor's exact split, such as AstraZeneca's 44% phase 1, 26% phase 2 and 29% phase 3, but the clusters by region are harder to spot.

10. Paid work, unpaid work and leisure

Women do more unpaid work than men in all 35 countries, from 47 more minutes a day in Finland to 300 in India. Every pair of points runs the same way, with women toward unpaid work and men toward paid work and leisure. Data: OECD Time Use Database.
Tip: a gray line between two Scatterternary points per country makes a dumbbell inside the triangle, drawn before the markers so they sit on top.
Get the data from the OECD Time Use Database (OECD SDMX API, dataflow DSD_TIME_USE@DF_TIME_USE)and shape it into a table(columns: country, iso3, sex, paid, unpaid, leisure, flag; minutes per day, ages 15-64).Build a plotly ternary plot of shares of paid + unpaid + leisure time: leisure at the top, paid work bottom left,unpaid work bottom right.- Women orange, men blue, a gray line joining each country's two points (draw lines first).- Zoom: leisure min 15%, paid work min 10%. Label 8 countries.- Title with the smallest and largest gap in unpaid minutes.
Another way to see it: a dumbbell chart of unpaid minutes alone ranks the countries exactly and shows the average gap of 127 minutes a day, but it leaves out the paid work and leisure that the ternary plot shows move the other way. See our dumbbell chart guide.

11. A three-way election

Labour won 411 of 649 seats in the 2024 UK general election with 33.7% of the vote, and 341 of those seats were won with less than half the votes because the rest of the vote was split between the Conservatives and everyone else. Each seat is a point, colored by its winner. Data: House of Commons Library, election results, 4 July 2024.
Tip: draw the reference lines (here Labour equal to Conservative, and Labour at 50%) as two-point Scatterternary lines before the markers, so readers can see which side of each line a seat falls on.
Get the data from the House of Commons Library election results for the UK general election of 4 July 2024(https://electionresults.parliament.uk/general-elections/6/candidacies.csv) and shape it into a table(columns: constituency, nation, valid_votes, lab, con, other, winner; vote counts, Speaker's seat left out).Build a plotly ternary plot of each constituency's vote shares: all other candidates at the top,Labour bottom left, Conservative bottom right.- One point per seat, colored by the winning party, with the seat count in the legend.- A dashed line where Labour equals Conservative and a dotted line at Labour 50%.- Title with Labour's seats, vote share and the number of seats won with under half the vote.