Пример #1
0
    def __init__(self, parent, messenger=None,
                 size=(6.00,3.70), dpi=96, **kwds):

        self.is_macosx = False
        if os.name == 'posix':
            if os.uname()[0] == 'Darwin': self.is_macosx = True

        matplotlib.rc('axes', axisbelow=True)
        matplotlib.rc('lines', linewidth=2)
        matplotlib.rc('xtick',  labelsize=11, color='k')
        matplotlib.rc('ytick',  labelsize=11, color='k')
        matplotlib.rc('grid',  linewidth=0.5, linestyle='-')

        self.messenger = messenger
        if (messenger is None): self.messenger = self.__def_messenger

        self.conf = ImageConfig()
        self.cursor_mode='cursor'

        self.launch_dir  = os.getcwd()
        self.mouse_uptime= time.time()
        self.last_event_button = None

        self.view_lim  = (None,None,None,None)
        self.zoom_lims = [self.view_lim]
        self.old_zoomdc= (None,(0,0),(0,0))

        self.parent    = parent

        self._yfmt = '%.4f'
        self._xfmt = '%.4f'

        self.figsize = size
        self.dpi     = dpi
        self.__BuildPanel(**kwds)
Пример #2
0
class ImagePanel(wx.Panel):
    """
    MatPlotlib Image on a wx.Panel, suitable for embedding
    in any wx.Frame.

    This does provide a right-click popup
    menu for configuration, zooming, saving an image of the
    figure, and Ctrl-C for copy-image-to-clipboard.

    For more features, see PlotFrame, which embeds a PlotPanel
    and also provides, a Menu, StatusBar, and Printing support.
    """

    help_msg =  """MPlot quick help:

 Left-Click:   to display X,Y coordinates
 Left-Drag:    to zoom in on plot region
 Right-Click:  display popup menu with choices:
                Zoom out 1 level       (that is, to previous view)
                Zoom all the way out   (to full data range)
                --------------------
                Configure Plot
                Save Plot Image

Also, these key bindings can be used
(For Mac OSX, replace 'Ctrl' with 'Apple'):

  Ctrl-S:     save plot image to file
  Ctrl-C:     copy plot image to clipboard
  Ctrl-K:     Configure Plot 
  Ctrl-Q:     quit

"""
    about_msg =  """MPlot  version 0.7
Matt Newville <*****@*****.**>"""

    def __init__(self, parent, messenger=None,
                 size=(6.00,3.70), dpi=96, **kwds):

        self.is_macosx = False
        if os.name == 'posix':
            if os.uname()[0] == 'Darwin': self.is_macosx = True

        matplotlib.rc('axes', axisbelow=True)
        matplotlib.rc('lines', linewidth=2)
        matplotlib.rc('xtick',  labelsize=11, color='k')
        matplotlib.rc('ytick',  labelsize=11, color='k')
        matplotlib.rc('grid',  linewidth=0.5, linestyle='-')

        self.messenger = messenger
        if (messenger is None): self.messenger = self.__def_messenger

        self.conf = ImageConfig()
        self.cursor_mode='cursor'

        self.launch_dir  = os.getcwd()
        self.mouse_uptime= time.time()
        self.last_event_button = None

        self.view_lim  = (None,None,None,None)
        self.zoom_lims = [self.view_lim]
        self.old_zoomdc= (None,(0,0),(0,0))

        self.parent    = parent

        self._yfmt = '%.4f'
        self._xfmt = '%.4f'

        self.figsize = size
        self.dpi     = dpi
        self.__BuildPanel(**kwds)

    def display(self,data,**kw):
        """
        display (that is, create a new image display on the current frame
        """
        print 'display: '
        c = self.axes.imshow(data)
        print c.cmap
        
    def overlay(self,data,**kw):
        print ' overlay ? '
        self.axes.imshow(data)
        

    def clear(self):
        """ clear plot """
        print 'clear'

    def unzoom_all(self,event=None):
        """ zoom out full data range """
        print 'unzoom all'
        self.unzoom(event)
        
    def unzoom(self,event=None):
        """ zoom out 1 level, or to full data range """
        print 'unzoom '
        
    def set_title(self,s):
        "set plot title"
        self.conf.title = s
        self.conf.relabel()
        
    def set_xlabel(self,s):
        "set plot xlabel"
        self.conf.xlabel = s
        self.conf.relabel()

    def set_ylabel(self,s):
        "set plot ylabel"
        self.conf.ylabel = s
        self.conf.relabel()

    def write_message(self,s,panel=0):
        """ write message to message handler (possibly going to GUI statusbar)"""
        self.messenger(s, panel=panel)

    def save_figure(self,event=None):
        """ save figure image to file"""
        file_choices = "PNG (*.png)|*.png|EPS (*.eps)|*.eps" 
        
        dlg = wx.FileDialog(self, message='Save Plot Figure as...',
                            defaultDir = os.getcwd(),
                            defaultFile='plot.png',
                            wildcard=file_choices,
                            style=wx.SAVE|wx.CHANGE_DIR)

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.canvas.print_figure(path,dpi=300)
            if (path.find(self.launch_dir) ==  0):
                path = path[len(self.launch_dir)+1:]
            self.write_message('Saved plot to %s' % path)

    def configure(self,event=None):
        try:
            self.win_config.Raise()
        except:
            self.win_config = GUIImageConfig(self.conf)
    ####
    ## create GUI 
    ####
    def __BuildPanel(self, **kwds):
        """ builds basic GUI panel and popup menu"""
        wx.Panel.__init__(self, self.parent, -1, **kwds)

        self.fig   = Figure(self.figsize,dpi=self.dpi)
        self.axes  = self.fig.add_axes([0.15,0.15,0.75,0.75],
                                       axisbg='#FEFEFE')
                                      
        self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
        self.fig.set_facecolor('#FBFBF8')

        self.conf.fig   = self.fig
        self.conf.canvas= self.canvas

        self.canvas.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))

        # This way of adding to sizer allows resizing
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 2, wx.LEFT|wx.TOP|wx.BOTTOM|wx.EXPAND,0)
        self.SetAutoLayout(True)
        self.SetSizer(sizer)
        self.Fit()

        # define zoom box properties
        self.zoombrush = wx.Brush('#333333',  wx.TRANSPARENT)
        self.zoompen   = wx.Pen('#FFAA99', 2, wx.LONG_DASH)

        # use matplotlib events
        self.canvas.mpl_connect("motion_notify_event",  self.__onMouseMotionEvent)
        self.canvas.mpl_connect("button_press_event",   self.__onMouseButtonEvent)
        self.canvas.mpl_connect("button_release_event", self.__onMouseButtonEvent)
        self.canvas.mpl_connect("key_press_event",      self.__onKeyEvent)

        # build pop-up menu for right-click display
        self.popup_unzoom_all = wx.NewId()        
        self.popup_unzoom_one = wx.NewId()
        self.popup_config     = wx.NewId()
        self.popup_save   = wx.NewId()        
        self.popup_menu = wx.Menu()
        self.popup_menu.Append(self.popup_unzoom_one, 'Zoom out 1 level')
        self.popup_menu.Append(self.popup_unzoom_all, 'Zoom all the way out')
        self.popup_menu.AppendSeparator()
        self.popup_menu.Append(self.popup_config,'Configure Plot')
        self.popup_menu.Append(self.popup_save,  'Save Plot Image')
        
        self.Bind(wx.EVT_MENU, self.unzoom,     id=self.popup_unzoom_one)
        self.Bind(wx.EVT_MENU, self.unzoom_all, id=self.popup_unzoom_all)
        self.Bind(wx.EVT_MENU, self.save_figure,id=self.popup_save)
        self.Bind(wx.EVT_MENU, self.configure,  id=self.popup_config)

    ####
    ##
    ## GUI events
    ##
    ####
    def onLeftDown(self,event=None):
        """ left button down: report x,y coords, start zooming mode"""
        if event == None: return
        print 'Left Down', dir(event)
        # print dir(event.guiEvent)
        self.conf.zoom_x = event.x
        self.conf.zoom_y = event.y
        if (event.inaxes != None):
            self.conf.zoom_init = (event.xdata, event.ydata)
            fmt = "X,Y= %s, %s" % (self._xfmt, self._yfmt)
            self.write_message(fmt % (event.xdata,event.ydata), panel=1)
        else:
            self.conf.zoom_init = self.axes.transData.inverted().transform((event.x, event.y))

        self.cursor_mode = 'zoom'
        self.__drawZoombox(self.old_zoomdc)
        self.old_zoomdc = (None, (0,0),(0,0))                                  

    def onLeftUp(self,event=None):
        """ left button up: zoom in on selected region?? """
        print ' left up ', event
        if event == None: return        
        dx = abs(self.conf.zoom_x - event.x)
        dy = abs(self.conf.zoom_y - event.y)
        t0 = time.time()
        if ((dx > 6) and (dy > 6) and (t0-self.mouse_uptime)>0.1 and
            self.cursor_mode == 'zoom'):
            self.mouse_uptime = t0
            if (event.inaxes != None):
                _end = (event.xdata,event.ydata)
            else: # allows zooming in to go slightly out of range....
                _end = self.axes.transData.inverted().transform((event.x, event.y))
            try:
                _ini = self.conf.zoom_init
                _lim = (min(_ini[0],_end[0]),max(_ini[0],_end[0]),
                        min(_ini[1],_end[1]),max(_ini[1],_end[1]))

                self.set_xylims(_lim, autoscale=False)
                self.zoom_lims.append(self.view_lim)
                self.view_lim = _lim
                txt = 'zoom level %i ' % (len(self.zoom_lims)-1)
                
                self.write_message(txt)
            except:
                self.write_message("Cannot Zoom")
        self.old_zoomdc = (None,(0,0),(0,0))
        self.cursor_mode = 'cursor'
        self.canvas.draw()

    def onRightDown(self,event=None):
        """ right button down: show pop-up"""
        if event == None: return      
        self.cursor_mode = 'cursor'
        # note that the matplotlib event location have to be converted
        # back to the wxWindows event location...
        # this undoes what happens in FigureCanvasWx.wrapper(event)
        location = wx.Point(event.x, self.fig.bbox.height()-event.y)
        self.PopupMenu(self.popup_menu,location)

    def onRightUp(self,event=None):
        """ right button up: put back to cursor mode"""
        self.cursor_mode = 'cursor'
        

    ####
    ##
    ## private methods
    ##
    ####
    def __def_messenger(self,s,panel=0):
        """ default, generic messenger: write to stdout"""
        sys.stdout.write(s)


    def __date_format(self,x):
        """ formatter for date x-data. primitive, and probably needs
        improvement, following matplotlib's date methods.        

        """
        span = self.axes.xaxis.get_view_interval().span()
        ticks = self.axes.xaxis.get_major_locator()()
        fmt = "%m/%d "                        

        if   span < 1800:     fmt = "%I%p \n%M:%S"
        elif span < 86400*5:  fmt = "%m/%d \n%H:%M"
        elif span < 86400*20: fmt = "%m/%d"
        # print 'date formatter  span: ', span, fmt
        s = time.strftime(fmt,time.localtime(x))
        return s
        
    def __xformatter(self,x,pos):
        " x-axis formatter "
        if self.use_dates:
            return self.__date_format(x)
        else:
            return self.__format(x,type='x')
    
    def __yformatter(self,y,pos):
        " y-axis formatter "        
        return self.__format(y,type='y')

    def __format(self, x, type='x'):
        """ home built tick formatter to use with FuncFormatter():
        x     value to be formatted
        type  'x' or 'y' to set which list of ticks to get

        also sets self._yfmt/self._xfmt for statusbar
        """
        fmt,v = '%1.5g','%1.5g'
        if type == 'y':
            ax = self.axes.yaxis
        else:
            ax = self.axes.xaxis
            
        try:
            dtick = 0.1 * ax.get_view_interval().span()
        except:
            dtick = 0.2
        try:
            ticks = ax.get_major_locator()()
            dtick = abs(ticks[1] - ticks[0])
        except:
            pass
        # print ' tick ' , type, dtick, ' -> ', 
        if   dtick > 99999:     fmt,v = ('%1.6e', '%1.7g')
        elif dtick > 0.99:      fmt,v = ('%1.0f', '%1.2f')
        elif dtick > 0.099:     fmt,v = ('%1.1f', '%1.3f')
        elif dtick > 0.0099:    fmt,v = ('%1.2f', '%1.4f')
        elif dtick > 0.00099:   fmt,v = ('%1.3f', '%1.5f')
        elif dtick > 0.000099:  fmt,v = ('%1.4f', '%1.6e')
        elif dtick > 0.0000099: fmt,v = ('%1.5f', '%1.6e')


        s =  fmt % x
        s.strip()
        s = s.replace('+', '')
        while s.find('e0')>0: s = s.replace('e0','e')
        while s.find('-0')>0: s = s.replace('-0','-')
        if type == 'y': self._yfmt = v
        if type == 'x': self._xfmt = v
        return s

    def __drawZoombox(self,dc):
        """ system-dependent hack to call wx.ClientDC.DrawRectangle
        with the right arguments"""
        if dc[0] == None: return
        pos  = dc[1]
        size = dc[2]
        dc[0].DrawRectangle(pos[0],pos[1],size[0],size[1])

        return (None, (0,0),(0,0))

    def __onKeyEvent(self,event=None):
        """ handles key events on canvas
        """
        if event == None: return
        key = event.guiEvent.GetKeyCode()
        if (key < wx.WXK_SPACE or  key > 255):  return
        mod  = event.guiEvent.ControlDown()
        ckey = chr(key)
        if self.is_macosx: mod = event.guiEvent.MetaDown()
        if (mod and ckey=='C'): self.canvas.Copy_to_Clipboard(event)
        if (mod and ckey=='S'): self.save_figure(event)
        if (mod and ckey=='K'): self.configure(event)
        if (mod and ckey=='Z'): self.unzoom_all(event)
        if (mod and ckey=='P'): self.canvas.Printer_Print(event)
        
    def __onMouseButtonEvent(self,event=None):
        """ general mouse press/release events. Here, event is
        a MplEvent from matplotlib.  This routine just dispatches
        to the appropriate onLeftDown, onLeftUp, onRightDown, onRightUp....
        methods.
        """
        if event == None: return
        button = event.button or self.last_event_button
        if (button == None): button = 1

        if button == 1:
            if event.name  == 'button_press_event':
                self.onLeftDown(event)
            elif event.name  == 'button_release_event':
                self.onLeftUp(event)
        elif button == 3:
            if event.name  == 'button_press_event':
                self.onRightDown(event)
            elif event.name  == 'button_release_event':
                self.onRightUp(event)
        self.last_event_button = button

    def __onMouseMotionEvent(self, event=None):
        """Draw a cursor over the axes"""
        if event == None: return
        if (self.cursor_mode != 'zoom'): return            
        try:
            x, y  = event.x, event.y
        except:
            self.cursor_mode == 'cursor'
            retrun
        self.__drawZoombox(self.old_zoomdc)
        self.old_zoomdc = (None, (0,0),(0,0))            

        x0     = min(x, self.conf.zoom_x)
        ymax   = max(y, self.conf.zoom_y)
        width  = abs(x -self.conf.zoom_x)
        height = abs(y -self.conf.zoom_y)
        y0     = self.canvas.figure.bbox.height() - ymax

        zdc = wx.ClientDC(self.canvas)
        zdc.SetBrush(self.zoombrush)
        zdc.SetPen(self.zoompen)
        zdc.SetLogicalFunction(wx.XOR)
        self.old_zoomdc = (zdc, (x0, y0), (width, height))
        self.__drawZoombox(self.old_zoomdc)
