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

Chris Parmer

Chris Parmer

November 7, 2026

2D Histograms and Density Contour Plots - A Complete Guide

TL;DR

  • A 2D histogram splits the plane of two numeric variables into a grid of cells and colors each cell by how many points fall in it, so hundreds of thousands of overlapping points become a readable picture of where the data is dense. A 2D histogram contour plot (density contour) draws the same counts as lines or bands of equal count.
  • 2D histograms are also known as density heatmaps and bivariate histograms, and with a histogram of each variable in the margins as joint plots; the contour version is also called a density contour plot. They are similar to marginal plots, hexbin plots, 2D density plots, heatmaps, and contour plots.
  • The setting that changes what you see is the cell size: too small and the chart turns into noise, too big and the groups melt together. Pick it yourself, and use a log color scale when the busiest cells hold thousands of times more points than the rest.
  • In Python, px.density_heatmap and px.density_contour make one in one line of code (the histogram2d and histogram2dcontour traces), with marginal_x and marginal_y for histograms in the margins. Plotly drew 10 million points as a 2D histogram in 2.6 seconds in our test.

What is a 2D histogram?

2D histograms look like this:

2D histogram of 314,942 ten-minute readings from six wind turbines at Kelmarsh wind farm in 2019, wind speed against power output on a log color scale: a dark S-shaped power curve rising from about 3 m/s to the rated 2,050 kW near 11 m/s, a flat band at full power, a row of readings at 0 kW, and scattered cells below the curve

Data: Kelmarsh wind farm SCADA data 2019, Plumley (2022), Zenodo, CC BY 4.0.

How do you read a 2D histogram? In the example above, notice these things:

  • Each small rectangle is a cell 0.25 m/s wide and 25 kW tall, and its color is how many of the 314,942 ten-minute readings fell in it. Each reading is a 10-minute average logged by the turbine’s supervisory control and data acquisition (SCADA) system.
  • The color scale is logarithmic: each step on the color bar is 10 times more readings. The dark S is the turbines’ power curve, where most readings sit, and the pale cells around it hold one to a few readings each.
  • Power starts at about 3 m/s and reaches the rated 2,050 kW near 11 m/s. The flat band at the top shows the turbines at full power, 5.1% of the time.
  • The row along 0 kW holds 10% of the readings. Most of them are calm periods under 3 m/s, and the rest are stops in stronger wind, which a scatter plot of 300,000 dots would hide under the curve.

It’s often used for charts like these:

Density contour lines of Old Faithful's 272 eruptions by eruption length and wait to the next eruption, labeled with counts from 4 to 24 per cell, over gray dots, with histograms of each variable in the margins: one group of short eruptions around 2 minutes followed by 54-minute waits and one of long eruptions around 4.3 minutes followed by 80-minute waits

The classic: two groups in two variables. Old Faithful’s 97 short eruptions are followed by a median wait of 54 minutes and its 175 long ones by 80 minutes, so the length of one eruption predicts the wait for the next. Data: R dataset faithful, Azzalini and Bowman (1990).

Two sets of density contour lines of daily US stock returns against daily changes in the 10-year Treasury yield: the blue 2000-2020 lines tilt up to the right, correlation +0.37, and the orange 2022-2026 lines tilt slightly the other way, correlation -0.10

The joint distribution of two returns. From 2000 to 2020, the 10-year Treasury yield fell (so bond prices rose) on 61% of the days the stock market fell; since 2022 it rose on 57% of them, so bonds fell along with stocks. Data: Kenneth R. French Data Library (the Center for Research in Security Prices (CRSP) market return) and Federal Reserve Economic Data (FRED, series DGS10).

Temperature-salinity diagram of 200,000 North Atlantic Argo float measurements as a 2D histogram on a log color scale with gray lines of equal potential density: a long diagonal band from warm salty surface water to a dark cluster at 3-4 degrees C and salinity 34.9, and a salty tongue at 8-12 degrees C reaching salinity 36.2

