class PlotCanvas(QtCore.QObject):
    """
    Class handling the plotting area in the application.
    """

    # Signals:
    # Request for new bitmap to display. The parameter
    # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
    update_screen_request = QtCore.pyqtSignal(list)

    def __init__(self, container, app):
        """
        The constructor configures the Matplotlib figure that
        will contain all plots, creates the base axes and connects
        events to the plotting area.

        :param container: The parent container in which to draw plots.
        :rtype: PlotCanvas
        """

        super(PlotCanvas, self).__init__()

        self.app = app

        # Options
        self.x_margin = 15  # pixels
        self.y_margin = 25  # Pixels

        # Parent container
        self.container = container

        # Plots go onto a single matplotlib.figure
        self.figure = Figure(dpi=50)  # TODO: dpi needed?
        self.figure.patch.set_visible(False)

        # These axes show the ticks and grid. No plotting done here.
        # New axes must have a label, otherwise mpl returns an existing one.
        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
        self.axes.set_aspect(1)
        self.axes.grid(True)
        self.axes.axhline(color='Black')
        self.axes.axvline(color='Black')

        # The canvas is the top level container (FigureCanvasQTAgg)
        self.canvas = FigureCanvas(self.figure)
        # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
        # self.canvas.setFocus()

        #self.canvas.set_hexpand(1)
        #self.canvas.set_vexpand(1)
        #self.canvas.set_can_focus(True)  # For key press

        # Attach to parent
        #self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
        self.container.addWidget(self.canvas)  # Qt

        # Copy a bitmap of the canvas for quick animation.
        # Update every time the canvas is re-drawn.
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)

        ### Bitmap Cache
        self.cache = CanvasCache(self, self.app)
        self.cache_thread = QtCore.QThread()
        self.cache.moveToThread(self.cache_thread)
        #super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
        # self.connect()
        self.cache_thread.start()
        self.cache.new_screen.connect(self.on_new_screen)

        # Events
        self.canvas.mpl_connect('button_press_event', self.on_mouse_press)
        self.canvas.mpl_connect('button_release_event', self.on_mouse_release)
        self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        #self.canvas.connect('configure-event', self.auto_adjust_axes)
        self.canvas.mpl_connect('resize_event', self.auto_adjust_axes)
        #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
        #self.canvas.connect("scroll-event", self.on_scroll)
        self.canvas.mpl_connect('scroll_event', self.on_scroll)
        self.canvas.mpl_connect('key_press_event', self.on_key_down)
        self.canvas.mpl_connect('key_release_event', self.on_key_up)
        self.canvas.mpl_connect('draw_event', self.on_draw)

        self.mouse = [0, 0]
        self.key = None

        self.pan_axes = []
        self.panning = False

    def on_new_screen(self):

        log.debug("Cache updated the screen!")

    def on_key_down(self, event):
        """

        :param event:
        :return:
        """
        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
        self.key = event.key

    def on_key_up(self, event):
        """

        :param event:
        :return:
        """
        self.key = None

    def mpl_connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the Matplotlib interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: func
        :return: Connection id
        :rtype: int
        """
        return self.canvas.mpl_connect(event_name, callback)

    def mpl_disconnect(self, cid):
        """
        Disconnect callback with the give id.
        :param cid: Callback id.
        :return: None
        """
        self.canvas.mpl_disconnect(cid)

    def connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the native Qt interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: function
        :return: Nothing
        """
        self.canvas.connect(event_name, callback)

    def clear(self):
        """
        Clears axes and figure.

        :return: None
        """

        # Clear
        self.axes.cla()
        try:
            self.figure.clf()
        except KeyError:
            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")

        # Re-build
        self.figure.add_axes(self.axes)
        self.axes.set_aspect(1)
        self.axes.grid(True)

        # Re-draw
        self.canvas.draw_idle()

    def adjust_axes(self, xmin, ymin, xmax, ymax):
        """
        Adjusts all axes while maintaining the use of the whole canvas
        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
        request that will be modified to fit these restrictions.

        :param xmin: Requested minimum value for the X axis.
        :type xmin: float
        :param ymin: Requested minimum value for the Y axis.
        :type ymin: float
        :param xmax: Requested maximum value for the X axis.
        :type xmax: float
        :param ymax: Requested maximum value for the Y axis.
        :type ymax: float
        :return: None
        """

        # FlatCAMApp.App.log.debug("PC.adjust_axes()")

        width = xmax - xmin
        height = ymax - ymin
        try:
            r = width / height
        except ZeroDivisionError:
            FlatCAMApp.App.log.error("Height is %f" % height)
            return
        canvas_w, canvas_h = self.canvas.get_width_height()
        canvas_r = float(canvas_w) / canvas_h
        x_ratio = float(self.x_margin) / canvas_w
        y_ratio = float(self.y_margin) / canvas_h

        if r > canvas_r:
            ycenter = (ymin + ymax) / 2.0
            newheight = height * r / canvas_r
            ymin = ycenter - newheight / 2.0
            ymax = ycenter + newheight / 2.0
        else:
            xcenter = (xmax + xmin) / 2.0
            newwidth = width * canvas_r / r
            xmin = xcenter - newwidth / 2.0
            xmax = xcenter + newwidth / 2.0

        # Adjust axes
        for ax in self.figure.get_axes():
            if ax._label != 'base':
                ax.set_frame_on(False)  # No frame
                ax.set_xticks([])  # No tick
                ax.set_yticks([])  # No ticks
                ax.patch.set_visible(False)  # No background
                ax.set_aspect(1)
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))
            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])

        # Sync re-draw to proper paint on form resize
        self.canvas.draw()

        ##### Temporary place-holder for cached update #####
        self.update_screen_request.emit([0, 0, 0, 0, 0])

    def auto_adjust_axes(self, *args):
        """
        Calls ``adjust_axes()`` using the extents of the base axes.

        :rtype : None
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        self.adjust_axes(xmin, ymin, xmax, ymax)

    def zoom(self, factor, center=None):
        """
        Zooms the plot by factor around a given
        center point. Takes care of re-drawing.

        :param factor: Number by which to scale the plot.
        :type factor: float
        :param center: Coordinates [x, y] of the point around which to scale the plot.
        :type center: list
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        if center is None or center == [None, None]:
            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]

        # For keeping the point at the pointer location
        relx = (xmax - center[0]) / width
        rely = (ymax - center[1]) / height

        new_width = width / factor
        new_height = height / factor

        xmin = center[0] - new_width * (1 - relx)
        xmax = center[0] + new_width * relx
        ymin = center[1] - new_height * (1 - rely)
        ymax = center[1] + new_height * rely

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))

        # Async re-draw
        self.canvas.draw_idle()

        ##### Temporary place-holder for cached update #####
        self.update_screen_request.emit([0, 0, 0, 0, 0])

    def pan(self, x, y):
        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin + x * width, xmax + x * width))
            ax.set_ylim((ymin + y * height, ymax + y * height))

        # Re-draw
        self.canvas.draw_idle()

        ##### Temporary place-holder for cached update #####
        self.update_screen_request.emit([0, 0, 0, 0, 0])

    def new_axes(self, name):
        """
        Creates and returns an Axes object attached to this object's Figure.

        :param name: Unique label for the axes.
        :return: Axes attached to the figure.
        :rtype: Axes
        """

        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)

    def on_scroll(self, event):
        """
        Scroll event handler.

        :param event: Event object containing the event information.
        :return: None
        """

        # So it can receive key presses
        # self.canvas.grab_focus()
        self.canvas.setFocus()

        # Event info
        # z, direction = event.get_scroll_direction()

        if self.key is None:

            if event.button == 'up':
                self.zoom(1.5, self.mouse)
            else:
                self.zoom(1 / 1.5, self.mouse)
            return

        if self.key == 'shift':

            if event.button == 'up':
                self.pan(0.3, 0)
            else:
                self.pan(-0.3, 0)
            return

        if self.key == 'control':

            if event.button == 'up':
                self.pan(0, 0.3)
            else:
                self.pan(0, -0.3)
            return

    def on_mouse_press(self, event):

        # Check for middle mouse button press
        if event.button == self.app.mouse_pan_button:

            # Prepare axes for pan (using 'matplotlib' pan function)
            self.pan_axes = []
            for a in self.figure.get_axes():
                if (event.x is not None and event.y is not None and a.in_axes(event) and
                        a.get_navigate() and a.can_pan()):
                    a.start_pan(event.x, event.y, 1)
                    self.pan_axes.append(a)

            # Set pan view flag
            if len(self.pan_axes) > 0: self.panning = True;

    def on_mouse_release(self, event):

        # Check for middle mouse button release to complete pan procedure
        if event.button == self.app.mouse_pan_button:
            for a in self.pan_axes:
                a.end_pan()

            # Clear pan flag
            self.panning = False

    def on_mouse_move(self, event):
        """
        Mouse movement event hadler. Stores the coordinates. Updates view on pan.

        :param event: Contains information about the event.
        :return: None
        """
        self.mouse = [event.xdata, event.ydata]

        # Update pan view on mouse move
        if self.panning is True:
            for a in self.pan_axes:
                a.drag_pan(1, event.key, event.x, event.y)

            # Async re-draw (redraws only on thread idle state, uses timer on backend)
            self.canvas.draw_idle()

            ##### Temporary place-holder for cached update #####
            self.update_screen_request.emit([0, 0, 0, 0, 0])

    def on_draw(self, renderer):

        # Store background on canvas redraw
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)

    def get_axes_pixelsize(self):
        """
        Axes size in pixels.

        :return: Pixel width and height
        :rtype: tuple
        """
        bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
        width, height = bbox.width, bbox.height
        width *= self.figure.dpi
        height *= self.figure.dpi
        return width, height

    def get_density(self):
        """
        Returns unit length per pixel on horizontal
        and vertical axes.

        :return: X and Y density
        :rtype: tuple
        """
        xpx, ypx = self.get_axes_pixelsize()

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        return width / xpx, height / ypx
예제 #2
0
    def update_plot(self):
        self.assign_parameters()
        ax = self.fig.gca(facecolor=self.background_color)
        ax.clear()
        ax.plot(self.plot_data[1, :],
                self.plot_data[0, :],
                color=self.linecolor,
                linestyle=self.linestyle,
                label=self.label,
                linewidth=self.lineswidth)

        # Set Legend
        if (len(self.label) > 0):
            legend = ax.legend(prop={
                'size': self.legend_font_size,
                'family': self.legend_font.family(),
                'style': self.legend_style,
                'weight': self.legend_weight
            },
                               facecolor=self.legend_facecolor,
                               edgecolor=self.legend_edgecolor,
                               loc=self.legend_pos)
            for text in legend.get_texts():
                text.set_color(self.legend_color)

        # Set title
        ax.set_title(self.title,
                     size=self.title_font_size,
                     family=self.title_font.family(),
                     style=self.title_style,
                     weight=self.title_weight,
                     color=self.title_color,
                     loc=self.title_horizontal_ali,
                     y=1 + (self.title_pad / 100))

        # Set x- and y-labeling
        ax.set_xlabel(self.xlbl,
                      labelpad=self.xy_labelpad,
                      size=self.xy_font_size,
                      family=self.xy_font.family(),
                      style=self.xy_style,
                      weight=self.xy_weight,
                      color=self.xy_color)
        ax.set_ylabel(self.ylbl,
                      labelpad=self.xy_labelpad,
                      size=self.xy_font_size,
                      family=self.xy_font.family(),
                      style=self.xy_style,
                      weight=self.xy_weight,
                      color=self.xy_color)

        scene = QtGui.QGraphicsScene()

        self.fig.tight_layout()
        canvas = FigureCanvas(self.fig)
        scene.addWidget(canvas)

        # resizing and fitting of GraphicsView and Scene
        self.PlotView.setFixedSize(canvas.get_width_height()[0] + 2,
                                   canvas.get_width_height()[1] + 2)
        self.PlotView.setSceneRect(0, 0,
                                   canvas.get_width_height()[0],
                                   canvas.get_width_height()[1])
        self.PlotView.fitInView(0, 0,
                                canvas.get_width_height()[0],
                                canvas.get_width_height()[1])
        self.PlotView.setScene(scene)

        # resizing the Window
        width = self.PlotEditor.minimumWidth() + canvas.get_width_height()[0]
        dialog_width = width if (width > 880) else 880
        window_size = dialog_width, self.PlotEditor.geometry().height()
        self.PlotEditor.resize(window_size[0], window_size[1])