Пример #3
0
class ImagePanel(wx.Panel):
    """
    MatPlotlib 2D plot as a wx.Panel, suitable for embedding
    in any wx.Frame.   This does provide a right-click popup
    menu for configuration, zooming, saving an image of the
    figure, and Ctrl-C for copy-image-to-clipboard.

    For more features, see PlotFrame, which embeds a PlotPanel
    and also provides, a Menu, StatusBar, and Printing support.
    """

    help_msg =  """MPlot quick help:

 Left-Click:   to display X,Y coordinates
 Left-Drag:    to zoom in on plot region
 Right-Click:  display popup menu with choices:
                Zoom out 1 level       (that is, to previous view)
                Zoom all the way out   (to full data range)
                --------------------
                Configure Plot
                Save Plot Image

Also, these key bindings can be used
(For Mac OSX, replace 'Ctrl' with 'Apple'):

  Ctrl-S:     save plot image to file
  Ctrl-C:     copy plot image to clipboard
  Ctrl-K:     Configure Plot 
  Ctrl-Q:     quit

"""
    about_msg =  """MPlot  version 0.7
Matt Newville <*****@*****.**>"""


    def __init__(self, parent, messenger=None,
                 size=(6.00,3.70), dpi=96, **kwds):

        self.is_macosx = False
        if os.name == 'posix':
            if os.uname()[0] == 'Darwin': self.is_macosx = True

        matplotlib.rc('axes', axisbelow=True)
        matplotlib.rc('lines', linewidth=2)
        matplotlib.rc('xtick',  labelsize=11, color='k')
        matplotlib.rc('ytick',  labelsize=11, color='k')
        matplotlib.rc('grid',  linewidth=0.5, linestyle='-')

        self.messenger = messenger
        if (messenger is None): self.messenger = self.__def_messenger

        self.conf = ImageConfig()
        self.cursor_mode='cursor'
        self.win_config = ImageGUIConfig(conf=self.conf)

        self._yfmt = '%.4f'
        self._xfmt = '%.4f'
        self.use_dates = False
        self.ylog_scale = False
        self.launch_dir  = os.getcwd()
        self.mouse_uptime= time.time()
        self.last_event_button = None

        self.view_lim  = (None,None,None,None)
        self.zoom_lims = [self.view_lim]
        self.old_zoomdc= (None,(0,0),(0,0))

        self.parent    = parent

        self.figsize = size
        self.dpi     = dpi
        self.__BuildPanel(**kwds)

    def display(self,data,x=None,y=None,**kw):
        """
        display (that is, create a new image display on the current frame
        """
        print 'display! '
        self.axes.cla()
        self.conf.ntraces  = 0
        self.data_range = [0,data.shape[0], 0, data.shape[1]]
        if x is not None: self.data_range[:1] = [min(x),max(x)]
        if y is not None: self.data_range[2:] = [min(y),max(y)]

        scale =  1.0*max(data.ravel())
        d = data / scale
        cnf = self.conf
        c = self.axes.imshow(d,cmap=self.win_config.cmap, interpolation=self.win_config.interp)


        # c = self.axes.pcolor(d,cmap=colormap.jet) # , interpolation='nearest')
        print c.cmap, dir(c.cmap)

    def plot(self,xdata,ydata, label=None, dy=None,
             color=None,  style =None, linewidth=None,
             marker=None,   markersize=None,
             use_dates=False, ylog_scale=False, grid=None,
             title=None,  xlabel=None, ylabel=None,  **kw):
        """
        plot (that is, create a newplot: clear, then oplot)
        """

        self.axes.cla()
        self.conf.ntraces  = 0
        self.data_range    = [min(xdata),max(xdata),
                              min(ydata),max(ydata)]
        if xlabel != None:   self.set_xlabel(xlabel)
        if ylabel != None:   self.set_ylabel(ylabel)            
        if title  != None:   self.set_title(title)
        if use_dates !=None: self.use_dates  = use_dates
        if ylog_scale !=None: self.ylog_scale = ylog_scale

        if grid: self.conf.show_grid = grid
        
        return self.oplot(xdata,ydata,label=label,
                          color=color,style=style,
                          linewidth=linewidth,dy=dy,
                          marker=marker, markersize=markersize,  **kw)
        
    def oplot(self,xdata,ydata, label=None,color=None,style=None,
              linewidth=None,marker=None,markersize=None,dy=None,
              autoscale=True, refresh=True, yaxis='left', **kw):
        """ basic plot method, overplotting any existing plot """
        # set y scale to log/linear
        yscale = 'linear'
        if (self.ylog_scale and min(ydata) > 0):  yscale = 'log'
        self.axes.set_yscale(yscale, basey=10)

        if dy is None:
            _lines = self.axes.plot(xdata,ydata)
        else:
            l1,l2 = self.axes.errorbar(xdata,ydata,yerr=dy)
            _lines = [l1]
            for i in l2: _lines.append(i)

