Ejemplo n.º 1
0
class PlotPanel(wx.Panel):
    ''' Base class for the plotting in GenX - all the basic functionallity
        should be implemented in this class. The plots should be derived from
        this class. These classes should implement an update method to update
        the plots.
    '''
    def __init__(self, parent, id = -1, color = None, dpi = None
            , style = wx.NO_FULL_REPAINT_ON_RESIZE|wx.EXPAND|wx.ALL
            , config = None, config_name = '', **kwargs):

        wx.Panel.__init__(self,parent, id = id, style = style, **kwargs)

        self.parent = parent
        self.callback_window = self
        self.config = config
        self.config_name = config_name
        self.figure = Figure(None,dpi)
        self.canvas = FigureCanvasWxAgg(self, -1, self.figure)
        self.canvas.SetExtraStyle(wx.EXPAND)
        self.SetColor(color)
        self.Bind(wx.EVT_IDLE, self._onIdle)
        self.Bind(wx.EVT_SIZE, self._onSize)
        self._resizeflag = True
        self.print_size = (15./2.54, 12./2.54)
        #self._SetSize()

        # Flags and bindings for zooming
        self.zoom = False
        self.zooming = False
        self.scale = 'linear'
        self.autoscale = True


        self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnLeftMouseButtonDown)
        self.canvas.Bind(wx.EVT_LEFT_UP, self.OnLeftMouseButtonUp)
        self.canvas.Bind(wx.EVT_MOTION, self.OnMouseMove)
        self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDblClick)
        self.canvas.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu)

        cursor = wx.StockCursor(wx.CURSOR_CROSS)
        self.canvas.SetCursor(cursor)
        self.old_scale_state = True
        self.ax = None

        # Init printout stuff
        self.fig_printer = FigurePrinter(self)

        # Create the drawing bitmap
        self.bitmap =wx.EmptyBitmap(1, 1)
