示例#1
0
def init_plot():
    dpi = 100
    #  class matplotlib.figure.Figure(figsize=None,
    #                                 dpi=None, facecolor=None,
    #                                 edgecolor=None, linewidth=1.0,
    #                                 frameon=True, subplotpars=None)
    fig = Figure(figsize=(3.0, 3.0), dpi=dpi)
    canvas = FigCanvas(fig)
    canvas.add_events(Gdk.EventMask.SCROLL_MASK)

    rect = [.1, .01, .88, .85]
    horiz = [Size.Scaled(1.0)]
    vert = [
        Size.Fixed(0.23),
        Size.Fixed(.25),
        Size.Scaled(1.),
        Size.Fixed(.1),
        Size.Scaled(1.0)
    ]
    divider = Divider(fig, rect, horiz, vert, aspect=False)
    axes = dict()
    # ##### ANALOG AXES #####
    # The args to add_subplot seem to be
    #    # of rows
    #    # of columns
    #    # pos of this plot in row order
    axes['analog'] = fig.add_axes(rect, label='analog', navigate=True)
    axes['analog'].set_axes_locator(divider.new_locator(nx=0, ny=4))
    #axes['analog'].set_axis_bgcolor('black')
    axes['analog'].set_ylabel('Output (V)')
    #axes['analog'].xaxis.set_label_position('top')
    axes['analog'].xaxis.set_ticks_position('top')
    #axes['analog'].set_xlabel('Time (s)')

    # ##### DIGITAL AXES #####
    axes['digital'] = fig.add_axes(rect,
                                   label='digital',
                                   navigate=True,
                                   sharex=axes['analog'])
    axes['digital'].set_axes_locator(divider.new_locator(nx=0, ny=2))
    #axes['digital'].xaxis.set_ticks_position('top')

    # ##### SCROLL INDICATOR AXES #####
    axes['t'] = fig.add_axes(rect, label='time')
    axes['t'].set_axes_locator(divider.new_locator(nx=0, ny=0))
    axes['t'].set_yticks(())
    #axes['t'].set_xlabel('Time (s)')
    axes['t'].set_xticklabels(())

    # set up GUI interactions and widgets
    axes['multi'] = MultiCursor(
        canvas,
        (axes['analog'], axes['digital'], axes['t']),
        color='r',
        lw=1,
        useblit=True,
    )

    axes['__scroll_master'] = ScrollMaster(axes['analog'], axes['digital'],
                                           axes['t'])

    axes['hspan-controls'] = dict()
    axes['vspan-controls'] = dict()

    # set useblit True on gtk3agg for enhanced performance
    axes['hspan-controls']['analog'] = SpanSelector(
        axes['analog'],
        axes['__scroll_master'].onselect_horizontal,
        'horizontal',
        useblit=True,
        rectprops=dict(alpha=0.5, facecolor='green'),
    )
    setattr(axes['hspan-controls']['analog'], 'visible', False)

    axes['vspan-controls']['analog'] = SpanSelector(
        axes['analog'],
        axes['__scroll_master'].onselect_vertical1,
        'vertical',
        useblit=True,
        rectprops=dict(alpha=0.5, facecolor='green'),
    )
    setattr(axes['vspan-controls']['analog'], 'visible', False)

    # set useblit True on gtk3agg for enhanced performance
    axes['hspan-controls']['digital'] = SpanSelector(
        axes['digital'],
        axes['__scroll_master'].onselect_horizontal,
        'horizontal',
        useblit=True,
        rectprops=dict(alpha=0.5, facecolor='green'),
    )
    setattr(axes['hspan-controls']['digital'], 'visible', False)

    axes['vspan-controls']['digital'] = SpanSelector(
        axes['digital'],
        axes['__scroll_master'].onselect_vertical2,
        'vertical',
        useblit=True,
        rectprops=dict(alpha=0.5, facecolor='green'),
    )
    setattr(axes['vspan-controls']['digital'], 'visible', False)

    # set useblit True on gtk3agg for enhanced performance
    axes['hspan-controls']['t'] = SpanSelector(
        axes['t'],
        axes['__scroll_master'].onselect_horizontal,
        'horizontal',
        useblit=False,
        rectprops=dict(alpha=0.5, facecolor='red'),
    )
    setattr(axes['hspan-controls']['t'], 'visible', False)

    canvas.mpl_connect('scroll_event', axes['__scroll_master'].onscroll)
    #canvas.connect('key-press-event', axes['__scroll_master'].onpress)
    zoom_effect(axes['digital'], axes['t'])

    # plot the data as a line series, and save the reference
    # to the plotted line series
    #
    #self.vector = [0]
    #self.plot_data = axes.plot(
    #    self.vector,
    #    linewidth=4,
    #    color=(1, 1, 0),
    #    marker='o',
    #    label="set1",
    #    )[0]

    #self.vector2 = [0]
    #self.plot_data2 = axes.plot(
    #    self.vector2,
    #    linewidth=2,
    #    dashes=[.2,.4],
    #    color=(0, 0, 1),
    #    label="set2",
    #    )[0]

    # leg = axes.legend()
    # # the matplotlib.patches.Rectangle instance surrounding the legend
    # frame  = leg.get_frame()
    # frame.set_facecolor('0.80')    # set the frame face color to light gray
    return axes, fig, canvas
示例#2
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.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??

        # Events
        self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        self.canvas.connect('configure-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('key_press_event', self.on_key_down)
        self.canvas.mpl_connect('key_release_event', self.on_key_up)

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

    def on_key_down(self, event):
        """

        :param event:
        :return:
        """
        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.queue_draw()

    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])

        # Re-draw
        self.canvas.queue_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))

        # Re-draw
        self.canvas.queue_draw()

    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.queue_draw()

    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, canvas, event):
        """
        Scroll event handler.

        :param canvas: The widget generating the event. Ignored.
        :param event: Event object containing the event information.
        :return: None
        """

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

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

        if self.key is None:

            if direction is Gdk.ScrollDirection.UP:
                self.zoom(1.5, self.mouse)
            else:
                self.zoom(1 / 1.5, self.mouse)
            return

        if self.key == 'shift':

            if direction is Gdk.ScrollDirection.UP:
                self.pan(0.3, 0)
            else:
                self.pan(-0.3, 0)
            return

        if self.key == 'ctrl+control':

            if direction is Gdk.ScrollDirection.UP:
                self.pan(0, 0.3)
            else:
                self.pan(0, -0.3)
            return

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

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