#        print 'determine self.data_range ', self.data_range, type(self.data_range)
#        print type(xdata), type(ydata), xdata.shape, ydata.shape
        
        self.data_range    = [min((self.data_range[0],min(xdata))),
                              max((self.data_range[1],max(xdata))),
                              min((self.data_range[2],min(ydata))),
                              max((self.data_range[3],max(ydata)))]

        cnf  = self.conf
        cnf.ntraces = cnf.ntraces + 1
        n = cnf.ntraces
        if label == None:   label = 'trace %i' % (n)
        cnf.set_trace_label(label,trace=n)
        cnf.lines[n-1] = _lines
        
        if color:            cnf.set_trace_color(color,trace=n)
        if style:            cnf.set_trace_style(style,trace=n)
        if marker:           cnf.set_trace_marker(marker,trace=n)
        if linewidth!=None:  cnf.set_trace_linewidth(linewidth,trace=n)        
        if markersize!=None: cnf.set_trace_markersize(markersize,trace=n)
        
        self.axes.yaxis.set_major_formatter(FuncFormatter(self.__yformatter))
        self.axes.xaxis.set_major_formatter(FuncFormatter(self.__xformatter))

        xa = self.axes.xaxis
        if (refresh):
            cnf.refresh_trace(n)
            cnf.relabel()

        if (autoscale):
            self.axes.autoscale_view()
            self.view_lim = (None,None,None,None)
            self.zoom_lims = [self.view_lim]
        if (self.conf.show_grid):
            # I'm sure there's a better way...
            for i in self.axes.get_xgridlines()+self.axes.get_ygridlines():
                i.set_color(self.conf.grid_color)
            self.axes.grid(True)
        
        self.redraw()
        return _lines

    def get_xylims(self):
        xx = self.axes.get_xlim()
        yy = self.axes.get_ylim()
        return (xx,yy)

    def set_xylims(self, xyrange,autoscale=True):
        """ update xy limits of a plot, as used with .update_line() """
        print 'set xylims ! ', xyrange, autoscale
        try:
            self.axes.set_xlim((xyrange[0],xyrange[1]),emit=True)
            self.axes.set_ylim((xyrange[2],xyrange[3]),emit=True)
            self.axes.update_datalim(((xyrange[0],xyrange[2]),
                                      (xyrange[1],xyrange[3])))
        except:
            autoscale = True

        if autoscale: self.axes.autoscale_view()            

    def redraw(self):
        self.canvas.draw()
        
    def update_line(self,trace,xdata,ydata):
        """ update a single trace, for faster redraw """
        self.conf.get_mpl_line(trace).set_data(xdata,ydata)
        # this effectively defeats zooming, which gets ugly in this fast-mode anyway.
        self.cursor_mode = 'cursor'
        self.redraw()

    def clear(self):
        """ clear plot """
        self.axes.cla()
        self.conf.ntraces  = 0
        self.conf.xlabel = ''
        self.conf.ylabel = ''
        self.conf.title  = ''

    def unzoom_all(self,event=None):
        """ zoom out full data range """
        self.zoom_lims = [(None,None,None,None)]

        self.set_xylims(self.data_range,autoscale=False)
        self.unzoom(event)
        
    def unzoom(self,event=None):
        """ zoom out 1 level, or to full data range """
        try:
            lims = self.zoom_lims.pop()
            if (len( self.zoom_lims ) < 1 or
                lims == (None,None,None,None)):
                lims = self.zoom_lims(pop)
                self.axes.autoscale_view()
            else:
                self.axes.set_xlim(lims[:2])
                self.axes.set_ylim(lims[2:])
        except:
            lims = (None,None,None,None)
            self.axes.autoscale_view()

        self.view_lim = lims
        self.old_zoomdc = (None,(0,0),(0,0))
        if len(self.zoom_lims)==0:
            txt = ''
        else:
            txt = 'zoom level %i' % (len(self.zoom_lims))
        self.write_message(txt)
        self.redraw()
        
    def set_title(self,s):
        "set plot title"
        self.conf.title = s
        self.conf.relabel()
        
    def set_xlabel(self,s):
        "set plot xlabel"
        self.conf.xlabel = s
        self.conf.relabel()

    def set_ylabel(self,s):
        "set plot ylabel"
        self.conf.ylabel = s
        self.conf.relabel()

    def write_message(self,s,panel=0):
        """ write message to message handler (possibly going to GUI statusbar)"""
        self.messenger(s, panel=panel)

    def save_figure(self,event=None):
        """ save figure image to file"""
        file_choices = "PNG (*.png)|*.png|EPS (*.eps)|*.eps" 
        
        dlg = wx.FileDialog(self, message='Save Plot Figure as...',
                            defaultDir = os.getcwd(),
                            defaultFile='plot.png',
                            wildcard=file_choices,
                            style=wx.SAVE|wx.CHANGE_DIR)

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.canvas.print_figure(path,dpi=300)
            if (path.find(self.launch_dir) ==  0):
                path = path[len(self.launch_dir)+1:]
            self.write_message('Saved plot to %s' % path)