#        DEBUG_MSG("__init__() - bitmap w:%d h:%d" % (w,h), 2, self)
        self._isDrawn = False

    def SetColor(self, rgbtuple=None):
        ''' Set the figure and canvas color to be the same '''
        if not rgbtuple:
            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
        col = [c/255. for c in rgbtuple]
        self.figure.set_facecolor(col)
        self.figure.set_edgecolor(col)
        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))

    def _onSize(self, evt):
        self._resizeflag = True
        self._SetSize()
        #self.canvas.draw(repaint = False)

    def _onIdle(self, evt):
        if self._resizeflag:
            self._resizeflag = False
            self._SetSize()
            #self.canvas.gui_repaint(drawDC = wx.PaintDC(self))


    def _SetSize(self, pixels = None):
        ''' This method can be called to force the Plot to be a desired
            size which defaults to the ClientSize of the Panel.
        '''
        if not pixels:
            pixels = self.GetClientSize()

        self.canvas.SetSize(pixels)
        #self.figure.set_size_inches(pixels[0]/self.figure.get_dpi()
        #, pixels[1]/self.figure.get_dpi())

    def ReadConfig(self):
        '''ReadConfig(self) --> None

        Reads in the config file
        '''
        bool_items = ['zoom', 'autoscale']
        bool_func = [self.SetZoom, self.SetAutoScale]

        if not self.config:
            return


        vals = []
        for index in range(len(bool_items)):
            try:
                val = self.config.get_boolean(self.config_name,\
                        bool_items[index])
            except io.OptionError as e:
                print('Could not locate option %s.%s'\
                %(self.config_name, bool_items[index]))
                vals.append(None)
            else:
                vals.append(val)

        try:
            scale = self.config.get(self.config_name, 'y scale')
            string_sucess = True
        except io.OptionError as e:
            string_sucess = False
            print('Could not locate option %s.%s'\
            %(self.config_name, 'scale'))
        else:
            self.SetYScale(scale)

        # This is done due to that the zoom and autoscale has to read
        # before any commands are issued in order not to overwrite
        # the config
        [bool_func[i](vals[i]) for i in range(len(vals)) if vals[i]]


    def WriteConfig(self):
        '''WriteConfig(self) --> None

        Writes the current settings to the config file
        '''
        if self.config:
            self.config.set(self.config_name, 'zoom', self.GetZoom())
            self.config.set(self.config_name, 'autoscale', self.GetAutoScale())
            self.config.set(self.config_name, 'y scale', self.GetYScale())

    def SetZoom(self, active = False):
        '''
        set the zoomstate
        '''
        #if not self.zoom_sel:
            #self.zoom_sel = RectangleSelector(self.ax,\
            # self.box_select_callback, drawtype='box',useblit=False)
        #print help(self.zoom_sel.ignore)

        if active:
            #self.zoom_sel.ignore = lambda x: False
            self.zoom = True
            cursor = wx.StockCursor(wx.CURSOR_MAGNIFIER)
            self.canvas.SetCursor(cursor)
            if self.callback_window:
                evt = state_changed(zoomstate = True,\
                        yscale = self.GetYScale(), autoscale = self.autoscale)
                wx.PostEvent(self.callback_window, evt)
            if self.ax:
                #self.ax.set_autoscale_on(False)
                self.old_scale_state = self.GetAutoScale()
                self.SetAutoScale(False)

        else:
            #self.zoom_sel.ignore = lambda x: True
            self.zoom = False
            cursor = wx.StockCursor(wx.CURSOR_CROSS)
            self.canvas.SetCursor(cursor)
            if self.callback_window:
                evt = state_changed(zoomstate = False,\
                    yscale = self.GetYScale(), autoscale = self.autoscale)
                wx.PostEvent(self.callback_window, evt)
            if self.ax:
                #self.ax.set_autoscale_on(self.autoscale)
                self.SetAutoScale(self.old_scale_state)
        self.WriteConfig()

    def GetZoom(self):
        '''GetZoom(self) --> state [bool]
        Returns the zoom state of the plot panel.
        True = zoom active
        False = zoom inactive
        '''
        return self.zoom

    def SetAutoScale(self, state):
        '''SetAutoScale(self, state) --> None

        Sets autoscale of the main axes wheter or not it should autoscale
        when plotting
        '''
        #self.ax.set_autoscale_on(state)
        self.autoscale = state
        self.WriteConfig()
        evt = state_changed(zoomstate = self.GetZoom(),\
                        yscale = self.GetYScale(), autoscale = self.autoscale)
        wx.PostEvent(self.callback_window, evt)


    def GetAutoScale(self):
        '''GetAutoScale(self) --> state [bool]

        Returns the autoscale state, true if the plots is automatically
        scaled for each plot command.
        '''
        return self.autoscale

    def AutoScale(self, force = False):
        '''AutoScale(self) --> None

        A log safe way to autoscale the plots - the ordinary axis tight
        does not work for negative log data. This works!
        '''
        if not (self.autoscale or force):
            return
        # If nothing is plotted no autoscale use defaults...
        if sum([len(line.get_ydata()) > 0 for line in self.ax.lines]) == 0:
            self.ax.set_xlim(0, 1)
            self.ax.set_ylim(1e-3, 1.0)
            return
        if self.scale == 'log':
            #print 'log scaling'
            # Find the lowest possible value of all the y-values that are
            #greater than zero. check so that y data contain data before min
            # is applied
            tmp = [line.get_ydata().compress(line.get_ydata() > 0.0).min()\
                   for line in self.ax.lines if array(line.get_ydata() > 0.0).sum() > 0]
            if len(tmp) > 0:
                ymin = min(tmp)
            else:
                ymin = 1e-3
            tmp = [line.get_ydata().compress(line.get_ydata() > 0.0).max()\
                   for line in self.ax.lines if array(line.get_ydata() > 0.0).sum() > 0]
            if len(tmp) > 0:
                ymax = max(tmp)
            else:
                ymax = 1
        else:
            ymin = min([array(line.get_ydata()).min()\
                     for line in self.ax.lines if len(line.get_ydata()) > 0])
            ymax = max([array(line.get_ydata()).max()\
                   for line in self.ax.lines if len(line.get_ydata()) > 0])
        tmp = [array(line.get_xdata()).min()\
                    for line in self.ax.lines if len(line.get_ydata()) > 0]
        if len(tmp) > 0:
            xmin = min(tmp)
        else:
            xmin = 0
        tmp = [array(line.get_xdata()).max()\
                    for line in self.ax.lines if len(line.get_ydata()) > 0]
        if len(tmp) > 0:
            xmax = max(tmp)
        else:
            xmax = 1
        # Set the limits
        #print 'Autoscaling to: ', ymin, ymax
        self.ax.set_xlim(xmin, xmax)
        self.ax.set_ylim(ymin*(1-sign(ymin)*0.05), ymax*(1+sign(ymax)*0.05))
        #self.ax.set_yscale(self.scale)
        self.flush_plot()

    def SetYScale(self, scalestring):
        ''' SetYScale(self, scalestring) --> None

        Sets the y-scale of the main plotting axes. Currently accepts
        'log' or 'lin'.
        '''
        if self.ax:
            if scalestring == 'log':
                self.scale = 'log'
                self.AutoScale(force = True)
                try:
                    self.ax.set_yscale('log')
                except OverflowError:
                    self.AutoScale(force = True)
            elif scalestring == 'linear' or scalestring == 'lin':
                self.scale = 'linear'
                self.ax.set_yscale('linear')
                self.AutoScale(force = True)
            else:
                raise ValueError('Not allowed scaling')
            self.flush_plot()
            evt = state_changed(zoomstate = self.GetZoom(),\
                        yscale = self.GetYScale(), autoscale = self.autoscale)
            wx.PostEvent(self.callback_window, evt)
            self.WriteConfig()

    def GetYScale(self):
        '''GetYScale(self) --> String

        Returns the current y-scale in use. Currently the string
        'log' or 'linear'. If the axes does not exist it returns None.
        '''
        if self.ax:
            return self.ax.get_yscale()
        else:
            return None


    def CopyToClipboard(self, event = None):
        '''CopyToClipboard(self, event) --> None

        Copy the plot to the clipboard.
        '''
        self.canvas.Copy_to_Clipboard(event = event)

    def PrintSetup(self, event = None):
        '''PrintSetup(self) --> None

        Sets up the printer. Creates a dialog box
        '''
        self.fig_printer.pageSetup()

    def PrintPreview(self, event = None):
        '''PrintPreview(self) --> None

        Prints a preview on screen.
        '''
        self.fig_printer.previewFigure(self.figure)

    def Print(self, event= None):
        '''Print(self) --> None

        Print the figure.
        '''
        self.fig_printer.printFigure(self.figure)


    def SetCallbackWindow(self, window):
        '''SetCallbackWindow(self, window) --> None

        Sets the callback window that should recieve the events from
        picking.
        '''

        self.callback_window = window

    def OnLeftDblClick(self, event):
        if self.ax and self.zoom:
            tmp = self.GetAutoScale()
            self.SetAutoScale(True)
            self.AutoScale()
            self.SetAutoScale(tmp)
            #self.AutoScale()
            #self.flush_plot()
            #self.ax.set_autoscale_on(False)

    def OnLeftMouseButtonDown(self, event):
        self.start_pos = event.GetPosition()
        #print 'Left Mouse button pressed ', self.ax.transData.inverse_xy_tup(self.start_pos)
        class Point:
            pass
        p = Point()
        p.x, p.y = self.start_pos
        size = self.canvas.GetClientSize()
        p.y = (size.height - p.y)
        if self.zoom and self.ax:
            if mat_ver > zoom_ver:
                in_axes = self.ax.in_axes(p)
            else:
                in_axes = self.ax.in_axes(*self.start_pos)
            if in_axes:
                self.zooming = True
                self.cur_rect = None
                self.canvas.CaptureMouse()
            else:
                self.zooming = False
        elif self.ax:
            size = self.canvas.GetClientSize()
            if mat_ver > zoom_ver:
                xy = self.ax.transData.inverted().transform(\
                    array([self.start_pos[0], size.height-self.start_pos[1]])\
                    [newaxis,:])
                x, y = xy[0,0], xy[0,1]
            else:
                x, y = self.ax.transData.inverse_xy_tup(\
                    (self.start_pos[0], size.height - self.start_pos[1]))
            if self.callback_window:
                evt = plot_position(text = '(%.3e, %.3e)'%(x, y))
                wx.PostEvent(self.callback_window, evt)
            #print x,y


    def OnMouseMove(self, event):
        if self.zooming and event.Dragging() and event.LeftIsDown():
            self.cur_pos = event.GetPosition()
            #print 'Mouse Move ', self.ax.transData.inverse_xy_tup(self.cur_pos)
            class Point:
                pass
            p = Point()
            p.x, p.y = self.cur_pos
            size = self.canvas.GetClientSize()
            p.y = (size.height - p.y)
            if mat_ver > zoom_ver:
                in_axes = self.ax.in_axes(p)
            else:
                in_axes = self.ax.in_axes(*self.start_pos)
            if in_axes:
                new_rect = (min(self.start_pos[0], self.cur_pos[0]),
                            min(self.start_pos[1], self.cur_pos[1]),
                        abs(self.cur_pos[0] - self.start_pos[0]),
                        abs(self.cur_pos[1] - self.start_pos[1]))
                self._DrawAndErase(new_rect, self.cur_rect)
                self.cur_rect = new_rect
        #event.Skip()

    def OnLeftMouseButtonUp(self, event):
        if self.canvas.HasCapture():
            #print 'Left Mouse button up'
            self.canvas.ReleaseMouse()
            if self.zooming and self.cur_rect:
                # Note: The coordinte system for matplotlib have a different
                # direction of the y-axis and a different origin!
                size = self.canvas.GetClientSize()
                if mat_ver > zoom_ver:
                    start = self.ax.transData.inverted().transform(\
                    array([self.start_pos[0], size.height-self.start_pos[1]])[newaxis,:])
                    end = self.ax.transData.inverted().transform(\
                    array([self.cur_pos[0], size.height-self.cur_pos[1]])[newaxis, :])
                    xend, yend = end[0,0], end[0,1]
                    xstart, ystart = start[0,0], start[0,1]
                else:
                    xstart, ystart = self.ax.transData.inverse_xy_tup(\
                        (self.start_pos[0], size.height-self.start_pos[1]))
                    xend, yend = self.ax.transData.inverse_xy_tup(\
                        (self.cur_pos[0], size.height-self.cur_pos[1]))

                #print xstart, xend
                #print ystart, yend
                self.ax.set_xlim(min(xstart,xend), max(xstart,xend))
                self.ax.set_ylim(min(ystart,yend), max(ystart,yend))
                self.flush_plot()
            self.zooming = False

    def _DrawAndErase(self, box_to_draw, box_to_erase = None):
        '''_DrawAndErase(self, box_to_draw, box_to_erase = None) --> None
        '''
        dc = wx.ClientDC(self.canvas)
        # dc.BeginDrawing()
        dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT))
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.SetLogicalFunction(wx.XOR)
        if box_to_erase:
            dc.DrawRectangle(*box_to_erase)
        dc.DrawRectangle(*box_to_draw)
        # dc.EndDrawing()

    def OnContextMenu(self, event):
        '''OnContextMenu(self, event) --> None

        Callback to show the popmenu for the plot which allows various
        settings to be made.
        '''
        menu = wx.Menu()

        zoomID = wx.NewId()
        menu.AppendCheckItem(zoomID, "Zoom")
        menu.Check(zoomID, self.GetZoom())
        def OnZoom(event):
            self.SetZoom(not self.GetZoom())
        self.Bind(wx.EVT_MENU, OnZoom, id = zoomID)

        zoomallID = wx.NewId()
        menu.Append(zoomallID, 'Zoom All')
        def zoomall(event):
            tmp = self.GetAutoScale()
            self.SetAutoScale(True)
            self.AutoScale()
            self.SetAutoScale(tmp)
            #self.flush_plot()
        self.Bind(wx.EVT_MENU, zoomall, id = zoomallID)

        copyID = wx.NewId()
        menu.Append(copyID, "Copy")
        def copy(event):
            self.CopyToClipboard()
        self.Bind(wx.EVT_MENU, copy, id = copyID)

        yscalemenu = wx.Menu()
        logID = wx.NewId()
        linID = wx.NewId()
        yscalemenu.AppendRadioItem(logID, "log")
        yscalemenu.AppendRadioItem(linID, "linear")
        menu.AppendMenu(-1, "y-scale", yscalemenu)
        if self.GetYScale() == 'log':
            yscalemenu.Check(logID, True)
        else:
            yscalemenu.Check(linID, True)

        def yscale_log(event):
            if self.ax:
                self.SetYScale('log')
                self.AutoScale()
                self.flush_plot()
        def yscale_lin(event):
            if self.ax:
                self.SetYScale('lin')
                self.AutoScale()
                self.flush_plot()
        self.Bind(wx.EVT_MENU, yscale_log, id = logID)
        self.Bind(wx.EVT_MENU, yscale_lin, id = linID)

        autoscaleID = wx.NewId()
        menu.AppendCheckItem(autoscaleID, "Autoscale")
        menu.Check(autoscaleID, self.GetAutoScale())
        def OnAutoScale(event):
            self.SetAutoScale(not self.GetAutoScale())
        self.Bind(wx.EVT_MENU, OnAutoScale, id = autoscaleID)

        # Time to show the menu
        self.PopupMenu(menu)

        menu.Destroy()

    def flush_plot(self):
        #self._SetSize()
        #self.canvas.gui_repaint(drawDC = wx.PaintDC(self))
        #self.ax.set_yscale(self.scale)
        self.canvas.draw()

    def update(self, data):
        pass
