px.imshow aspect=’auto’ vs ‘equal’: Heatmap Size and Zoom

Share on:

px.imshow draws square cells by default for NumPy arrays and pandas DataFrames (aspect="equal"). With aspect="auto", the cells stretch to fill the whole plotting area instead. Use "auto" when a wide or tall matrix would otherwise render as a thin strip, or when you need box-zoom to select a thin slice of the heatmap.

What aspect does in px.imshow

  • aspect="equal" keeps every cell square: one cell is as wide as it is tall. The heatmap keeps the shape of your array, so a 6 × 48 matrix becomes a long, thin strip.
  • aspect="auto" keeps the plotting area fixed and stretches the cells to fit it, so they are usually no longer square.
  • aspect=None, the default, means "equal" for NumPy arrays, lists and DataFrames, and "auto" for xarray data, whose coordinates often have different units.

Before and after: a wide matrix

Here is a 6 × 48 matrix of random values, drawn twice:

import numpy as np
import plotly.express as px

rng = np.random.default_rng(7)
data = rng.normal(size=(6, 48))   # 6 rows, 48 columns

# Default: aspect="equal", square cells
fig = px.imshow(data, color_continuous_scale="RdBu_r")
fig.show()

# Stretch the cells to fill the plot
fig = px.imshow(data, aspect="auto", color_continuous_scale="RdBu_r")
fig.show()
px.imshow with the default aspect: a 6 by 48 heatmap drawn as a thin strip of square cells
Default px.imshow(data): square cells turn a 6 × 48 matrix into a thin strip.

With the default, the 48 columns fill the width, but because every cell must stay square, the six rows end up only about 100 pixels tall and most of the figure is empty.

The same heatmap with aspect=auto: the cells stretch to fill the whole plot
With aspect="auto" the same data fills the plotting area.

With aspect="auto", the same data fills the whole plotting area. Each cell is now taller than it is wide, which is fine for a heatmap: the colour carries the information, not the cell shape.

Why box-zoom is locked with aspect=”equal”

If you drag a box to zoom into a default px.imshow heatmap, the zoom box keeps its proportions, so you cannot zoom into a thin horizontal or vertical slice. That is the behaviour reported in plotly.py issue #3636.

The reason is in the layout that aspect="equal" sets. You can inspect it:

fig = px.imshow(data)
print(fig.layout.xaxis.scaleanchor, fig.layout.xaxis.constrain)   # y domain

fig = px.imshow(data, aspect="auto")
print(fig.layout.xaxis.scaleanchor, fig.layout.xaxis.constrain)   # None None

With scaleanchor="y", the x-axis is tied to the y-axis, so zooming one zooms the other by the same amount. aspect="auto" removes that link, and box-zoom then works in any direction.

RGB images ignore aspect

For a colour image, an array with shape (height, width, 3) or 4 channels, px.imshow draws an Image trace instead of a Heatmap. The aspect argument has no effect there, and the browser keeps the pixels square (issue #5172). To let the image zoom freely, remove the axis link, as a Plotly collaborator suggests in issue #4861:

fig = px.imshow(rgb_image)
fig.update_layout(xaxis_scaleanchor=False, yaxis_scaleanchor=False)   # False, not None

Other px.imshow options worth knowing

Most real heatmaps, such as a correlation matrix, use a few more arguments:

import pandas as pd

df = pd.DataFrame(rng.normal(size=(200, 5)),
                  columns=["price", "rooms", "area", "age", "distance"])
corr = df.corr()

fig = px.imshow(
    corr,
    text_auto=".2f",                   # print each value in its cell
    aspect="auto",
    color_continuous_scale="RdBu_r",   # blue for negative, red for positive
    zmin=-1, zmax=1,                   # fix the colour range for correlations
    labels=dict(x="Feature", y="Feature", color="Correlation"),
)
fig.show()
  • text_auto: True, or a d3 format string such as ".2f", writes the values in the cells (single-channel data only).
  • color_continuous_scale: any named Plotly colour scale, such as "Viridis" or "RdBu_r", or a list of colours.
  • zmin and zmax: fix the ends of the colour scale. Without them the range follows your data.
  • labels: titles for the x axis, the y axis and the colour bar (keys x, y and color), also used in the hover text.
  • x and y: tick labels for the columns and rows. Their lengths must match the array.
  • origin: "upper" (the default, row 0 at the top) or "lower".

FAQ

What is the default aspect in px.imshow?

None, which means "equal" (square cells) for NumPy arrays, lists and pandas DataFrames, and "auto" for xarray data.

How do I make a px.imshow heatmap fill the figure?

Pass aspect="auto". The cells then stretch to fill the plotting area. To change the size of the figure itself, use fig.update_layout(width=..., height=...).

Why can’t I zoom into part of my px.imshow heatmap?

With the default aspect="equal", the x-axis is anchored to the y-axis, so the zoom box keeps its shape. Use aspect="auto" to zoom freely.

Does aspect=”auto” work for RGB images?

No. For colour images, px.imshow uses an Image trace, which keeps pixels square. Remove the axis link with fig.update_layout(xaxis_scaleanchor=False, yaxis_scaleanchor=False) instead.

The code in this post was tested with plotly 5.22, and the aspect behaviour matches the current plotly 7 documentation. For more chart types in Matplotlib, Seaborn and Plotly, see the Python data visualization guide.

Anup-Das-Anuptechtips

I write about System Design, Backend Architecture, GenAI Infrastructure, and scalable Python applications.

Leave a Comment