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