Exemplo n.º 1
0
class PlatformExtrinsics3DPlot(wx.Panel):

	def __init__(self, parent):
		wx.Panel.__init__(self, parent)

		self.initialize()

	def initialize(self):
		fig = Figure(facecolor=(0.7490196,0.7490196,0.7490196,1), tight_layout=True)
		self.canvas = FigureCanvasWxAgg(self, -1, fig)
		self.canvas.SetExtraStyle(wx.EXPAND)
		self.ax = fig.gca(projection='3d', axisbg=(0.7490196,0.7490196,0.7490196,1))

		self.Bind(wx.EVT_SIZE, self.onSize)
		self.Layout()

	def onSize(self,event):
		self.canvas.SetClientSize(self.GetClientSize())
		self.canvas.draw()
		self.Layout()

	def add(self, args):
		R, t, center, point, normal, [x,y,z], circle = args

		# plot the surface, data, and synthetic circle
		self.ax.scatter(x, z, y, c='b', marker='o')
		#self.ax.scatter(center[0], center[2], center[1], c='b', marker='o')
		self.ax.plot(circle[0], circle[2], circle[1], c='r')

		d = profile.getProfileSettingFloat('pattern_distance')

		self.ax.plot([t[0],t[0]+50*R[0][0]], [t[2],t[2]+50*R[2][0]], [t[1],t[1]+50*R[1][0]], linewidth=2.0, color='red')
		self.ax.plot([t[0],t[0]+50*R[0][1]], [t[2],t[2]+50*R[2][1]], [t[1],t[1]+50*R[1][1]], linewidth=2.0, color='green')
		self.ax.plot([t[0],t[0]+d*R[0][2]], [t[2],t[2]+d*R[2][2]], [t[1],t[1]+d*R[1][2]], linewidth=2.0, color='blue')

		self.ax.plot([0,50], [0,0], [0,0], linewidth=2.0, color='red')
		self.ax.plot([0,0], [0,0], [0,50], linewidth=2.0, color='green')
		self.ax.plot([0,0], [0,50], [0,0], linewidth=2.0, color='blue')

		self.ax.set_xlabel('X')
		self.ax.set_ylabel('Z')
		self.ax.set_zlabel('Y')

		self.ax.set_xlim(-150, 150)
		self.ax.set_ylim(0, 400)
		self.ax.set_zlim(-150, 150)

		self.ax.invert_xaxis()
		self.ax.invert_yaxis()
		self.ax.invert_zaxis()

		self.canvas.draw()
		self.Layout()

	def clear(self):
		self.ax.cla()