예제 #3
0
class CourseMapDialog(QtGui.QDialog):
    """Implements a dialog box to display course maps."""

    time_slider_changed = QtCore.Signal(float)

    def __init__(self, parent):
        QtGui.QDialog.__init__(self, parent)
        self._ui = ui_course_map_dialog.Ui_Dialog()
        self._ui.setupUi(self)

        params = matplotlib.figure.SubplotParams(0, 0, 1, 1, 0, 0)
        self._figure = matplotlib.figure.Figure(subplotpars=params)
        self._canvas = FigureCanvas(self._figure)
        self._canvas.mpl_connect('motion_notify_event', self._handle_mouse)
        self._canvas.mpl_connect('scroll_event', self._handle_scroll)
        self._canvas.mpl_connect('button_release_event',
                                 self._handle_mouse_release)
        self._mouse_start = None

        # Make QT drawing not be super slow.  See:
        # https://github.com/matplotlib/matplotlib/issues/2559/
        def draw():
            FigureCanvas.draw(self._canvas)
            self._canvas.repaint()

        self._canvas.draw = draw
        self._plot = self._figure.add_subplot(111)

        self._gdal_source = course_gdal.GdalSource()
        self._gdal = None

        layout = QtGui.QVBoxLayout(self._ui.mapFrame)
        layout.addWidget(self._canvas, 1)

        self._log_data = dict()

        # TODO sammy make COLORS a project wide config
        self._COLORS = 'rgbcmyk'
        self._next_color = 0
        self._bounds = ((0, 0), (0, 0))
        self._time_current = 0
        self._total_time = 0
        self._ui.timeSlider.valueChanged.connect(self._handle_time_slider)

    def add_log(self, log_name, log):
        log_data = _LogMapData(log_name, log, self._COLORS[self._next_color])
        self._log_data[log_name] = log_data
        self._next_color = (self._next_color + 1) % len(self._COLORS)

        if self._gdal is None:
            self._gdal = self._gdal_source.get_gdal(
                log_data.utm_data[0][1], log_data.utm_data[0][2])
            if self._gdal is not None:
                self._plot.imshow(self._gdal.image, extent=self._gdal.extent)

        self._plot.add_line(log_data.line)
        self._plot.add_line(log_data.marker)
        self._plot.legend(loc=2)
        self._update_scale()

    def update_sync(self):
        for log_data in self._log_data.itervalues():
            log_data.update_line_data()
            log_data.update_marker(self._time_current)

        self._update_scale()

    def _update_scale(self):
        self._plot.relim()
        minx = 1e10
        miny = 1e10
        maxx = -1e10
        maxy = -1e10
        max_time = 0

        for log_data in self._log_data.itervalues():
            bounds = log_data.bounds
            minx = min(minx, bounds[0][0])
            miny = min(miny, bounds[0][1])
            maxx = max(maxx, bounds[1][0])
            maxy = max(maxy, bounds[1][1])
            max_time = max(max_time, log_data.utm_data[-1][0])
        self._total_time = max_time

        x_size = maxx - minx
        y_size = maxy - miny
        xy_delta = x_size - y_size
        if xy_delta > 0:
            miny -= xy_delta / 2
            maxy += xy_delta / 2
        else:
            minx += xy_delta / 2
            maxx -= xy_delta / 2

        self._bounds = ((minx, miny), (maxx, maxy))
        self._plot.set_xlim(left=minx, right=maxx)
        self._plot.set_ylim(bottom=miny, top=maxy)
        self._canvas.draw()

    def _handle_mouse(self, event):
        if not event.inaxes or event.button != 1:
            return

        if self._mouse_start is None:
            self._mouse_start = event
            return

        # Handle a pan event.  What we want is for the point (data)
        # where the mouse was originally clicked to stay under the
        # pointer.
        (width, height) = self._canvas.get_width_height()
        px_x = (self._bounds[1][0] - self._bounds[0][0]) / width
        px_y = (self._bounds[1][1] - self._bounds[0][1]) / height
        x_change = (self._mouse_start.x - event.x) * px_x
        y_change = (self._mouse_start.y - event.y) * px_y
        self._plot.set_xlim(left=self._bounds[0][0] + x_change,
                            right=self._bounds[1][0] + x_change)
        self._plot.set_ylim(bottom=self._bounds[0][1] + y_change,
                            top=self._bounds[1][1] + y_change)
        self._canvas.draw()

    def _update_bounds(self):
        xlim = self._plot.get_xlim()
        ylim = self._plot.get_ylim()
        self._bounds = ((xlim[0], ylim[0]), (xlim[1], ylim[1]))

    def _handle_mouse_release(self, event):
        if event.button != 1:
            return
        self._mouse_start = None
        self._update_bounds()

    def _handle_scroll(self, event):
        # Determine the relative offset of the clicked position to the
        # center of the frame so that we can keep the data under the
        # cursor.
        (width, height) = self._canvas.get_width_height()
        x_off = float(width - event.x) / width
        y_off = float(height - event.y) / height
        x_scale = (self._bounds[1][0] - self._bounds[0][0]) * (event.step / 10.)
        y_scale = (self._bounds[1][1] - self._bounds[0][1]) * (event.step / 10.)

        # Check if we've tried to zoom to far and would invert our axes
        new_xlim = (self._bounds[0][0] + (x_scale * (1. - x_off)),
                    self._bounds[1][0] - (x_scale * x_off))
        new_ylim = (self._bounds[0][1] + (y_scale * (1. - y_off)),
                    self._bounds[1][1] - (y_scale * y_off))
        if (new_xlim[1] <= new_xlim[0]) or (new_ylim[1] <= new_ylim[0]):
            return

        self._plot.set_xlim(left=new_xlim[0], right=new_xlim[1])
        self._plot.set_ylim(bottom=new_ylim[0], top=new_ylim[1])
        self._update_bounds()
        self._canvas.draw()

    def update_time(self, new_time, update_slider=True):
        # Bound the time to our useful range.
        new_time = max(0, min(new_time, self._total_time))
        self._time_current = new_time
        for log_data in self._log_data.itervalues():
            log_data.update_marker(new_time)
        self._canvas.draw()
        self._ui.elapsedTime.setText(str(new_time))

        # if update_slider:
        #     self._ui.timeSlider.setValue(1000 * (new_time / self._total_time))

    def _handle_time_slider(self):
        if self._total_time == 0:
            return
        current = self._ui.timeSlider.value() / 1000.
        self.update_time(current * self._total_time, update_slider=False)
        self.time_slider_changed.emit(self._time_current)
