Crosshair cursor utilising matplotlib.backends.backend_wxagg - proposal of adding the attached sample

Hello,

I already opened a thread which was unfortunately misspelled.

In order to help other users finding this subject I correcr that.

After looking at the hints I received - thanks a lot folks! - I created a sample which works to some extend.

Four questions remain:

1. When I close my application I receive a meesage box.
I have to translate the message box from german so in english the text may differ.
Title: python.exe – Error in application
Text: statement “0x0166b07f” points on memory “0x00000000004”. Read could not be executed in memory.
How to avoid that?

2. When I start my application the crosshair cursor moves but the cursors initial position does not disappear until I leave the axes.

3. I there a better way to update the figure. I have to delete the axes object in order to get my colobar refreshed.

4. If this is found to be a useful sample, who decides whether this sample should be addes to the samples of matplotlib?

Please see the sample below.

Cheers Reinhard

# -*- coding: UTF-8 -*-
from numpy import arange, sin, pi

import matplotlib

matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor, SpanSelector
import pylab

import wx

class ContourFrame(wx.Frame):
    '''
        ContourFrame is a sample showing how to add a Matplotlib Figure to a
        wxPython application.
        It should demonstrate how to use a crosshair cursor to select certain lines
        or rows in a contour plot.
        
        This frame contains a vertical box sizer holding
        - The whole content is in sizerMain (m)
        - sizerMain is in posession of sizerVertical (v)
        - in the upper region of sizerVertical resides the Matplotlib figure canvas (C)
          in the lower region FlexGridSizer sizerHorizontal (f)
        - FlexGridSizer sizerHorizontal contains some wx control elements (1, 2, 3, 4, 5, 6)
        
        The sizers are thought to be set as follows
        mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
        m vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv m
        m v v m
        m v CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC v m
        m v CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC v m
        m v CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC v m
        m v v m
        m v ffffffffffffffffffffffffffffffffff v m
        m v f f v m
        m v f 11111111 222222222 33333333333 f v m
        m v f 11111111 222222222 33333333333 f v m
        m v f f v m
        m v f 44444444 555555555 66666666666 f v m
        m v f 44444444 555555555 66666666666 f v m
        m v f f v m
        m v ffffffffffffffffffffffffffffffffff v m
        m vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv m
        mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
    '''
    
    def __init__(self):
        '''
        construktor of frame object
        '''
        # 1. Call the constructor of the parent class
        wx.Frame.__init__(self, None, -1, 'contour sample with cursor') #, size=(655, 630))
        # create the wx status bar
        self.statusbar = self.CreateStatusBar()
        # Divide the status into three field.
        self.statusbar.SetFieldsCount(3)
        self.statusbar.SetStatusWidths([-1, -2, -3])
        self.statusbar.SetStatusText("Current Position:", 0)
        
        # 2. Lets create some data for contouring utilizing pylab functions (stolen from contourf_demo.py)
        delta = 0.025
        x = y = arange(-3.0, 3.01, delta)
        self.X, self.Y = pylab.meshgrid(x, y)
        Z1 = pylab.bivariate_normal(self.X, self.Y, 1.0, 1.0, 0.0, 0.0)
        Z2 = pylab.bivariate_normal(self.X, self.Y, 1.5, 0.5, 1, 1)
        self.Z = 10 * (Z1 - Z2)
        # these values are updated by the sliders
        self.factor = 1.0
        self.addon = 0.0
        
        # 3. All the matplotlib stuff is done in the following lines. In the end we are in possession of
        # a FigureCanvas which we can add to a sizer just like a widget.
        # the matplotlib-figure object
        self.figure = Figure()
        # the matplotlib-axes object is obviously created here
        self.axes = self.figure.add_subplot(111)
        # annotations found in some web-sample
        # Red, Bold, 12pt
        self.axes.set_title('A contour plot generated with contourf', color='r', fontweight='bold', fontsize=12)
        # Green, `smaller' size
        self.axes.set_xlabel('This is the x axis', color='g', fontstyle='oblique', fontsize='greater')
        # Italic
        self.axes.set_ylabel('This is the x axis', color='b',fontstyle='italic')
        # method contourf of the axes objects also stolen from contourf_demo.py
        self.contourf = self.axes.contourf(self.X, self.Y, (self.factor * self.Z + self.addon),
                            cmap=pylab.cm.jet)
                        
        self.colorbar = self.figure.colorbar(self.contourf, orientation='horizontal')
        # create a FigureCanvas which belongs to our frame and contains our figure object
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.canvas.draw()
        self.cursor = Cursor(self.axes, useblit=True, color='red', linewidth=2 )
        # use the Matplotlib bind functionality to bind the cursor move
        self.canvas.mpl_connect('motion_notify_event', self.onmousemove)
        
        # 4. Now we are doing the wxpython user interface
        # the mainpanel holds all widgets
        self.mainpanel = wx.Panel(self, -1)
        # Lets create a control element - sliderMultiply
        self.sliderMultiply = wx.Slider(self.mainpanel, 100, 1.0, -5.0, 5.0, pos=(10, 10), size=(305, -1),
                                style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS )
        self.sliderMultiply.SetTickFreq(5, 1)
        self.sliderMultiplyName = wx.StaticText (self.mainpanel, -1, 'multiply matrix with')
        # and another control element sliderAdd
        self.sliderAdd = wx.Slider(self.mainpanel, 100, 0.0, -10.0, 10.0, pos=(10, 10), size=(305, -1),
                                style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS )
        self.sliderAdd.SetTickFreq(5, 1)
        self.sliderAddName = wx.StaticText (self.mainpanel, -1, 'add matrix with')
        
        # Checkboxes ae used to control the visibility of the crosshair cusor
        self.CheckBoxHorizontalCursor = wx.CheckBox(self.mainpanel, -1, "horizontal Cursor")
        self.CheckBoxHorizontalCursor.SetValue(True)
        self.CheckBoxVerticalCursor = wx.CheckBox(self.mainpanel, -1, "vertical Cursor")
        self.CheckBoxVerticalCursor.SetValue(True)
        
        # Bind the wx GUI events
        # Trigger when a slider or scrollbar change is finished - All sliders and scrollbars are
        # controlled by one bind. This is marvellous but thats the way it works.
        self.Bind(wx.EVT_SCROLL_ENDSCROLL, self.OnScroll)
        self.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxHorizontalCursor, self.CheckBoxHorizontalCursor)
        self.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxVerticalCursor, self.CheckBoxVerticalCursor)
        
        # Now we add the wx controls to a FlexGridSizer named sizerHorizontal
        self.sizerHorizontal = wx.FlexGridSizer(rows=2, cols=3, hgap=0, vgap=0)
        self.sizerHorizontal.Add(self.CheckBoxHorizontalCursor, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.GROW, 10)
        self.sizerHorizontal.Add(self.sliderMultiplyName, 0, wx.ALIGN_CENTER )
        self.sizerHorizontal.Add(self.sliderAddName, 0, wx.ALIGN_CENTER)
        self.sizerHorizontal.Add(self.CheckBoxVerticalCursor, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.GROW, 10)
        self.sizerHorizontal.Add(self.sliderMultiply, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.GROW, 10)
        self.sizerHorizontal.Add(self.sliderAdd, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.GROW, 10)
        # We add the matplotlib canvas and the horizontal sizer to a vertical BoxSizer
        self.sizerVertical = wx.BoxSizer(wx.VERTICAL)
        self.sizerVertical.Add(self.canvas, 0, wx.LEFT | wx.BOTTOM | wx.GROW)
        self.sizerVertical.Add(self.sizerHorizontal, 0, wx.GROW | wx.ALIGN_CENTER_HORIZONTAL)
        # the vertical BoxSizer belongs to the main panel
        self.mainpanel.SetSizer(self.sizerVertical)
        self.sizerVertical.Fit(self.mainpanel)
        self.sizerMain = wx.BoxSizer(wx.VERTICAL)
        # create sizerMain which holds all the stuff and fit our frame
        self.sizerMain.Add(self.sizerVertical)
        self.SetSizer(self.sizerMain)
        self.Fit()

    def OnScroll(self, evt):
        # take the slider value
        self.factor = float(self.sliderMultiply.GetValue())
        self.addon = float(self.sliderAdd.GetValue())
        # clear the figure or delete the axes - I did not find an update for the color bar so I
        # create everything again after clearing or deleting it
        #self.figure.clf()
        self.figure.delaxes(self.axes)
        # Once again do all the axes stuff
        # the matplotlib-axes object is obviously created here
        self.axes = self.figure.add_subplot(111)
        # annotations found in some web-sample
        # Red, Bold, 12pt
        self.axes.set_title('A contour plot generated with contourf', color='r', fontweight='bold', fontsize=12)
        # Green, `smaller' size
        self.axes.set_xlabel('This is the x axis', color='g', fontstyle='oblique', fontsize='greater')
        # Italic
        self.axes.set_ylabel('This is the x axis', color='b',fontstyle='italic')
        self.contour = self.axes.contourf(self.X, self.Y, (self.factor * self.Z + self.addon))
        self.contourf = self.axes.contourf(self.X, self.Y, (self.factor * self.Z + self.addon),
                            cmap=pylab.cm.jet)
        self.colorbar = self.figure.colorbar(self.contourf, orientation='horizontal')
        self.cursor = Cursor(self.axes, useblit=True, color='red', linewidth=2 )
        self.canvas.draw()

    def OnCheckBoxHorizontalCursor(self,event):
        # Show or hide the horizontal cursor
        boolHorizontalCursor = self.CheckBoxHorizontalCursor.GetValue()
        if boolHorizontalCursor == True:
            self.cursor.horizOn = True
        else:
            self.cursor.horizOn = False
        
    def OnCheckBoxVerticalCursor(self,event):
        # Show or hide the vertical cursor
        boolVerticalCursor = self.CheckBoxVerticalCursor.GetValue()
        if boolVerticalCursor == True:
            self.cursor.vertOn = True
        else:
            self.cursor.vertOn = False

    def onmousemove(self,event):
        # the cursor position is given in the wx status bar
        self.figure.gca()
        if event.inaxes:
            x, y = event.xdata, event.ydata
            self.statusbar.SetStatusText("x = %.2f" %x, 1)
            self.statusbar.SetStatusText("y = %.2f" %y, 2)

class App(wx.App):

    def OnInit(self):
        # 'Create the main window and insert the custom frame'
        frame = ContourFrame()
        frame.Show(True)

        return True

app = App(0)
app.MainLoop()