Exemplo n.º 2
0
class LaserTriangulation3DPlot(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        self.initialize()

    def initialize(self):
        fig = Figure(facecolor=(0.7490196, 0.7490196, 0.7490196, 1),
                     tight_layout=True)
        self.canvas = FigureCanvasWxAgg(self, -1, fig)
        self.canvas.SetExtraStyle(wx.EXPAND)
        self.ax = fig.gca(projection='3d',
                          axisbg=(0.7490196, 0.7490196, 0.7490196, 1))

        self.Bind(wx.EVT_SIZE, self.onSize)
        self.Layout()

    def onSize(self, event):
        self.canvas.SetClientSize(self.GetClientSize())
        self.canvas.draw()
        self.Layout()

    def add(self, args):
        dL, nL, stdL, dR, nR, stdR = args

        rL = np.cross(np.array([0, 0, 1]), nL)
        sL = np.cross(rL, nL)
        RL = np.array([rL, sL, nL])

        rR = np.cross(np.array([0, 0, 1]), nR)
        sR = np.cross(rR, nR)
        RR = np.array([rR, sR, nR])

        self.addPlane(RL, dL * nL)
        self.addPlane(RR, dR * nR)

        self.ax.plot([0, 50], [0, 0], [0, 0], linewidth=2.0, color='red')
        self.ax.plot([0, 0], [0, 0], [0, 50], linewidth=2.0, color='green')
        self.ax.plot([0, 0], [0, 50], [0, 0], linewidth=2.0, color='blue')

        self.ax.set_xlabel('X')
        self.ax.set_ylabel('Z')
        self.ax.set_zlabel('Y')

        self.ax.text(-100, 0, 0, str(round(stdL, 5)), fontsize=15)
        self.ax.text(100, 0, 0, str(round(stdR, 5)), fontsize=15)

        self.ax.set_xlim(-150, 150)
        self.ax.set_ylim(0, 400)
        self.ax.set_zlim(-150, 150)

        self.ax.invert_xaxis()
        self.ax.invert_yaxis()
        self.ax.invert_zaxis()

        self.canvas.draw()
        self.Layout()

    def addPlane(self, R, t):
        w = 200
        h = 300

        p = np.array([[-w / 2, -h / 2, 0], [-w / 2, h / 2,
                                            0], [w / 2, h / 2, 0],
                      [w / 2, -h / 2, 0], [-w / 2, -h / 2, 0]])
        n = np.array([[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]])

        self.ax.plot([0, t[0]], [0, t[2]], [0, t[1]],
                     linewidth=2.0,
                     color='yellow')

        points = np.dot(R.T, p.T) + np.array([t, t, t, t, t]).T
        normals = np.dot(R.T, n.T)

        X = np.array([points[0], normals[0]])
        Y = np.array([points[1], normals[1]])
        Z = np.array([points[2], normals[2]])

        self.ax.plot_surface(X, Z, Y, linewidth=0, color=(1, 0, 0, 0.8))

        self.canvas.draw()

    def clear(self):
        self.ax.cla()
Exemplo n.º 3
0
class CameraIntrinsics3DPlot(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        self.initialize()

    def initialize(self):
        self.fig = Figure(facecolor=(0.7490196, 0.7490196, 0.7490196, 1),
                          tight_layout=True)
        self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
        self.canvas.SetExtraStyle(wx.EXPAND)

        self.ax = self.fig.gca(projection='3d',
                               axisbg=(0.7490196, 0.7490196, 0.7490196, 1))

        self.print_canvas()

        self.Bind(wx.EVT_SIZE, self.on_size)
        self.Layout()

    def on_size(self, event):
        self.canvas.SetClientSize(self.GetClientSize())
        self.Layout()
        event.Skip()

    def print_canvas(self):
        self.ax.plot([0, 50], [0, 0], [0, 0], linewidth=2.0, color='red')
        self.ax.plot([0, 0], [0, 0], [0, 50], linewidth=2.0, color='green')
        self.ax.plot([0, 0], [0, 50], [0, 0], linewidth=2.0, color='blue')

        self.ax.set_xlabel('X')
        self.ax.set_ylabel('Z')
        self.ax.set_zlabel('Y')
        self.ax.set_xlim(-150, 150)
        self.ax.set_ylim(0, 500)
        self.ax.set_zlim(-150, 150)
        self.ax.invert_xaxis()
        self.ax.invert_yaxis()
        self.ax.invert_zaxis()

    def add(self, error, rvecs, tvecs):
        w = pattern.columns * pattern.square_width
        h = pattern.rows * pattern.square_width

        p = np.array([[0, 0, 0], [w, 0, 0], [w, h, 0], [0, h, 0], [0, 0, 0]])
        n = np.array([[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]])

        c = np.array([[30, 0, 0], [0, 30, 0], [0, 0, -30]])

        self.ax.text(-100, 200, 0, str(round(error, 5)), fontsize=15)

        for ind, transvector in enumerate(rvecs):

            R = cv2.Rodrigues(transvector)[0]
            t = tvecs[ind]

            points = (np.dot(R, p.T) + np.array([t, t, t, t, t]).T)[0]
            normals = np.dot(R, n.T)

            X = np.array([points[0], normals[0]])
            Y = np.array([points[1], normals[1]])
            Z = np.array([points[2], normals[2]])

            coords = (np.dot(R, c.T) + np.array([t, t, t]).T)[0]

            CX = coords[0]
            CY = coords[1]
            CZ = coords[2]

            color = (random.random(), random.random(), random.random(), 0.8)

            self.ax.plot_surface(X, Z, Y, linewidth=0, color=color)

            self.ax.plot([t[0][0], CX[0]], [t[2][0], CZ[0]], [t[1][0], CY[0]],
                         linewidth=1.0,
                         color='green')
            self.ax.plot([t[0][0], CX[1]], [t[2][0], CZ[1]], [t[1][0], CY[1]],
                         linewidth=1.0,
                         color='red')
            self.ax.plot([t[0][0], CX[2]], [t[2][0], CZ[2]], [t[1][0], CY[2]],
                         linewidth=1.0,
                         color='blue')
            self.canvas.draw()

        self.Layout()

    def clear(self):
        self.ax.cla()
        self.print_canvas()
Exemplo n.º 4
0
class PlotPanel(BasePanel):
    """
    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.
    """

    def __init__(self, parent, size=(700, 450), dpi=150, axisbg=None,
                 facecolor=None, fontsize=9, trace_color_callback=None,
                 output_title='plot', with_data_process=True, theme=None,
                 **kws):

        self.trace_color_callback = trace_color_callback
        BasePanel.__init__(self, parent,
                           output_title=output_title, size=size, **kws)

        self.conf = PlotConfig(panel=self, theme=theme,
                               with_data_process=with_data_process)
        self.data_range = {}
        self.win_config = None
        self.cursor_callback = None
        self.lasso_callback = None
        self.cursor_mode = 'zoom'
        self.parent  = parent
        self.figsize = (size[0]*1.0/dpi, size[1]*1.0/dpi)
        self.dpi  = dpi
        self.conf.facecolor = ifnotNone(axisbg, self.conf.facecolor)
        self.conf.facecolor = ifnotNone(facecolor, self.conf.facecolor)

        # axesmargins : margins in px left/top/right/bottom
        self.axesmargins = (30, 30, 30, 30)

        self.BuildPanel()
        self.conf.user_limits = {} # [None, None, None, None]
        self.data_range = {}
        self.conf.zoom_lims = []
        self.conf.axes_traces = {}
        self.use_dates = False
        self.dates_style = None

    def plot(self, xdata, ydata, side='left', title=None,
             xlabel=None, ylabel=None, y2label=None,
             use_dates=False, dates_style=None, **kws):
        """
        create a new plot of x/y data, clearing any existing plot on the panel

        """
        allaxes = self.fig.get_axes()
        if len(allaxes) > 1:
            for ax in allaxes[1:]:
                if ax in self.data_range:
                    self.data_range.pop(ax)
                self.fig.delaxes(ax)

        self.data_range = {}
        self.conf.zoom_lims = []
        self.conf.axes_traces = {}
        self.clear()
        axes = self.axes
        if side == 'right':
            axes = self.get_right_axes()
        self.conf.reset_lines()
        self.conf.yscale = 'linear'
        self.conf.user_limits[axes] = 4*[None]

        if xlabel is not None:
            self.set_xlabel(xlabel, delay_draw=True)
        if ylabel is not None:
            self.set_ylabel(ylabel, delay_draw=True)
        if y2label is not None:
            self.set_y2label(y2label, delay_draw=True)
        if title is not None:
            self.set_title(title, delay_draw=True)
        self.dates_style = ifnotNone(dates_style, self.dates_style)
        self.use_dates = ifnotNone(use_dates, self.use_dates)
        return self.oplot(xdata, ydata, side=side, **kws)


    def oplot(self, xdata, ydata, side='left', label=None, xlabel=None,
              ylabel=None, y2label=None, title=None, dy=None,
              ylog_scale=None, xlog_scale=None, grid=None, xmin=None,
              xmax=None, ymin=None, ymax=None, color=None, style=None,
              drawstyle=None, linewidth=2, marker=None, markersize=None,
              refresh=True, show_legend=None, legend_loc='best',
              legend_on=True, delay_draw=False, bgcolor=None,
              framecolor=None, gridcolor=None, labelfontsize=None,
              titlefontsize=None, legendfontsize=None, fullbox=None,
              axes_style=None, zorder=None, viewpad=None, theme=None,
              use_dates=None, dates_style=None, **kws):
        """
        basic plot method, adding to an existing display

        """
        self.cursor_mode = 'zoom'
        conf = self.conf
        conf.plot_type = 'lineplot'
        axes = self.axes
        if theme is not None:
            conf.set_theme(theme=theme)
        if side == 'right':
            axes = self.get_right_axes()
        # set y scale to log/linear
        if ylog_scale is not None:
            conf.yscale = {False:'linear', True:'log'}[ylog_scale]

        if xlog_scale is not None:
            conf.xscale = {False:'linear', True:'log'}[xlog_scale]

        axes.xaxis.set_major_formatter(FuncFormatter(self.xformatter))
        self.dates_style = ifnotNone(dates_style, self.dates_style)
        self.use_dates = ifnotNone(use_dates, self.use_dates)
        if isinstance(xdata[0], datetime):
            self.use_dates = True

        if self.use_dates:
            # date handling options to get xdate to mpl dates
            #   1. xdate are in datetime: convert to mpl dates
            #   2. xdata are strings: parse with datestr2num
            #   3. xdata are floats:
            #        a) dates_styles=='dates': use directly
            #        b) else: convert as unix timestamp to mpl dates
            x0 = xdata[0]
            dstyle = self.dates_style
            if dstyle is None: dstyle = ''
            if isinstance(x0, datetime):
                xdata = dates.date2num(xdata)
            elif isinstance(x0, str) or dstyle.lower().startswith('str'):
                xdata = dates.datestr2num(xdata)
            elif not dstyle.lower().startswith('dates'):
                xdata = dates.epoch2num(xdata)
        linewidth = ifNone(linewidth, 2)
        conf.viewpad = ifnotNone(viewpad, conf.viewpad)

        if xlabel is not None:
            self.set_xlabel(xlabel, delay_draw=delay_draw)
        if ylabel is not None:
            self.set_ylabel(ylabel, delay_draw=delay_draw)
        if y2label is not None:
            self.set_y2label(y2label, delay_draw=delay_draw)
        if title  is not None:
            self.set_title(title, delay_draw=delay_draw)
        if show_legend is not None:
            conf.set_legend_location(legend_loc, legend_on)
            conf.show_legend = show_legend

        conf.show_grid = ifnotNone(grid, conf.show_grid)

        # set data range for this trace
        # datarange = [min(xdata), max(xdata), min(ydata), max(ydata)]

        if axes not in conf.user_limits:
            conf.user_limits[axes] = [None, None, None, None]

        conf.user_limits[axes][0] = ifnotNone(xmin, conf.user_limits[axes][0])
        conf.user_limits[axes][1] = ifnotNone(xmax, conf.user_limits[axes][1])
        conf.user_limits[axes][2] = ifnotNone(ymin, conf.user_limits[axes][2])
        conf.user_limits[axes][3] = ifnotNone(ymax, conf.user_limits[axes][3])

        if axes == self.axes:
            axes.yaxis.set_major_formatter(FuncFormatter(self.yformatter))
        else:
            axes.yaxis.set_major_formatter(FuncFormatter(self.y2formatter))

        zorder = ifNone(zorder, 5*(conf.ntrace+1))

        if axes not in conf.axes_traces:
            conf.axes_traces[axes] = []
        conf.axes_traces[axes].append(conf.ntrace)

        conf.gridcolor = ifnotNone(gridcolor, conf.gridcolor)
        conf.facecolor = ifnotNone(bgcolor, conf.facecolor)

        if framecolor is not None:
            self.canvas.figure.set_facecolor(framecolor)

        conf.set_trace_zorder(zorder, delay_draw=True)
        if color:
            conf.set_trace_color(color, delay_draw=True)
        if style:
            conf.set_trace_style(style, delay_draw=True)
        if marker:
            conf.set_trace_marker(marker, delay_draw=True)
        if linewidth is not None:
            conf.set_trace_linewidth(linewidth, delay_draw=True)
        if markersize is not None:
            conf.set_trace_markersize(markersize, delay_draw=True)
        if drawstyle is not None:
            conf.set_trace_drawstyle(drawstyle, delay_draw=True)
        if dy is None:
            _lines = axes.plot(xdata, ydata, drawstyle=drawstyle, zorder=zorder)
        else:
            _lines = axes.errorbar(xdata, ydata, yerr=dy, zorder=zorder)

        if axes not in conf.data_save:
            conf.data_save[axes] = []
        conf.data_save[axes].append((xdata, ydata))

        if conf.show_grid and axes == self.axes:
            # I'm sure there's a better way...
            for i in axes.get_xgridlines() + axes.get_ygridlines():
                i.set_color(conf.gridcolor)
                i.set_zorder(-100)
            axes.grid(True)
        else:
            axes.grid(False)

        if (self.conf.xscale == 'log' or self.conf.yscale == 'log'):
            self.set_logscale(xscale=self.conf.xscale,
                              yscale=self.conf.yscale,
                              delay_draw=delay_draw)

        if label is None:
            label = 'trace %i' % (conf.ntrace+1)
        conf.set_trace_label(label, delay_draw=True)
        needs_relabel = False
        if labelfontsize is not None:
            conf.labelfont.set_size(labelfontsize)
            needs_relabel = True
        if titlefontsize is not None:
            conf.titlefont.set_size(titlefontsize)
            needs_relabel = True

        if legendfontsize is not None:
            conf.legendfont.set_size(legendfontsize)
            needs_relabel = True

        if conf.ntrace < len(conf.lines):
            conf.lines[conf.ntrace] = _lines
        else:
            conf.init_trace(conf.ntrace, 'black', 'solid')
            conf.lines.append(_lines)

        # now set plot limits:
        if not delay_draw:
            self.set_viewlimits()

        if refresh:
            conf.refresh_trace(conf.ntrace)
            needs_relabel = True

        if conf.show_legend and not delay_draw:
            conf.draw_legend()

        if needs_relabel and not delay_draw:
            conf.relabel()


        # axes style ('box' or 'open')
        conf.axes_style = 'box'
        if fullbox is not None and not fullbox:
            conf.axes_style = 'open'
        if axes_style in ('open', 'box', 'bottom'):
            conf.axes_style = axes_style
        conf.set_axes_style(delay_draw=delay_draw)
        if not delay_draw:
            self.draw()
            self.canvas.Refresh()
        conf.ntrace = conf.ntrace + 1
        return _lines

    def plot_many(self, datalist, side='left', title=None,
                  xlabel=None, ylabel=None, **kws):
        """
        plot many traces at once, taking a list of (x, y) pairs
        """
        def unpack_tracedata(tdat, **kws):
            if (isinstance(tdat, dict) and
                'xdata' in tdat and 'ydata' in tdat):
                xdata = tdat.pop('xdata')
                ydata = tdat.pop('ydata')
                out = kws
                out.update(tdat)
            elif isinstance(tdat, (list, tuple)):
                out = kws
                xdata = tdat[0]
                ydata = tdat[1]
            return (xdata, ydata, out)

        opts = dict(side=side, title=title, xlabel=xlabel, ylabel=ylabel,
                    delay_draw=True)
        opts.update(kws)
        x0, y0, opts = unpack_tracedata(datalist[0], **opts)

        self.plot(x0, y0, **opts)

        for dat in datalist[1:]:
            x, y, opts = unpack_tracedata(dat, delay_draw=True)
            self.oplot(x, y, **opts)

        self.reset_formats()
        conf = self.conf
        if conf.show_legend:
            conf.draw_legend()
        conf.relabel()
        self.draw()
        self.canvas.Refresh()

    def add_text(self, text, x, y, side='left', size=None,
                 rotation=None, ha='left', va='center',
                 family=None, **kws):
        """add text at supplied x, y position
        """
        axes = self.axes
        if side == 'right':
            axes = self.get_right_axes()
        dynamic_size = False
        if size is None:
            size = self.conf.legendfont.get_size()
            dynamic_size = True
        t = axes.text(x, y, text, ha=ha, va=va, size=size,
                      rotation=rotation, family=family, **kws)
        self.conf.added_texts.append((dynamic_size, t))
        self.draw()

    def add_arrow(self, x1, y1, x2, y2,  side='left',
                  shape='full', color='black',
                  width=0.01, head_width=0.03, overhang=0, **kws):
        """add arrow supplied x, y position"""
        dx, dy = x2-x1, y2-y1

        axes = self.axes
        if side == 'right':
            axes = self.get_right_axes()
        axes.arrow(x1, y1, dx, dy, shape=shape,
                   length_includes_head=True,
                   fc=color, edgecolor=color,
                   width=width, head_width=head_width,
                   overhang=overhang, **kws)
        self.draw()

    def scatterplot(self, xdata, ydata, label=None, size=10,
                    color=None, edgecolor=None,
                    selectcolor=None, selectedge=None,
                    xlabel=None, ylabel=None, y2label=None,
                    xmin=None, xmax=None, ymin=None, ymax=None,
                    viewpad=None, title=None, grid=None, callback=None, **kw):

        if xlabel is not None:
            self.set_xlabel(xlabel)
        if ylabel is not None:
            self.set_ylabel(ylabel)
        if y2label is not None:
            self.set_y2label(y2label)
        if title  is not None:
            self.set_title(title)
        if grid is not None:
            self.conf.show_grid = grid
        if callback is not None:
            self.lasso_callback = callback

        self.conf.plot_type = 'scatter'
        self.cursor_mode = 'lasso'
        if color is not None:
            self.conf.scatter_normalcolor = color
        if edgecolor is not None:
            self.conf.scatter_normaledge  = edgecolor
        if selectcolor is not None:
            self.conf.scatter_selectcolor = selectcolor
        if selectedge is not None:
            self.conf.scatter_selectedge = selectedge
        if viewpad is not None:
            self.conf.viewpad = viewpad

        axes = self.axes
        self.conf.user_limits[axes] = [xmin, xmax, ymin, ymax]

        self.conf.axes_traces = {axes: [0]}
        self.conf.set_trace_label('scatterplot')
        # self.conf.set_trace_datarange((min(xdata), max(xdata),
        #                                min(ydata), max(ydata)))

        self.conf.scatter_xdata = xdata
        self.conf.scatter_ydata = ydata
        self.axes.scatter(xdata, ydata, c=self.conf.scatter_normalcolor,
                          edgecolors=self.conf.scatter_normaledge)

        if self.conf.show_grid:
            for i in axes.get_xgridlines()+axes.get_ygridlines():
                i.set_color(self.conf.gridcolor)
                i.set_zorder(-30)
            axes.grid(True)
        else:
            axes.grid(False)
        xrange = max(xdata) - min(xdata)
        yrange = max(ydata) - min(ydata)

        xmin = min(xdata) - xrange/25.0
        xmax = max(xdata) + xrange/25.0
        ymin = min(ydata) - yrange/25.0
        ymax = max(ydata) + yrange/25.0

        axes.set_xlim((xmin, xmax), emit=True)
        axes.set_ylim((ymin, ymax), emit=True)
        self.set_viewlimits()
        self.draw()

    def lassoHandler(self, vertices):
        conf = self.conf
        if self.conf.plot_type == 'scatter':
            xd, yd = conf.scatter_xdata, conf.scatter_ydata
            sdat = list(zip(xd, yd))
            oldmask = conf.scatter_mask
            try:
                self.axes.scatter(xd[where(oldmask)], yd[where(oldmask)],
                                  s=conf.scatter_size,
                                  c=conf.scatter_normalcolor,
                                  edgecolors=conf.scatter_normaledge)
            except IndexError:
                self.axes.scatter(xd, yd, s=conf.scatter_size,
                                  c=conf.scatter_normalcolor,
                                  edgecolors=conf.scatter_normaledge)

            mask = conf.scatter_mask = inside_poly(vertices, sdat)
            pts = nonzero(mask)[0]
            self.axes.scatter(xd[where(mask)], yd[where(mask)],
                              s=conf.scatter_size,
                              c=conf.scatter_selectcolor,
                              edgecolors=conf.scatter_selectedge)

        else:
            xdata = self.axes.lines[0].get_xdata()
            ydata = self.axes.lines[0].get_ydata()
            sdat = [(x, y) for x, y in zip(xdata, ydata)]
            mask = inside_poly(vertices,sdat)
            pts = nonzero(mask)[0]

        self.lasso = None
        self.draw()
        # self.canvas.draw_idle()
        if (self.lasso_callback is not None and
            hasattr(self.lasso_callback , '__call__')):
            self.lasso_callback(data = sdat,
                                selected=pts, mask=mask)

    def set_xylims(self, limits, axes=None, side='left'):
        "set user-defined limits and apply them"
        if axes is None:
            axes = self.axes
            if side == 'right':
                axes = self.get_right_axes()
        self.conf.user_limits[axes] = list(limits)
        self.unzoom_all()

    def set_viewlimits(self):
        """updates xy limits of a plot based on current data,
        user defined limits, and any zoom level

        """
        self.reset_formats()
        self.conf.set_viewlimits()

    def get_viewlimits(self, axes=None):
        if axes is None: axes = self.axes
        xmin, xmax = axes.get_xlim()
        ymin, ymax = axes.get_ylim()
        return (xmin, xmax, ymin, ymax)

    def clear(self):
        """ clear plot """
        for ax in self.fig.get_axes():
            ax.cla()
        self.conf.ntrace = 0
        self.conf.xlabel = ''
        self.conf.ylabel = ''
        self.conf.y2label = ''
        self.conf.title  = ''
        self.conf.data_save = {}

    def reset_config(self):
        """reset configuration to defaults."""
        self.conf.set_defaults()

    def unzoom(self, event=None, **kws):
        """ zoom out 1 level, or to full data range """
        self.reset_formats()
        self.conf.unzoom(full=False)

    def unzoom_all(self, event=None):
        """ zoom out full data range """
        self.reset_formats()
        self.conf.unzoom(full=True)

    def process_data(self, event=None, expr=None):
        if expr in self.conf.data_expressions:
            self.conf.data_expr = expr
            self.conf.process_data()
            self.draw()
        if expr is None:
            expr = ''
        if self.conf.data_deriv:
            if expr is None:
                expr = 'y'
            expr = "deriv(%s)" % expr
        self.write_message("plotting %s" % expr, panel=0)

    def toggle_deriv(self, evt=None, value=None):
        "toggle derivative of data"
        if value is None:
            self.conf.data_deriv = not self.conf.data_deriv

            expr = self.conf.data_expr or ''
            if self.conf.data_deriv:
                expr = "deriv(%s)" % expr
            self.write_message("plotting %s" % expr, panel=0)

            self.conf.process_data()

    def set_logscale(self, event=None, xscale='linear', yscale='linear',
                     delay_draw=False):
        "set log or linear scale for x, y axis"
        self.conf.set_logscale(xscale=xscale, yscale=yscale,
                               delay_draw=delay_draw)

    def toggle_legend(self, evt=None, show=None):
        "toggle legend display"
        if show is None:
            show = not self.conf.show_legend
            self.conf.show_legend = show
        self.conf.draw_legend()

    def toggle_grid(self, evt=None, show=None):
        "toggle grid display"
        if show is None:
            show = not self.conf.show_grid
        self.conf.enable_grid(show)

    def configure(self, event=None):
        """show configuration frame"""
        if self.win_config is not None:
            try:
                self.win_config.Raise()
            except:
                self.win_config = None

        if self.win_config is None:
            self.win_config = PlotConfigFrame(parent=self,
                                              config=self.conf,
                                              trace_color_callback=self.trace_color_callback)
            self.win_config.Raise()

    ####
    ## create GUI
    ####
    def BuildPanel(self):
        """ builds basic GUI panel and popup menu"""
        self.fig   = Figure(self.figsize, dpi=self.dpi)
        # 1 axes for now
        self.gridspec = GridSpec(1,1)
        self.axes  = self.fig.add_subplot(self.gridspec[0],
                                          facecolor=self.conf.facecolor)
        self.canvas = FigureCanvas(self, -1, self.fig)
        self.canvas.SetClientSize((self.figsize[0]*self.dpi, self.figsize[1]*self.dpi))
        self.canvas.SetMinSize((100, 100))

        self.printer.canvas = self.canvas
        self.set_bg(self.conf.framecolor)
        self.conf.canvas = self.canvas
        self.canvas.SetCursor(wxCursor(wx.CURSOR_CROSS))
        self.canvas.mpl_connect("pick_event", self.__onPickEvent)

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

        # 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.autoset_margins()
        self.SetSizer(sizer)
        self.SetSize(self.GetBestVirtualSize())

        canvas_draw = self.canvas.draw
        def draw(*args, **kws):
            self.autoset_margins()
            canvas_draw(*args, **kws)
        self.canvas.draw = draw
        self.addCanvasEvents()

    def BuildPopup(self):
        # build pop-up menu for right-click display
        self.popup_menu = popup = wx.Menu()
        MenuItem(self, popup, 'Configure', '',   self.configure)

        MenuItem(self, popup, 'Save Image', '',   self.save_figure)
        popup.AppendSeparator()

        MenuItem(self, popup, 'Undo Zoom/Pan', '',   self.unzoom)
        MenuItem(self, popup, 'Zoom all the way out', '', self.unzoom_all)

        popup.AppendSeparator()
        MenuItem(self, popup, 'Zoom X and Y', '',
                 partial(self.onZoomStyle, style='both x and y'),
                 kind=wx.ITEM_RADIO, checked=True)
        MenuItem(self, popup, 'Zoom X Only', '',
                 partial(self.onZoomStyle, style='x only'),
                 kind=wx.ITEM_RADIO)
        MenuItem(self, popup, 'Zoom Y Only', '',
                 partial(self.onZoomStyle, style='y only'),
                 kind=wx.ITEM_RADIO)

    def onZoomStyle(self, event=None, style='both x and y'):
        self.conf.zoom_style = style

    def _updateCanvasDraw(self):
        """ Overload of the draw function that update
        axes position before each draw"""
        fn = self.canvas.draw
        def draw2(*a,**k):
            self._updateGridSpec()
            return fn(*a,**k)
        self.canvas.draw = draw2

    def get_default_margins(self):
        """get default margins"""
        trans = self.fig.transFigure.inverted().transform

        # Static margins
        l, t, r, b = self.axesmargins
        (l, b), (r, t) = trans(((l, b), (r, t)))

        # Extent
        dl, dt, dr, db = 0, 0, 0, 0
        for i, ax in enumerate(self.fig.get_axes()):
            (x0, y0),(x1, y1) = ax.get_position().get_points()
            try:
                (ox0, oy0), (ox1, oy1) = ax.get_tightbbox(self.canvas.get_renderer()).get_points()
                (ox0, oy0), (ox1, oy1) = trans(((ox0 ,oy0),(ox1 ,oy1)))
                dl = min(0.2, max(dl, (x0 - ox0)))
                dt = min(0.2, max(dt, (oy1 - y1)))
                dr = min(0.2, max(dr, (ox1 - x1)))
                db = min(0.2, max(db, (y0 - oy0)))
            except:
                pass

        return (l + dl, t + dt, r + dr, b + db)

    def autoset_margins(self):
        """auto-set margins  left, bottom, right, top
        according to the specified margins (in pixels)
        and axes extent (taking into account labels,
        title, axis)
        """
        if not self.conf.auto_margins:
            return
        # coordinates in px -> [0,1] in figure coordinates
        trans = self.fig.transFigure.inverted().transform

        # Static margins
        if not self.use_dates:
            self.conf.margins = l, t, r, b = self.get_default_margins()
            self.gridspec.update(left=l, top=1-t, right=1-r, bottom=b)
        # Axes positions update
        for ax in self.fig.get_axes():
            try:
                ax.update_params()
            except ValueError:
                pass
            ax.set_position(ax.figbox)

    def draw(self):
        self.canvas.draw()

    def update_line(self, trace, xdata, ydata, side='left', draw=False,
                    update_limits=True):
        """ update a single trace, for faster redraw """

        x = self.conf.get_mpl_line(trace)
        x.set_data(xdata, ydata)
        # datarange = [xdata.min(), xdata.max(), ydata.min(), ydata.max()]
        # self.conf.set_trace_datarange(datarange, trace=trace)
        axes = self.axes
        if side == 'right':
            axes = self.get_right_axes()

        if update_limits:
            self.set_viewlimits()
        if draw:
            self.draw()

    def get_figure(self):
        return self.fig

    def __onPickEvent(self, event=None):
        """pick events"""
        legline = event.artist
        trace = self.conf.legend_map.get(legline, None)
        visible = True
        if trace is not None and self.conf.hidewith_legend:
            line, legline, legtext = trace
            visible = not line.get_visible()
            line.set_visible(visible)
            if visible:
                legline.set_zorder(10.00)
                legline.set_alpha(1.00)
                legtext.set_zorder(10.00)
                legtext.set_alpha(1.00)
            else:
                legline.set_alpha(0.50)
                legtext.set_alpha(0.50)


    ####
    ## GUI events
    ####
    def report_leftdown(self, event=None):
        if event is None:
            return
        ex, ey = event.x, event.y
        msg = ''
        try:
            x, y = self.axes.transData.inverted().transform((ex, ey))
        except:
            x, y = event.xdata, event.ydata

        if x is not None and y is not None:
            msg = "X,Y= %g, %g" % (x, y)
        if len(self.fig.get_axes()) > 1:
            ax2 = self.fig.get_axes()[1]
            try:
                x2, y2 = ax2.transData.inverted().transform((ex, ey))
                msg = "X,Y,Y2= %g, %g, %g" % (x, y, y2)
            except:
                pass

        nsbar = getattr(self, 'nstatusbar', 1)
        self.write_message(msg,  panel=max(0, nsbar - 2))
        if (self.cursor_callback is not None and
            hasattr(self.cursor_callback , '__call__')):
            self.cursor_callback(x=event.xdata, y=event.ydata)