joferkington / mpldatacursor

Interactive "data cursors" (a.k.a. annotation pop-ups) for matplotlib
MIT License
194 stars 47 forks source link

Update values in the cursor when plot changes (without re clicking) #31

Open ajn1985 opened 9 years ago

ajn1985 commented 9 years ago

Hi, First off, thanks a bunch for creating this. :)

I have one question regarding the use of the data cursor:

Say, I have a 10x10x10 data array which I want to display using imshow plot and a slider, where the plot updates when the slider is moved. After I click on the plot at say x=10, y=10, at slider position 1 (where the z value is 1000), I would like the data cursor to remain at the same x,y position but get updated as I move the slider.

Is it possible to do this? If so could you point me in the right direction on how to do so?

Regards, Adithya

joferkington commented 9 years ago

@ajn1985 - Sorry I've taken so long to reply!

There probably is a way to have mpldatacursor do this automatically, but for the moment, it's easiest to update the annotation when you update the image.

At present, it's easiest to re-fire the event that originally displayed the "popup". In the next release, I'll try to have a way of making this a bit easier.

In the meantime, here's a general example:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from mpldatacursor import datacursor

def main():
    data = np.random.random((10, 10, 10))

    fig, ax = plt.subplots()
    explorer = DataExplorer(data, ax)
    updater = DatacursorUpdater(datacursor(axes=ax))
    explorer.callback = updater
    plt.show()

class DataExplorer(object):
    """
    I imagine you have something similar to this class to "scroll" through your 3D array.
    """
    def __init__(self, data, ax, callback=None):
        self.data = data
        self.ax = ax
        self.im = self._plot_image()
        self.callback = callback

        self.sliderax = self.ax.figure.add_axes([0.27, 0.01, 0.5, 0.03])
        self.slider = Slider(self.sliderax, 'Index', 0, self.data.shape[2],
                             valinit=0)
        self.slider.on_changed(self.slider_update)

    def _plot_image(self):
        im = self.ax.imshow(self.data[:,:,0], interpolation='none', cmap='gray',
                            vmin=self.data.min(), vmax=self.data.max())
        return im

    def slider_update(self, val):
        self.im.set_data(self.data[:,:,int(val)])
        if self.callback is not None:
            self.callback()
        self.ax.figure.canvas.draw()

class DatacursorUpdater(object):
    def __init__(self, dc):
        self.orig_formatter = dc.formatter
        dc.formatter = self.formatter
        self.dc = dc
        self.last_event = None

    def formatter(self, **kwargs):
        """The sole purpose of this is to hold onto the last pick event."""
        self.last_event = kwargs['event']
        return self.orig_formatter(**kwargs)

    def __call__(self):
        """Update the datacursor's displayed values by re-firing the last pick
        event."""
        if self.last_event is None:
            return
        canvas = self.last_event.artist.figure.canvas
        canvas.callbacks.process('pick_event', self.last_event)

main()