예제 #4
0
class DrawingPad_Painter(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent=parent)

        #prepare data and figure
        self.traj_pnts = []
        self.curr_traj = None
        self.lines = []
        self.curr_line = None

        self.dpi = 100
        self.fig = Figure(figsize=(3.24, 5.0), dpi=self.dpi, facecolor="white")
        self.canvas = FigureCanvas(self.fig)
        self.ax_painter = self.fig.add_subplot(111, aspect='equal')
        self.ax_painter.hold(True)
        self.ax_painter.set_xlim([-2, 2])
        self.ax_painter.set_ylim([-2, 2])
        self.ax_painter.set_aspect('equal')
        self.ax_painter.set_xticks([])
        self.ax_painter.set_yticks([])
        self.ax_painter.axis('off')

        self.hbox_layout = QHBoxLayout()
        self.hbox_layout.addWidget(self.canvas, 5)

        self.line_width = 12.0

        # self.ctrl_pnl_layout = QVBoxLayout()
        # #a button to clear the figure
        # self.clean_btn = QPushButton('Clear')
        # self.ctrl_pnl_layout.addWidget(self.clean_btn)
        #
        # self.hbox_layout.addLayout(self.ctrl_pnl_layout, 1)

        self.setLayout(self.hbox_layout)
        self.drawing = False

        self.create_event_handler()
        return

    def create_event_handler(self):
        self.canvas_button_clicked_cid = self.canvas.mpl_connect('button_press_event', self.on_canvas_mouse_clicked)
        self.canvas_button_released_cid = self.canvas.mpl_connect('button_release_event', self.on_canvas_mouse_released)
        self.canvas_motion_notify_cid = self.canvas.mpl_connect('motion_notify_event', self.on_canvas_mouse_move)

        return

    def on_canvas_mouse_clicked(self, event):
        # print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
        # event.button, event.x, event.y, event.xdata, event.ydata)
        self.drawing = True
        # create a new line if we are drawing within the area
        if event.xdata is not None and event.ydata is not None and self.curr_line is None and self.curr_traj is None:
            self.curr_line, = self.ax_painter.plot([event.xdata], [event.ydata], '-k', linewidth=self.line_width)
            self.curr_traj = [np.array([event.xdata, event.ydata])]
            self.canvas.draw()
        return

    def on_canvas_mouse_released(self, event):
        # print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
        # event.button, event.x, event.y, event.xdata, event.ydata)
        self.drawing = False
        # store finished line and trajectory
        # print self.curr_traj
        self.lines.append(self.curr_line)
        self.traj_pnts.append(self.curr_traj)
        self.curr_traj = None
        self.curr_line = None
        return

    def on_clean(self, event):
        print 'clean the canvas...'
        #clear everything
        for line in self.ax_painter.lines:
            self.ax_painter.lines.remove(line)
        self.lines = []

        if self.curr_line is not None:
            self.ax_painter.lines.remove(self.curr_line)
        self.curr_line = None
        self.canvas.draw()

        self.traj_pnts = []
        self.curr_traj = None
        self.drawing = False
        return

    def on_canvas_mouse_move(self, event):
        if self.drawing:
            # print 'In movement: x=',event.x ,', y=', event.y,', xdata=',event.xdata,', ydata=', event.ydata
            if event.xdata is not None and event.ydata is not None and self.curr_line is not None and self.curr_traj is not None:
                #append new data and update drawing
                self.curr_traj.append(np.array([event.xdata, event.ydata]))
                tmp_curr_data = np.array(self.curr_traj)
                self.curr_line.set_xdata(tmp_curr_data[:, 0])
                self.curr_line.set_ydata(tmp_curr_data[:, 1])
                self.canvas.draw()
        return

    def plot_trajs_helper(self, trajs):
        tmp_lines = []
        for traj in trajs:
            tmp_line, = self.ax_painter.plot(traj[:, 0], traj[:, 1], '-.g', linewidth=self.line_width)
            tmp_lines.append(tmp_line)
            self.canvas.draw()
        #add these tmp_lines to lines record
        self.lines = self.lines + tmp_lines
        return

    def get_traj_data(self):
        return self.traj_pnts

    def get_image_data(self):
        """
        Get the deposited image
        """
        w,h = self.canvas.get_width_height()
        buf = np.fromstring ( self.canvas.tostring_argb(), dtype=np.uint8 )
        buf.shape = ( w, h, 4 )

        # canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
        buf = np.roll ( buf, 3, axis = 2 )
        return buf
