proplot-dev / proplot

🎨 A succinct matplotlib wrapper for making beautiful, publication-quality graphics
https://proplot.readthedocs.io
MIT License
1.11k stars 102 forks source link

Understanding autolayout and colorbar placement #380

Closed cvanelteren closed 1 year ago

cvanelteren commented 2 years ago

Description

I have been using proplot for the last few months and most of the time it takes off the heavy lifting that is a bi tricky to do in native matplotlib. However, sometimes, the autolayout feature fails without finding a trivial way to prevent the layout from changing. What is the problem? I am trying to add some annotation to a complex plot I am making (see simplified snippet below), and I am trying to understand how proplot computes the layout for a figure. Are there any resources on the rationale or more info on what went into the underlying design? I have browsed a bit around in the source code (e.g. here) to understand what proplot is doing.

In my case I aim to provide a simple annotation next to a figure with a colorbar. The problem is that hte autoscale feature produces a layout where the colorbar is removed from the main axes.

image whereas what I aim to achieve is something similar to: image

Steps to reproduce

# import proplot as plt
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# add colorbar proplot
norm = plt.pyplot.cm.colors.Normalize(vmin = 0, vmax = 1)
s = plt.pyplot.cm.ScalarMappable(norm = norm, cmap = "jet")

# add colorbar matplotlib
# norm = plt.cm.colors.Normalize(vmin = 0, vmax = 1)
# s = plt.cm.ScalarMappable(norm = norm, cmap = "jet")
cbar = fig.colorbar(s)
# cbar = ax.colorbar(s)

# add annotation
bbox = cbar.ax.get_window_extent()
c = bbox._transform.inverted().transform([bbox.x1, 0])
x = c[0] * 4
start = (x, 0.8)
end = (x, 1)

p=dict(arrowstyle="<-",
    connectionstyle="arc3",
    lw= 2)

ax.annotate("test", start, xytext = end,
            xycoords = cbar.ax.transAxes,
            ha = "center", va = "center",
            arrowprops = p)
fig.show()
Equivalent matplotlib code ```python import matplotlib.pyplot as plt import matplotlib print(matplotlib.__version__) fig, ax = plt.subplots() # add colorbar matplotlib norm = plt.cm.colors.Normalize(vmin = 0, vmax = 1) s = plt.cm.ScalarMappable(norm = norm, cmap = "jet") cbar = fig.colorbar(s) # cbar = ax.colorbar(s) # add annotation bbox = cbar.ax.get_window_extent() c = bbox._transform.inverted().transform([bbox.x1, 0]) x = c[0] * 4 start = (x, 0.8) end = (x, 1) p=dict(arrowstyle="<-", connectionstyle="arc3", lw= 2) ax.annotate("test", start, xytext = end, xycoords = cbar.ax.transAxes, ha = "center", va = "center", arrowprops = p) fig.show() ```

Proplot version

0.9.5

cvanelteren commented 2 years ago

For posterity, I ended up using this

import proplot as plt
fig, ax = plt.subplots()
# add colorbar proplot
norm = plt.pyplot.cm.colors.Normalize(vmin = 0, vmax = 1)
s = plt.pyplot.cm.ScalarMappable(norm = norm, cmap = "jet")

cbar = ax.colorbar(s)

# add annotation
x = 3 # heuristic
start = (x, 0.8)
end = (x, 1)

p=dict(arrowstyle="<-",
    connectionstyle="arc3",
    lw= 2)

cbar.ax.annotate("test", start, xytext = end,
            xycoords = cbar.ax.transAxes,
            ha = "center", va = "center",
            arrowprops = p)
fig.show()

which produces. image

This makes intuitive also more sense than my prior hacky approach. Perhaps this is a non-issue ;-)!

lukelbd commented 1 year ago

Good fix! This was tight-layout related -- setting tight=False and/or using ax.annotate(..., in_layout=False), then adding space to the edge of the figure with e.g. pplt.subplots(right=3), would also work. But your fix is simpler.

Basically, since you were calling the original annotate function from the main axes, proplot's tight layout algorithm was trying to offset the colorbar away from the text (matplotlib's own tight layout algorithm would have done something similar if you had multiple subplots in the figure). However, since the annotation position is relative to the colorbar, it got moved again after the colorbar was moved, producing that weird result.

Strangely, if I re-draw the figure repeatedly with fig.show() you can see the arrow "chasing" the colorbar away.

...
fig.show()

iTerm2 4xfhru tmps3vgvymu

fig.show()

iTerm2 3PKISY tmph_9e_6yt

fig.show()

iTerm2 Ilplo7 tmp5ptm8ud2

cvanelteren commented 1 year ago

Interesting. Thanks for the additional info.