Reputation: 565
My in my "real world" problem i want to recalculate the x y values written in the tick labeling of my figure after i have zoomed in it in such a way that the origin is always at (0,0) and obviously the relative distances of the values on the x and y axis stay the same. My problem was solved in this thread: Initial solution
The solution includes the creation of one invisible axis that holds the plot and one visible axis that gets different tick lables after zooming.
In my case i want to superimpose multiple countor and coutourf plots. For only one of those plots i want to add a colorbar to the figure! But when i create the colorbar in my script, the two axis objects i have created shift relative to each other. Without the colorbar they are perfectly aligned!
Here is a MWE that roughly recreates the behavior:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab, cm
# Default delta is large because that makes it fast, and it illustrates
# the correct registration between image and contours.
delta = 0.5
extent = (-3, 4, -4, 3)
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = (Z1 - Z2) * 10
levels = np.arange(-2.0, 1.601, 0.4) # Boost the upper limit to avoid truncation errors.
norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=2)
ax2.contourf(X, Y, Z, levels,
cmap=cm.get_cmap(cmap, len(levels) - 1),
norm=norm,
)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm ) #Do not show unnecessary parts of the colormap
sm._A = []
cb = plt.colorbar(sm,extend="both", label="units")
cb.ax.tick_params(labelsize=10)
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
ax.axis('scaled')
plt.axis('scaled')
# Show
plt.show()
Now the contourplot seems to be shifted realtive to the visible axis. I found a few hints online that suggest, that the "colorbar box automatically eats up space from the axes to which it is attached" Link1 Link2
But i do not really know what i need to do to change this behavior nor do i understand if my issue is related.
Please note, that the part:
ax.axis('scaled')
plt.axis('scaled')
is necessary as i need to keep the aspect ratio exactly like it is in the data set!
Thank you in advance!
Upvotes: 0
Views: 615
Reputation: 339765
You can change the position of ax
(the empty axes with the labels) to the position of ax2
(the axes showing the data) after adding the colorbar via
ax.set_position(ax2.get_position())
Alternatively, create the colorbar by "steeling" the space from both axes,
cb = fig.colorbar(sm,ax=[ax,ax2], extend="both", label="units")
Both solutions are found in the answers to this linked question.
ax.axis('scaled')
ax2.axis('scaled')
Additionally, put the ax
on top if the ax2
, such that the contourf plot does not overlap the axes spines.
# put `ax` on top, to let the contours not overlap the shown axes
ax.set_zorder(2)
ax.patch.set_visible(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=1)
Complete code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab, cm
delta = 0.5
extent = (-3, 4, -4, 3)
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = (Z1 - Z2) * 10
levels = np.arange(-2.0, 1.601, 0.4)
norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# put `ax` on top, to let the contours not overlap the shown axes
ax.set_zorder(2)
ax.patch.set_visible(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=1)
ax2.contourf(X, Y, Z, levels,
cmap=cm.get_cmap(cmap, len(levels) - 1),
norm=norm,
)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm )
sm._A = []
cb = fig.colorbar(sm,ax=[ax,ax2], extend="both", label="units")
cb.ax.tick_params(labelsize=10)
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
ax.axis('scaled')
ax2.axis('scaled')
#ax.set_position(ax2.get_position())
# Show
plt.show()
Upvotes: 1