예제 #5
0
class PlotCanvas:
    """
    Class handling the plotting area in the application.
    """

    def __init__(self, container):
        """
        The constructor configures the Matplotlib figure that
        will contain all plots, creates the base axes and connects
        events to the plotting area.

        :param container: The parent container in which to draw plots.
        :rtype: PlotCanvas
        """
        # Options
        self.x_margin = 15  # pixels
        self.y_margin = 25  # Pixels

        # Parent container
        self.container = container

        # Plots go onto a single matplotlib.figure
        self.figure = Figure(dpi=50)  # TODO: dpi needed?
        self.figure.patch.set_visible(False)

        # These axes show the ticks and grid. No plotting done here.
        # New axes must have a label, otherwise mpl returns an existing one.
        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
        self.axes.set_aspect(1)
        self.axes.grid(True)

        # The canvas is the top level container (Gtk.DrawingArea)
        self.canvas = FigureCanvas(self.figure)
        # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
        # self.canvas.setFocus()

        #self.canvas.set_hexpand(1)
        #self.canvas.set_vexpand(1)
        #self.canvas.set_can_focus(True)  # For key press

        # Attach to parent
        #self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
        self.container.addWidget(self.canvas)  # Qt

        # Copy a bitmap of the canvas for quick animation.
        # Update every time the canvas is re-drawn.
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)

        # Events
        self.canvas.mpl_connect('button_press_event', self.on_mouse_press)
        self.canvas.mpl_connect('button_release_event', self.on_mouse_release)
        self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        #self.canvas.connect('configure-event', self.auto_adjust_axes)
        self.canvas.mpl_connect('resize_event', self.auto_adjust_axes)
        #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
        #self.canvas.connect("scroll-event", self.on_scroll)
        self.canvas.mpl_connect('scroll_event', self.on_scroll)
        self.canvas.mpl_connect('key_press_event', self.on_key_down)
        self.canvas.mpl_connect('key_release_event', self.on_key_up)
        self.canvas.mpl_connect('draw_event', self.on_draw)

        self.mouse = [0, 0]
        self.key = None

        self.pan_axes = []
        self.panning = False

    def on_key_down(self, event):
        """

        :param event:
        :return:
        """
        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
        self.key = event.key

    def on_key_up(self, event):
        """

        :param event:
        :return:
        """
        self.key = None

    def mpl_connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the Matplotlib interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: func
        :return: Connection id
        :rtype: int
        """
        return self.canvas.mpl_connect(event_name, callback)

    def mpl_disconnect(self, cid):
        """
        Disconnect callback with the give id.
        :param cid: Callback id.
        :return: None
        """
        self.canvas.mpl_disconnect(cid)

    def connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the native GTK interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: function
        :return: Nothing
        """
        self.canvas.connect(event_name, callback)

    def clear(self):
        """
        Clears axes and figure.

        :return: None
        """

        # Clear
        self.axes.cla()
        try:
            self.figure.clf()
        except KeyError:
            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")

        # Re-build
        self.figure.add_axes(self.axes)
        self.axes.set_aspect(1)
        self.axes.grid(True)

        # Re-draw
        self.canvas.draw_idle()

    def adjust_axes(self, xmin, ymin, xmax, ymax):
        """
        Adjusts all axes while maintaining the use of the whole canvas
        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
        request that will be modified to fit these restrictions.

        :param xmin: Requested minimum value for the X axis.
        :type xmin: float
        :param ymin: Requested minimum value for the Y axis.
        :type ymin: float
        :param xmax: Requested maximum value for the X axis.
        :type xmax: float
        :param ymax: Requested maximum value for the Y axis.
        :type ymax: float
        :return: None
        """

        # FlatCAMApp.App.log.debug("PC.adjust_axes()")

        width = xmax - xmin
        height = ymax - ymin
        try:
            r = width / height
        except ZeroDivisionError:
            FlatCAMApp.App.log.error("Height is %f" % height)
            return
        canvas_w, canvas_h = self.canvas.get_width_height()
        canvas_r = float(canvas_w) / canvas_h
        x_ratio = float(self.x_margin) / canvas_w
        y_ratio = float(self.y_margin) / canvas_h

        if r > canvas_r:
            ycenter = (ymin + ymax) / 2.0
            newheight = height * r / canvas_r
            ymin = ycenter - newheight / 2.0
            ymax = ycenter + newheight / 2.0
        else:
            xcenter = (xmax + xmin) / 2.0
            newwidth = width * canvas_r / r
            xmin = xcenter - newwidth / 2.0
            xmax = xcenter + newwidth / 2.0

        # Adjust axes
        for ax in self.figure.get_axes():
            if ax._label != 'base':
                ax.set_frame_on(False)  # No frame
                ax.set_xticks([])  # No tick
                ax.set_yticks([])  # No ticks
                ax.patch.set_visible(False)  # No background
                ax.set_aspect(1)
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))
            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])

        # Sync re-draw to proper paint on form resize
        self.canvas.draw()

    def auto_adjust_axes(self, *args):
        """
        Calls ``adjust_axes()`` using the extents of the base axes.

        :rtype : None
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        self.adjust_axes(xmin, ymin, xmax, ymax)

    def zoom(self, factor, center=None):
        """
        Zooms the plot by factor around a given
        center point. Takes care of re-drawing.

        :param factor: Number by which to scale the plot.
        :type factor: float
        :param center: Coordinates [x, y] of the point around which to scale the plot.
        :type center: list
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        if center is None or center == [None, None]:
            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]

        # For keeping the point at the pointer location
        relx = (xmax - center[0]) / width
        rely = (ymax - center[1]) / height

        new_width = width / factor
        new_height = height / factor

        xmin = center[0] - new_width * (1 - relx)
        xmax = center[0] + new_width * relx
        ymin = center[1] - new_height * (1 - rely)
        ymax = center[1] + new_height * rely

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))

        # Async re-draw
        self.canvas.draw_idle()

    def pan(self, x, y):
        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin + x * width, xmax + x * width))
            ax.set_ylim((ymin + y * height, ymax + y * height))

        # Re-draw
        self.canvas.draw_idle()

    def new_axes(self, name):
        """
        Creates and returns an Axes object attached to this object's Figure.

        :param name: Unique label for the axes.
        :return: Axes attached to the figure.
        :rtype: Axes
        """

        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)

    def on_scroll(self, event):
        """
        Scroll event handler.

        :param event: Event object containing the event information.
        :return: None
        """

        # So it can receive key presses
        # self.canvas.grab_focus()
        self.canvas.setFocus()

        # Event info
        # z, direction = event.get_scroll_direction()

        if self.key is None:

            if event.button == 'up':
                self.zoom(1.5, self.mouse)
            else:
                self.zoom(1 / 1.5, self.mouse)
            return

        if self.key == 'shift':

            if event.button == 'up':
                self.pan(0.3, 0)
            else:
                self.pan(-0.3, 0)
            return

        if self.key == 'control':

            if event.button == 'up':
                self.pan(0, 0.3)
            else:
                self.pan(0, -0.3)
            return

    def on_mouse_press(self, event):

        # Check for middle mouse button press
        if event.button == 2:

            # Prepare axes for pan (using 'matplotlib' pan function)
            self.pan_axes = []
            for a in self.figure.get_axes():
                if (event.x is not None and event.y is not None and a.in_axes(event) and
                        a.get_navigate() and a.can_pan()):
                    a.start_pan(event.x, event.y, 1)
                    self.pan_axes.append(a)

            # Set pan view flag
            if len(self.pan_axes) > 0:
                self.panning = True;

    def on_mouse_release(self, event):

        # Check for middle mouse button release to complete pan procedure
        if event.button == 2:
            for a in self.pan_axes:
                a.end_pan()

            # Clear pan flag
            self.panning = False

    def on_mouse_move(self, event):
        """
        Mouse movement event hadler. Stores the coordinates. Updates view on pan.

        :param event: Contains information about the event.
        :return: None
        """
        self.mouse = [event.xdata, event.ydata]

        # Update pan view on mouse move
        if self.panning is True:
            for a in self.pan_axes:
                a.drag_pan(1, event.key, event.x, event.y)

            # Async re-draw (redraws only on thread idle state, uses timer on backend)
            self.canvas.draw_idle()

    def on_draw(self, renderer):

        # Store background on canvas redraw
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)
예제 #6
0
class MatplotlibWithNavigationWidget(QWidget):
    """
    MatplotlibWidget inherits PyQt4.QtGui.QHBoxLayout

    Options: option_name (default_value)
    -------
    parent (None): parent widget
    title (''): figure title
    xlabel (''): X-axis label
    ylabel (''): Y-axis label
    xlim (None): X-axis limits ([min, max])
    ylim (None): Y-axis limits ([min, max])
    xscale ('linear'): X-axis scale
    yscale ('linear'): Y-axis scale
    width (4): width in inches
    height (3): height in inches
    dpi (100): resolution in dpi
    hold (False): if False, figure will be cleared each time plot is called

    Widget attributes:
    -----------------
    figure: instance of matplotlib.figure.Figure
    axes: figure axes

    Example:
    -------
    self.widget = MatplotlibWidget(self, yscale='log', hold=True)
    from numpy import linspace
    x = linspace(-10, 10)
    self.widget.axes.plot(x, x**2)
    self.wdiget.axes.plot(x, x**3)
    """
    def __init__(self,
                 parent=None,
                 title='',
                 xlabel='',
                 ylabel='',
                 xlim=None,
                 ylim=None,
                 xscale='linear',
                 yscale='linear',
                 width=4,
                 height=3,
                 dpi=100,
                 hold=False):
        self.figure = Figure(figsize=(width, height), dpi=dpi)
        self.axes = self.figure.add_subplot(111)
        self.axes.set_title(title)
        self.axes.set_xlabel(xlabel)
        self.axes.set_ylabel(ylabel)
        if xscale is not None:
            self.axes.set_xscale(xscale)
        if yscale is not None:
            self.axes.set_yscale(yscale)
        if xlim is not None:
            self.axes.set_xlim(*xlim)
        if ylim is not None:
            self.axes.set_ylim(*ylim)
        #self.axes.hold(hold)

        QWidget.__init__(self, parent)

        self.canvas = FigureCanvas(self.figure)
        self.mpl_toolbar = NavigationToolbar(self.canvas, self)
        self.vbox = QVBoxLayout(self)
        self.vbox.addWidget(self.canvas, 1)
        self.vbox.addWidget(self.mpl_toolbar)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def sizeHint(self):
        w, h = self.canvas.get_width_height()
        return QSize(w, h)  # +6 added by Ghislain

    def minimumSizeHint(self):
        return QSize(10, 10)
