Search code examples
pythonimagematplotlibplotmatplotlib-widget

updating colorbar without resetting zoom history in matplotlib user interface


I am trying to make a plot in which the color scale is updated on zoom on the base of the visualized data, using a scheme similar to e.g. http://matplotlib.org/examples/event_handling/viewlims.html (also note similar question Matplotlib imshow, dynamically resample based on zoom)

However I met a problem when dealing with the colorbar, after removing it and adding a new one, the zoom history is reset. In my real code, the colorbar update is done at every zoom, as a result back and home buttons in matplotlib plot don't work at all.

This is probably clearer looking at the example code below. What is the reason? Is there a way to prevent this from happening?

The code, stripped from all unnecessary parts looks more or less like this:

#create and plot a test image with colorbar,
#zoom and everything works
import numpy as np
N=100
a=np.random.random((N,N))
plt.figure()
plt.imshow(a,interpolation='none')
plt.colorbar()
plt.show()
#at this point I can zoom and use back and forward buttons as always

#but if I zoom in and then execute the following code, the history is reset and I cannot go back or home any more (the zooming history works in the future, but is reset every time I replace the colorbar):
ax=plt.gca()
im=ax.images[-1]
im.colorbar.remove()
#in real code, color range is updated here
plt.colorbar()

Solution

  • This is difficult, unfortunately, and it also depends a bit on what backend you're using. There are two types of toolbars: toolbar2, which is usually the default, and toolmanager, which will become the default. My solutions will be based around toolbar2, the current default.

    The problem here is that the view history for a figure fig is stored in two cbook.Stack objects (fig.canvas.toolbar._views and fig.canvas.toolbar._positions) that are cleared during updates by fig.canvas.toolbar.update(). There are thus two solutions I can think of easily.

    The first is to copy the two stacks and then restore them:

    import copy
    s = copy.copy( fig.canvas.toolbar._views )
    p = copy.copy( fig.canvas.toolbar._positions )
    ax=plt.gca()
    im=ax.images[-1]
    im.colorbar.remove()
    #in real code, color range is updated here
    plt.colorbar()
    fig.canvas.toolbar._views = s
    fig.canvas.toolbar._positions = p
    

    The second is to remove the update function from your NavigationToolbar2 object. For example:

    fig.canvas.toolbar.update = lambda: None
    

    and then your original code will work without resetting the history:

    ax=plt.gca()
    im=ax.images[-1]
    im.colorbar.remove()
    #in real code, color range is updated here
    plt.colorbar()
    

    For toolmanager, you'll need to look at ToolViewsPositions in backend_tools.