Temperature-salinity (T-S) diagrams in oceanography. Oceanographers recognize water masses as dense clusters on a T-S diagram: 17% of these North Atlantic measurements fall at 3-4 °C and a salinity of 34.85-34.95, at a median depth of about 1.5 km, and salty Mediterranean water shows up as a tongue near 1,000 m. Data: Argo floats via Ifremer’s data server, 2023.

How a 2D histogram counts points

A one-dimensional histogram splits one axis into bins and counts the values in each. A 2D histogram does the same on two axes at once, so the bins become a grid of rectangles, and a contour plot of it joins places with equal counts.

Step 1: draw a grid and count. Here are Old Faithful’s 272 eruptions in cells 0.5 minutes wide and 5 minutes tall. The busiest cells hold 23 and 24 eruptions, and most of the grid is empty:

Old Faithful's 272 eruptions as gray dots over a grid of 0.5 minute by 5 minute cells, each filled cell showing its count, from 1 up to 24 in the cells around 4.0 to 5.0 minutes and 75 to 85 minutes

Step 2: draw lines through equal counts. A 2D histogram contour plot takes the same counts, treats each as a height at the middle of its cell, and draws contour lines through the places where the count would be 4, 8, 12, and so on, filling the bands between them. The contour levels are counts per cell, so the labels change if you change the cell size:

The same Old Faithful counts drawn as a filled 2D histogram contour plot with bands labeled 4 to 24 eruptions per cell, the original cell counts shown as small gray numbers

The lines are smooth only because they are interpolated between cell centers; the information is still the counts of step 1. That’s the main difference from a 2D kernel density estimate (KDE), which puts a small smooth bump on every point and adds them up, so its contours don’t depend on a grid at all.

Choosing the cell size

The cell size plays the part that the bin width plays in a histogram. With cells that are too small, each cell holds zero or one point and the chart is noise. With cells that are too big, the shape of each group disappears into a few blocks:

Old Faithful's 272 eruptions as three 2D histograms: 0.1 by 1 minute cells with a maximum of 5 per cell and 74% of filled cells holding one eruption, 0.5 by 5 minute cells showing two clear groups with up to 24 per cell, and 1.5 by 15 minute cells with up to 85 per cell and no detail inside the groups

A few rules help:

  • Aim for the busiest cells to hold at least 10 to 20 points; with 272 points, that means a grid of about eight by 12 cells, not 40 by 60.
  • MATLAB’s histogram2 offers Scott’s rule, a cell width of 3.5 × standard deviation × n^(−1/4) on each axis. For Old Faithful it gives 0.98 by 11.7 minutes, which is too coarse for data with two groups, so treat it as a starting point and try half of it.
  • Use round numbers that match the data’s resolution: 0.25 m/s for wind speed, 1 mmHg for blood pressure, and 1 second for a wave period. If the data are rounded, like Old Faithful’s waits in whole minutes, make the cell a multiple of the rounding or some cells will get more values than others.
  • With hundreds of thousands of points, you can afford small cells; then use a log color scale so the sparse cells still show.

A short history

Francis Galton made one of the first 2D histograms in 1886, a table of 928 adult children’s heights against their mid-parent’s height in one-inch classes, in “Regression towards mediocrity in hereditary stature”. He smoothed it by writing at each corner “the sum of the entries in the four adjacent squares”, and noticed that “lines drawn through entries of the same value formed a series of concentric and similar ellipses”, which led him to the idea of regression (Friendly and Denis 2005 trace the story). This is his table redrawn with Plotly, with contours through the same four-cell sums:

Galton's 1886 table redrawn: a grid of one-inch cells of adult children's height against mid-parent's height with Galton's count in each cell, up to 48, and purple contour lines through equal four-cell sums at 10, 30, 50 and 70 forming tilted oval rings

Data: Galton (1886), Table I, via the R package HistData (Galton). A redraw from his data, not a copy of his Plate X.