예제 #7
0
    def update_plot(self):
        self.assign_parameters()
        ax = self.fig.gca(facecolor=self.background_color)
        ax.clear()
        ax.plot(self.plot_data[1, :],
                self.plot_data[0, :],
                color=self.linecolor,
                linestyle=self.linestyle,
                label=self.label,
                linewidth=self.lineswidth)

        # Set Legend
        if (len(self.label) > 0):
            legend = ax.legend(prop={
                'size': self.legend_font_size,
                'family': self.legend_font.family(),
                'style': self.legend_style,
                'weight': self.legend_weight
            },
                               facecolor=self.legend_facecolor,
                               edgecolor=self.legend_edgecolor,
                               loc=self.legend_pos)
            for text in legend.get_texts():
                text.set_color(self.legend_color)

        # Set title
        ax.set_title(self.title,
                     size=self.title_font_size,
                     family=self.title_font.family(),
                     style=self.title_style,
                     weight=self.title_weight,
                     color=self.title_color,
                     loc=self.title_horizontal_ali,
                     y=1 + (self.title_pad / 100))

        # Set x- and y-labeling
        ax.set_xlabel(self.xlbl,
                      labelpad=self.xy_labelpad,
                      size=self.xy_font_size,
                      family=self.xy_font.family(),
                      style=self.xy_style,
                      weight=self.xy_weight,
                      color=self.xy_color)
        ax.set_ylabel(self.ylbl,
                      labelpad=self.xy_labelpad,
                      size=self.xy_font_size,
                      family=self.xy_font.family(),
                      style=self.xy_style,
                      weight=self.xy_weight,
                      color=self.xy_color)

        width_pixels, height_pixels, xfak, yfak = self.compute_widht_height_pixels(
        )
        scene = QtGui.QGraphicsScene()

        self.fig.tight_layout()
        canvas = FigureCanvas(self.fig)
        scene.addWidget(canvas)

        self.PlotView.setFixedSize(canvas.get_width_height()[0] + 2,
                                   canvas.get_width_height()[1] + 2)
        self.PlotView.setSceneRect(0, 0,
                                   canvas.get_width_height()[0],
                                   canvas.get_width_height()[1])
        self.PlotView.fitInView(0, 0,
                                canvas.get_width_height()[0],
                                canvas.get_width_height()[1])
        self.PlotView.setScene(scene)
예제 #8
0
    import sys

    app = QtGui.QApplication(sys.argv)
    imageViewer = ImageViewer()
    imageViewer.show()
    
    figure = plt.figure()
    figure.set_size_inches(11, 8.5)
    figure.patch.set_facecolor('white')
    plt.plot([1, 2, 3, 4], [1, 2, 3, 4], '-b')

    figure_canvas = FigureCanvasQTAgg(figure)
    figure_canvas.draw()
            
    size = figure_canvas.size()
    width, height = size.width(), size.height()
    print(width, height)
    print(figure_canvas.get_width_height())
    imgbuffer = figure_canvas.buffer_rgba()
    image = QtGui.QImage(imgbuffer, width, height,
                         QtGui.QImage.Format_ARGB32)
                         
    # Reference for the RGB to BGR swap:
    # http://sourceforge.net/p/matplotlib/mailman/message/5194542/
      
    image = QtGui.QImage.rgbSwapped(image)    
    imageViewer.load_image(image, 0)
    
    
    sys.exit(app.exec_())