#     def copy_to_clipboard(self, event=None):
#         "copy image to system clipboard"
#         bmp_obj = wx.BitmapDataObject()
#         bmp_obj.SetBitmap(self.canvas.bitmap)
#         wx.TheClipboard.Open()
#         wx.TheClipboard.SetData(bmp_obj)
#         wx.TheClipboard.Close()
#         self.write_message('copied plot image to clipboard')        

    def configure(self,event=None):
        try:
            self.win_config.Raise()
        except:
            self.win_config = GUIConfig(self.conf)

    ####
    ##
    ## create GUI 
    ##
    ####
    def __BuildPanel(self, **kwds):
        """ builds basic GUI panel and popup menu"""
        wx.Panel.__init__(self, self.parent, -1, **kwds)

        self.fig   = Figure(self.figsize,dpi=self.dpi)
        self.axes  = self.fig.add_axes([0.08,0.08,0.90,0.90],
                                       axisbg='#FEFEFE')
                                      
        self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
        self.fig.set_facecolor('#FBFBF8')

        self.conf.axes  = self.axes
        self.conf.fig   = self.fig
        self.conf.canvas= self.canvas

        self.canvas.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))

        # overwrite ScalarFormatter from ticker.py here:
        self.axes.yaxis.set_major_formatter(FuncFormatter(self.__yformatter))
        self.axes.xaxis.set_major_formatter(FuncFormatter(self.__xformatter))

        # This way of adding to sizer allows resizing
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 2, wx.LEFT|wx.TOP|wx.BOTTOM|wx.EXPAND,0)
        self.SetAutoLayout(True)
        self.SetSizer(sizer)
        self.Fit()

        # define zoom box properties
        self.zoombrush = wx.Brush('#333333',  wx.TRANSPARENT)
        self.zoompen   = wx.Pen('#FFAA99', 2, wx.LONG_DASH)

        # use matplotlib events
        self.canvas.mpl_connect("motion_notify_event",  self.__onMouseMotionEvent)
        self.canvas.mpl_connect("button_press_event",   self.__onMouseButtonEvent)
        self.canvas.mpl_connect("button_release_event", self.__onMouseButtonEvent)
        self.canvas.mpl_connect("key_press_event",      self.__onKeyEvent)

        # build pop-up menu for right-click display
        self.popup_unzoom_all = wx.NewId()        
        self.popup_unzoom_one = wx.NewId()
        self.popup_config     = wx.NewId()
        self.popup_save   = wx.NewId()        
        self.popup_menu = wx.Menu()
        self.popup_menu.Append(self.popup_unzoom_one, 'Zoom out 1 level')
        self.popup_menu.Append(self.popup_unzoom_all, 'Zoom all the way out')
        self.popup_menu.AppendSeparator()
        self.popup_menu.Append(self.popup_config,'Configure Plot')
        self.popup_menu.Append(self.popup_save,  'Save Plot Image')
        
        self.Bind(wx.EVT_MENU, self.unzoom,     id=self.popup_unzoom_one)
        self.Bind(wx.EVT_MENU, self.unzoom_all, id=self.popup_unzoom_all)
        self.Bind(wx.EVT_MENU, self.save_figure,id=self.popup_save)
        self.Bind(wx.EVT_MENU, self.configure,  id=self.popup_config)

    ####
    ##
    ## GUI events
    ##
    ####
    def onLeftDown(self,event=None):
        """ left button down: report x,y coords, start zooming mode"""
        if event == None: return
        # print 'Left Down', dir(event)
        # print dir(event.guiEvent)
        self.conf.zoom_x = event.x
        self.conf.zoom_y = event.y
        if (event.inaxes != None):
            self.conf.zoom_init = (event.xdata, event.ydata)
            fmt = "X,Y= %s, %s" % (self._xfmt, self._yfmt)
            self.write_message(fmt % (event.xdata,event.ydata), panel=1)
        else:
            self.conf.zoom_init = self.axes.transData.inverted().transform((event.x, event.y))

        print 'Left Down ', self.conf.zoom_init, event.x, event.xdata
        self.cursor_mode = 'zoom'
        self.__drawZoombox(self.old_zoomdc)
        self.old_zoomdc = (None, (0,0),(0,0))                                  

    def onLeftUp(self,event=None):
        """ left button up: zoom in on selected region?? """
        if event == None: return        
        dx = abs(self.conf.zoom_x - event.x)
        dy = abs(self.conf.zoom_y - event.y)
        print ' left up ', event.x, event.y, dx, dy
        t0 = time.time()
        if ((dx > 6) and (dy > 6) and (t0-self.mouse_uptime)>0.1 and
            self.cursor_mode == 'zoom'):
            self.mouse_uptime = t0
            if (event.inaxes != None):
                _end = (event.xdata,event.ydata)
            else: # allows zooming in to go slightly out of range....
                _end = self.axes.transData.inverted().transform((event.x, event.y))
            try:
                _ini = self.conf.zoom_init
                _lim = (min(_ini[0],_end[0]),max(_ini[0],_end[0]),
                        min(_ini[1],_end[1]),max(_ini[1],_end[1]))

                print ' zoom:: ', _ini, _end,
                print ' zoom:: ', _lim
                self.set_xylims(_lim, autoscale=False)
                self.zoom_lims.append(self.view_lim)
                self.view_lim = _lim
                txt = 'zoom level %i ' % (len(self.zoom_lims)-1)
                
                self.write_message(txt)

                self.old_zoomdc = (None,(0,0),(0,0))
                print 'Now going to Redraw '
                self.redraw()

            except:
                self.write_message("Cannot Zoom")
        self.cursor_mode = 'cursor'

    def onRightDown(self,event=None):
        """ right button down: show pop-up"""
        if event == None: return      
        self.cursor_mode = 'cursor'
        # note that the matplotlib event location have to be converted
        # back to the wxWindows event location...
        # this undoes what happens in FigureCanvasWx.wrapper(event)
        location = wx.Point(event.x, self.fig.bbox.height-event.y)
        self.PopupMenu(self.popup_menu,location)

    def onRightUp(self,event=None):
        """ right button up: put back to cursor mode"""
        self.cursor_mode = 'cursor'
        
    ####
    ##
    ## private methods
    ##
    ####
    def __def_messenger(self,s,panel=0):
        """ default, generic messenger: write to stdout"""
        sys.stdout.write(s)

    def __date_format(self,x):
        """ formatter for date x-data. primitive, and probably needs
        improvement, following matplotlib's date methods.        

        """
        span = self.axes.xaxis.get_view_interval().span()
        ticks = self.axes.xaxis.get_major_locator()()
        fmt = "%m/%d "                        

        if   span < 1800:     fmt = "%I%p \n%M:%S"
        elif span < 86400*5:  fmt = "%m/%d \n%H:%M"
        elif span < 86400*20: fmt = "%m/%d"
        # print 'date formatter  span: ', span, fmt
        s = time.strftime(fmt,time.localtime(x))
        return s
        
    def __xformatter(self,x,pos):
        " x-axis formatter "
        if self.use_dates:
            return self.__date_format(x)
        else:
            return self.__format(x,type='x')
    
    def __yformatter(self,y,pos):
        " y-axis formatter "        
        return self.__format(y,type='y')

    def __format(self, x, type='x'):
        """ home built tick formatter to use with FuncFormatter():
        x     value to be formatted
        type  'x' or 'y' to set which list of ticks to get

        also sets self._yfmt/self._xfmt for statusbar
        """
        fmt,v = '%1.5g','%1.5g'
        if type == 'y':
            ax = self.axes.yaxis
        else:
            ax = self.axes.xaxis
            
        try:
            dtick = 0.1 * ax.get_view_interval().span()
        except:
            dtick = 0.2
        try:
            ticks = ax.get_major_locator()()
            dtick = abs(ticks[1] - ticks[0])
        except:
            pass
        # print ' tick ' , type, dtick, ' -> ', 
        if   dtick > 99999:     fmt,v = ('%1.6e', '%1.7g')
        elif dtick > 0.99:      fmt,v = ('%1.0f', '%1.2f')
        elif dtick > 0.099:     fmt,v = ('%1.1f', '%1.3f')
        elif dtick > 0.0099:    fmt,v = ('%1.2f', '%1.4f')
        elif dtick > 0.00099:   fmt,v = ('%1.3f', '%1.5f')
        elif dtick > 0.000099:  fmt,v = ('%1.4f', '%1.6e')
        elif dtick > 0.0000099: fmt,v = ('%1.5f', '%1.6e')


        s =  fmt % x
        s.strip()
        s = s.replace('+', '')
        while s.find('e0')>0: s = s.replace('e0','e')
        while s.find('-0')>0: s = s.replace('-0','-')
        if type == 'y': self._yfmt = v
        if type == 'x': self._xfmt = v
        return s

    def __drawZoombox(self,dc):
        """ system-dependent hack to call wx.ClientDC.DrawRectangle
        with the right arguments"""
        if dc[0] == None: return
        pos  = dc[1]
        size = dc[2]
        dc[0].DrawRectangle(pos[0],pos[1],size[0],size[1])

        return (None, (0,0),(0,0))

    def __onKeyEvent(self,event=None):
        """ handles key events on canvas
        """
        if event == None: return
        key = event.guiEvent.GetKeyCode()
        if (key < wx.WXK_SPACE or  key > 255):  return
        mod  = event.guiEvent.ControlDown()
        ckey = chr(key)
        if self.is_macosx: mod = event.guiEvent.MetaDown()
        if (mod and ckey=='C'): self.canvas.Copy_to_Clipboard(event)
        if (mod and ckey=='S'): self.save_figure(event)
        if (mod and ckey=='K'): self.configure(event)
        if (mod and ckey=='Z'): self.unzoom_all(event)
        if (mod and ckey=='P'): self.canvas.Printer_Print(event)
        
    def __onMouseButtonEvent(self,event=None):
        """ general mouse press/release events. Here, event is
        a MplEvent from matplotlib.  This routine just dispatches
        to the appropriate onLeftDown, onLeftUp, onRightDown, onRightUp....
        methods.
        """
        if event == None: return
        button = event.button or self.last_event_button
        if (button == None): button = 1

        if button == 1:
            if event.name  == 'button_press_event':
                self.onLeftDown(event)
            elif event.name  == 'button_release_event':
                self.onLeftUp(event)
        elif button == 3:
            if event.name  == 'button_press_event':
                self.onRightDown(event)
            elif event.name  == 'button_release_event':
                self.onRightUp(event)
        self.last_event_button = button

    def __onMouseMotionEvent(self, event=None):
        """Draw a cursor over the axes"""
        if event == None: return
        if (self.cursor_mode != 'zoom'): return            
        try:
            x, y  = event.x, event.y
        except:
            self.cursor_mode == 'cursor'
            return
        self.__drawZoombox(self.old_zoomdc)
        self.old_zoomdc = (None, (0,0),(0,0))            

        x0     = min(x, self.conf.zoom_x)
        ymax   = max(y, self.conf.zoom_y)
        width  = abs(x -self.conf.zoom_x)
        height = abs(y -self.conf.zoom_y)
        y0     = self.canvas.figure.bbox.height - ymax

        zdc = wx.ClientDC(self.canvas)
        # print 'on Mouse Motion ', zdc, x, y
        zdc.SetBrush(self.zoombrush)
        zdc.SetPen(self.zoompen)
        zdc.SetLogicalFunction(wx.XOR)
        self.old_zoomdc = (zdc, (x0, y0), (width, height))
        self.__drawZoombox(self.old_zoomdc)