Hexagonal bins followed a century later, when Daniel Carr and colleagues proposed them for scatter plots with too many points (Carr et al. 1987).

Alternatives to a 2D histogram

Here’s the same set of 314,942 turbine readings drawn six ways. Some are better for the question, some worse, some just different:

The Kelmarsh readings drawn six ways: a scatter plot where the power curve is a solid band, a 2D histogram on a log color scale, a 2D histogram contour of raw counts that shows only a sliver of the curve near the busiest cells, a hexbin plot on a log scale, a KDE contour on a log scale from a sample of 20,000 that smears the sharp edges, and a binned average power curve that falls above 17 m/s
  • Scatter plot (worse above about 10,000 points, better below). It shows every point, including the rare ones, but here the curve is a solid band and you can’t tell 10 readings from 10,000.
  • 2D histogram contour of the raw counts (worse here, better for data within one order of magnitude). The busiest cells hold over 5,000 readings, so linear contour levels only show the low-wind end of the curve. It works well when the counts are similar in size, as in the Old Faithful and stock market examples.
  • Hexbin plot (works too). Hexagons count the same points with less visible grid structure, and every neighboring cell is the same distance away. With Plotly you draw the hexagons from your own bins, while a 2D histogram is one trace.
  • 2D density plot, a KDE contour (works too, for smooth shapes). It needs no grid and gives smooth contours, but evaluating it took seconds on a 20,000-point sample, and it blurs the sharp edge at rated power and the row at 0 kW.
  • Binned average line (better for the question “what does the turbine produce at each wind speed”). This is the method of bins that power curve tests use. It summarizes each column in one number, but the average hides the spread, and above 17 m/s a few stops pull it down to 1,510 kW at 20 m/s.
  • Heatmap of a matrix (different). A heatmap colors cells by a value you already have, such as a correlation matrix or prices by hour and day. A 2D histogram computes its value by counting points; once counted, you can draw it with go.Heatmap, as several examples here do. A contour plot is the same relationship for contours: go.Contour draws a grid you give it, go.Histogram2dContour makes the grid from points.

How to make a 2D histogram

With AI

ChatGPT, Claude Code, GitHub Copilot, Cursor, and other AI tools are excellent at writing Plotly code. Name Plotly in the prompt, and say what the cells should be and how to color the counts:

Make a plotly 2D histogram from kelmarsh.csv (columns: turbine, ws = wind speed in m/s, kw = power in kW;
314,942 ten-minute readings).
- Bin with numpy.histogram2d in cells 0.25 m/s wide and 25 kW tall, x from 0 to 24 m/s, y from -50 to 2,175 kW,
and draw the counts with go.Heatmap.
- Color by log10(count) with a light-to-dark blue scale, empty cells blank (NaN), and a color bar labeled
1, 10, 100, 1,000, 10,000. Show the real count in the hover text.
- Add a dotted line at the rated power, 2,050 kW, and axis titles with units.
- Check: print the busiest cell and the share of readings at 1,950 kW or more.

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 2D histogram the same way:

Show a 2D histogram of wind speed against power output for each turbine, with a log color scale, a slider for the cell size, and a dropdown to pick the turbine.

Make a 2D histogram in Python or JavaScript

This is the code AI tools write under the hood. In Python, Plotly Express makes a 2D histogram with px.density_heatmap and a 2D histogram contour plot with px.density_contour, and both take marginal_x and marginal_y for histograms in the margins (marginal plots):