Ejemplo n.º 2
0
class Plotter(wx.Panel):
    """Accepts data and plots it using wx.Panel and matplotlib.

    Keyword arguments:
    data (iterable) -- data to be plotted, passed to matplotlib
    zoomfactor (float) -- zooming factor for plot (default 1.25)
    """
    def __init__(self, parent, data=None, zoomfactor=1.25):

        self.data = data
        self.zoomfactor = zoomfactor
        self.scale = 1
        self.prev_mouse_location = None
        wx.Panel.__init__(self, parent)

        self.figure = Figure()
        self.axes = self.figure.add_subplot(111,
                axisbg='#ffffff')

        self.axes.set_yticklabels([])
        self.axes.set_xticklabels([])
        max_amp = max(numpy.amax(data), -numpy.amin(data))
        self.axes.set_ylim(-max_amp, max_amp)
        self.axes.set_xlim(0, len(data))

        self.canvas = FigureCanvas(self, -1, self.figure)

        self.canvas.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
        self.canvas.Bind(wx.EVT_MOUSEWHEEL, self._on_mouse_wheel)
        #self.canvas.Bind(wx.EVT_MOTION, self._on_mousemove)
        self.SetFocus()

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        self.SetSizer(self.sizer)

    def draw(self, data=None):
        """Draws self.data or passed data."""
        if data is None:
            data = self.data
        self.axes.plot(data, color='#1498DB', linewidth=0.3)

    def _zoom(self, factor, system=None):
        """Private function, use zoomin and zoomout instead."""
        self.scale *= factor
        x_min, x_max = self.axes.get_xlim()
        if not system:
            self.axes.set_xlim(x_min/factor, x_max/factor)
        else:
            width = x_max - x_min
            point = system[0]
            ratio = system[1]
            new_width = width/factor

            new_x_min = (point - ratio * new_width)
            self.axes.set_xlim(new_x_min, new_x_min + new_width)

        self.canvas.draw()

    def zoomin(self, factor=None, system=None):
        """Zooms in plot with factor (default self.zoomfactor)"""
        if not factor:
            factor = self.zoomfactor
        self._zoom(self.zoomfactor, system)

    def zoomout(self, factor=None, system=None):
        """Zooms in plot with factor (default self.zoomfactor)"""
        if not factor:
            factor = self.zoomfactor
        self._zoom(1.0/self.zoomfactor, system)

    def span(self, direction):
        """spans 1/10th of the way left or right, based on direction parameter

        Keyword arguments:
        direction (str) -- must be equal to 'left' or 'right'
        """
        if direction == 'right':
            multiplier = 1
        elif direction == 'left':
            multiplier = -1
        x_min, x_max = self.axes.get_xlim()
        data_range = x_max - x_min
        step = multiplier * (data_range/10)
        self.axes.set_xlim(x_min + step, x_max + step)
        self.canvas.draw()

    def _on_key_down(self, event=None):
        """Event handler for key down events."""
        keycode = event.GetKeyCode()
        if keycode == 73:
            self.zoomin()
        elif keycode == 79:
            self.zoomout()
        elif keycode == wx.WXK_LEFT:
            self.span('left')
        elif keycode == wx.WXK_RIGHT:
            self.span('right')
        else:
            event.Skip()

    def _on_mouse_wheel(self, event=None):
        """Event handler for mouse wheel events."""
        rotation = event.GetWheelRotation()

        w = self.canvas.GetClientSize()[0]
        x_pos = round(event.GetX() - 0.125 * w) - 1
        rx = x_pos / (0.75 * w)

        x_min, x_max = self.axes.get_xlim()
        width = x_max - x_min
        point = rx * width + x_min

        if rotation > 0:
            self.zoomin(system=(point, rx))
        else:
            self.zoomout(system=(point, rx))