import pandas as pd
import plotly.express as px
df = pd.read_csv("faithful.csv") # 272 eruptions: eruptions and waiting, in minutes
# 2D histogram (density heatmap): counts of points per cell, with a histogram of each variable in the margins
fig = px.density_heatmap(df, x="eruptions", y="waiting", marginal_x="histogram", marginal_y="histogram")
fig.update_traces(xbins=dict(start=1.5, end=5.5, size=0.5), ybins=dict(start=40, end=100, size=5),
selector=dict(type="histogram2d"))
fig.show()
# 2D histogram contour (density contour): lines through equal counts, drawn over the points
fig = px.density_contour(df, x="eruptions", y="waiting")
fig.update_traces(xbins=dict(start=1, end=6, size=0.5), ybins=dict(start=30, end=110, size=5),
contours_showlabels=True)
fig.add_scatter(x=df.eruptions, y=df.waiting, mode="markers", marker=dict(size=4, color="gray"))
fig.show()

Set xbins and ybins yourself rather than nbinsx and nbinsy, which Plotly treats as a maximum and rounds to nice sizes. Start the contour’s bins one cell before the data and end one after, so the lines close around the groups instead of stopping at the edge.

For hundreds of thousands of points, or for a log color scale, count the cells with NumPy and draw only the counts. The browser then gets 8,544 cells instead of 314,942 points:

import numpy as np
import pandas as pd
import plotly.graph_objects as go
df = pd.read_csv("kelmarsh.csv") # 314,942 ten-minute readings: ws (m/s), kw (kW)
# Bin in Python, then send only the counts: 96 x 89 cells instead of 314,942 points
counts, xe, ye = np.histogram2d(df.ws, df.kw, bins=[np.arange(0, 24.01, 0.25), np.arange(-50, 2176, 25)])
counts = counts.T # rows are y bins
z = np.log10(np.where(counts > 0, counts, np.nan)) # log color; empty cells stay blank
fig = go.Figure(go.Heatmap(
x=(xe[:-1] + xe[1:]) / 2, y=(ye[:-1] + ye[1:]) / 2, z=z, customdata=counts, colorscale="blues",
hovertemplate="%{x} m/s, %{y} kW: %{customdata:,.0f} readings<extra></extra>",
colorbar=dict(title="Readings", tickvals=[0, 1, 2, 3, 4], ticktext=["1", "10", "100", "1,000", "10,000"]),
))
fig.update_layout(xaxis_title="Wind speed (m/s)", yaxis_title="Power output (kW)")
fig.show()

In JavaScript, plotly.js bins the points itself with a histogram2dcontour (or histogram2d) trace:

<div id="chart"></div>
<script src="https://cdn.plot.ly/plotly-4.1.1.min.js"></script>
<script>
// Old Faithful's 272 eruptions (R faithful dataset), as CSV from the Rdatasets mirror
fetch("https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/master/csv/datasets/faithful.csv")
.then(r => r.text())
.then(text => {
const rows = text.trim().split("\n").slice(1).map(l => l.split(","));
const x = rows.map(r => +r[1]), y = rows.map(r => +r[2]); // eruptions, waiting
Plotly.newPlot("chart", [
{type: "histogram2dcontour", x: x, y: y, colorscale: [[0, "white"], [1, "#1d5aa6"]],
xbins: {start: 1, end: 6, size: 0.5}, ybins: {start: 30, end: 110, size: 5},
contours: {start: 4, end: 24, size: 4, showlabels: true}, colorbar: {title: {text: "Eruptions"}}},
{type: "scatter", mode: "markers", x: x, y: y, marker: {size: 4, color: "gray"}, showlegend: false}
], {
width: 800, height: 560,
xaxis: {title: {text: "Eruption length (minutes)"}, range: [1.4, 5.6]},
yaxis: {title: {text: "Wait until the next eruption (minutes)"}, range: [38, 100]}
});
});
</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 command-line interface (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 what makes a 2D histogram readable is the cell size, the color scale, and the labels. Here are the turbine readings both ways, with the same blue color scale.

Don’t let a few hundred tiny cells and a linear color scale decide the chart. The busiest cells, idle turbines in calm wind, hold thousands of readings, so everything else fades to the palest blue, and the raw column names ws and kw leave the reader guessing at units:

The turbine readings as a 2D histogram with about 300 by 300 tiny cells and a linear color scale from 0 to about 2,000: almost everything is pale, only a dark smudge near 2-3 m/s and 0 kW stands out, the power curve is a faint ghost, and the axes are titled ws and kw

Do pick cells that match the data’s resolution, color the counts on a log scale with empty cells left white, and write axis and color bar titles in words with units:

The same readings in 0.25 m/s by 25 kW cells on a log color scale from 1 to 10,000 with empty cells white, showing the full power curve, the flat top at rated power and the row of zeros, with axis titles Wind speed (m/s) and Power output (kW)

Checklist

Do

  • Set the cell size yourself (xbins, ybins or np.histogram2d edges) and say it in the subtitle.
  • Use a log color scale when counts span more than about two orders of magnitude, and label the color bar in real counts (1, 10, 100).
  • Leave empty cells white (NaN), so “no data” doesn’t look like “a little data”.
  • Use histnorm="percent" or "probability" when you compare groups of different sizes, as in the stock market example.
  • Draw the points under density contours when there are fewer than a few thousand, so readers see how few points the outer lines rest on.
  • Add marginal histograms when each variable’s own distribution matters (see the marginal plot guide).

Don’t

  • Compare colors between two charts with different cell sizes; the counts per cell change with the area of the cell.
  • Use histfunc="avg" on go.Histogram2d without checking the empty cells: they are drawn as an average of 0. Compute averages with pandas and draw them with go.Heatmap (example 3 below).
  • Read structure into cells that hold one or two points.
  • Use a rainbow color scale for counts; a single-hue scale from light to dark reads as more and less.

Options to customize

A 2D histogram in Plotly is a histogram2d trace (px.density_heatmap) and the contour version a histogram2dcontour trace (px.density_contour). These are the options that matter most; the full lists are in the histogram2d reference and the histogram2dcontour reference.

  1. xbins, ybins (start, end, size): the cell edges. Set them yourself; use the same bins for every trace you compare.
  2. nbinsx, nbinsy: a maximum number of bins that Plotly rounds to nice sizes. Fine for a first look.
  3. histfunc ("count", "sum", "avg", "min", "max") with z: aggregate a third variable per cell instead of counting points.
  4. histnorm ("percent", "probability", "density", "probability density"): normalize counts, to compare groups of different sizes or cells of different areas.
  5. marginal_x, marginal_y in Plotly Express ("histogram", "box", "violin", "rug"): a distribution of each variable in the margins, the joint plot layout.
  6. contours (start, end, size, coloring="fill", "lines" or "heatmap", showlabels, labelfont): the contour levels, which are counts per cell, and how they are drawn.
  7. ncontours with autocontour=True: a maximum number of levels when you don’t set them.
  8. colorscale, zmin, zmax, reversescale: the color mapping; set zmax when comparing charts.
  9. texttemplate="%{z}": print the count in each cell, as in a scatter diagram table.
  10. xgap, ygap on go.Heatmap: thin white lines between cells, useful when the cells are few and large.
  11. coloraxis: one shared color scale for several 2D histograms in subplots.
  12. numpy.histogram2d plus go.Heatmap or go.Contour: bin in Python for log color scales, millions of points, or masks on sparse cells.

More 2D histogram examples

Six more 2D histograms and density contour plots built from real public data, each with its source, the technique that makes it work, and a prompt to rebuild it.

1. Blood pressure categories in US adults

2D histogram of diastolic against systolic blood pressure for 6,100 US adults in 2 mmHg cells with histograms in the margins and the 2017 ACC/AHA category boundaries as dashed red L-shaped lines: normal 46%, elevated 14%, stage 1 22%, stage 2 18%, with many cells above the 140 systolic line but left of the 90 diastolic line

Blood pressure categories are regions of this plane, so the cells show who falls where: 18% of 6,100 adults are in stage 2 hypertension, and of the 900 with a systolic reading of 140 or more, 64% have a diastolic under 90. Data: US Centers for Disease Control and Prevention (CDC), National Health and Nutrition Examination Survey (NHANES) August 2021-August 2023; categories from the 2017 American College of Cardiology and American Heart Association (ACC/AHA) guideline.

Tip: draw guideline thresholds as L-shaped lines, because a category defined by “either number at or above” is a corner, not a box.

Get the data from CDC NHANES August 2021-August 2023 files BPXO_L (BPXOSY1-3 systolic and BPXODI1-3 diastolic
oscillometric readings, mmHg) and DEMO_L (RIDAGEYR), adults 18 and over, mean of the three readings
(columns: seqn, age, sys, dia).
Build a plotly px.density_heatmap of dia (x) against sys (y) with 2 mmHg bins and marginal histograms, dashed
L-shaped lines at 120, 130/80, and 140/90, and the share of adults in each ACC/AHA category written on the chart.

2. German power prices by hour of day

2D histogram of 8,760 German day-ahead power prices in 2025 by hour of day and 10 EUR/MWh price bins, with an orange line of hourly medians dipping from about 110 EUR/MWh in the morning to 49.7 at 1 pm and rising to 125.2 at 8 pm, and a band of negative prices around midday

With the hour on the x axis, each column is a year of prices for that hour. The median fell to 49.7 EUR/MWh at 1 pm, in the midday solar peak, and 99 of 2025’s 573 negative-price hours were at 1 pm. Data: Energy-Charts (Fraunhofer ISE), prices from Bundesnetzagentur | SMARD.de.

Tip: when one axis is time of day, every column has the same number of points (365 here), so the counts compare directly without normalizing.

Get the data from the Energy-Charts API of Fraunhofer ISE (api.energy-charts.info): /price?bzn=DE-LU for 2025,
15-minute prices averaged to hours, in German local time (columns: time, hour, price in EUR/MWh).
Build a plotly go.Histogram2d with one bin per hour and 10 EUR/MWh price bins from -100 to 300, a white-to-blue
scale, a line of each hour's median price on top, and a zero line.

3. Power prices against wind and solar output and demand

Grid of average German power prices in 2025 by wind and solar output (x, 0 to 64 GW) and load (y, 33 to 76 GW): dark orange above 150 EUR/MWh in the top left, fading to blue below zero in the bottom right, with dashed diagonal lines of equal residual load at 10, 30 and 50 GW

Instead of counting hours, each cell shows their average price. Hours with under 10 GW of wind and solar averaged 131 EUR/MWh and hours with 50 GW or more averaged 3, and the colors follow the diagonals of equal residual load, the demand left for other plants. Data: Energy-Charts (Fraunhofer ISE), 8,760 hours in 2025.

Tip: compute averages with pandas and draw them with go.Heatmap, leaving cells with under three hours empty, because histfunc="avg" paints empty cells as 0.

Get the data from the Energy-Charts API of Fraunhofer ISE: /price?bzn=DE-LU and /public_power?country=de for
2025, averaged to hours (columns: time, price in EUR/MWh, load_gw, wind_gw, solar_gw).
Build a plotly go.Heatmap of the average price per 2 GW by 2 GW cell of wind_gw + solar_gw (x) and load_gw (y),
computed with pandas cut and groupby, cells with under 3 hours left empty, a scale from blue below 0 to dark
orange above 150, and dashed lines where load minus wind and solar is 10, 30, and 50 GW.

4. Where home runs come from

2D histogram of all 124,439 batted balls of the 2025 MLB regular season by launch angle and exit velocity in 3 degree by 2 mph cells, in light blue, with orange contour lines of the 5,648 home runs on the same cells concentrated in a dashed box at 20 to 35 degrees and 100 mph or more

The blue cells are every batted ball tracked in the 2025 Major League Baseball (MLB) regular season, and the orange lines are the home runs alone. Balls hit 100 mph or harder at 20-35 degrees were 6.7% of all batted balls but 77% of the 5,648 home runs, and 52% of them left the park. Data: MLB Statcast via Baseball Savant.

Tip: to compare a subset with the whole, draw the whole as a 2D histogram and the subset as contour lines on the same bins, so neither hides the other.

Get the data from Baseball Savant's Statcast search CSV export, 2025 regular season, one request per day, keeping
balls in play with launch_speed and launch_angle (columns: ev = launch_speed in mph, la = launch_angle in degrees,
hr = 1 if events is home_run).
Build a plotly go.Histogram2d of all batted balls (la on x, ev on y) in 3 degree by 2 mph cells on a white-to-blue
scale, a go.Histogram2dContour of the home runs on the same bins with coloring="lines" in orange and labeled
levels, and a dashed box at 20-35 degrees and 100+ mph with the share of balls and home runs inside it.

5. A wave scatter diagram for an offshore site

Wave scatter diagram for buoy 42040 in the Gulf of Mexico, 2015 to 2024: a grid of 1-second mean zero-crossing period by 0.5 m significant wave height cells with the number of hours printed in each, 14,056 hours in the busiest cell at 3-4 seconds and 0.5-1 m, and single hours up to 9.18 m during Hurricane Nate

Offshore engineers size platforms and plan vessel operations from a scatter diagram, the hours a site spends in each cell of significant wave height (Hs) and mean zero-crossing period (Tz). At this Gulf of Mexico buoy, 63% of 78,306 hours had waves under 1 m and 22 hours had 6 m or more, the highest 9.18 m during Hurricane Nate in 2017. Data: US National Oceanic and Atmospheric Administration (NOAA), National Data Buoy Center, station 42040.

Tip: print the count in every filled cell and leave the empty ones blank, the table layout of offshore standards such as DNV-RP-C205, with a light color scale behind the numbers.

Get the data from the NOAA National Data Buoy Center historical standard meteorological files for station
42040, 2015-2024 (42040h{year}.txt.gz), the first wave record in each hour (columns: time, hs = WVHT in m,
tz = APD in seconds).
Build a plotly go.Heatmap scatter diagram: counts from numpy.histogram2d in 1 s by 0.5 m cells, the hour count
printed in every filled cell with texttemplate, empty cells blank, a light log-scaled blue behind the numbers,
xgap and ygap of 1, and the largest Hs labeled with its date.

6. Exoplanet masses and orbits

Exoplanet mass against orbital period on log axes for 3,035 planets with measured masses, dots colored by detection method (transit blue, radial velocity orange) with purple density contour lines: a tight cluster of hot Jupiters at 3 to 5 days and about Jupiter's mass, a cluster of small planets at 1 to 30 Earth masses on short orbits, and a cloud of giants on orbits of hundreds to thousands of days found by radial velocity

Of 3,035 exoplanets with a measured mass, 716 (24%) are hot Jupiters, giants that orbit in under 10 days, and 655 of those were found by transits, which favor big planets on short orbits. The contour lines show three crowds, and the dot colors show how much each owes to the detection method. Data: NASA Exoplanet Archive, Planetary Systems Composite Parameters.

Tip: bin the log10 of both variables and relabel the ticks as real numbers, because Plotly’s histogram bins are linear even on a log axis.

Get the data from the NASA Exoplanet Archive Table Access Protocol (TAP) service, table pscomppars, planets with pl_orbper and
pl_bmasse and pl_bmassprov of "Mass" or "Msini" (columns: pl_name, discoverymethod, pl_orbper, pl_bmasse).
Build a plotly figure: dots of log10(pl_orbper) against log10(pl_bmasse) colored by discoverymethod, and a
go.Histogram2dContour of the same log values with 0.25 bins, coloring="lines", labeled levels 10, 40, 70, and
100, ticks relabeled 0.1, 1, 10, 100, 1,000, and dotted lines at Earth's and Jupiter's mass.

FAQ

bluesly
x
instagram
youtube
medium
fcaebook

Product

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