def mathTex_to_QPixmap(mathTex, fs): ''' E/ mathTEx : The formula to be displayed on screen in LaTeX E/ fs : The desired font size S/ qpixmap : The image to be displayed in Qpixmap format ''' # Set up a mpl figure instance # fig = Figure() fig.patch.set_facecolor('none') fig.set_canvas(FigureCanvas(fig)) renderer = fig.canvas.get_renderer() # Plot the mathTex expression # ax = fig.add_axes([0, 0, 1, 1]) ax.axis('off') ax.patch.set_facecolor('none') t = ax.text(0, 0, mathTex, ha='left', va='bottom', fontsize=fs) # Fit figure size to text artist # fwidth, fheight = fig.get_size_inches() fig_bbox = fig.get_window_extent(renderer) text_bbox = t.get_window_extent(renderer) tight_fwidth = text_bbox.width * fwidth / fig_bbox.width tight_fheight = text_bbox.height * fheight / fig_bbox.height fig.set_size_inches(tight_fwidth, tight_fheight) # Convert mpl figure to QPixmap # buf, size = fig.canvas.print_to_buffer() qimage = QtGui.QImage.rgbSwapped(QtGui.QImage(buf, size[0], size[1], QtGui.QImage.Format_ARGB32)) qpixmap = QtGui.QPixmap(qimage) return qpixmap
def mathTex_to_QPixmap(mathTex, fs): # ---- set up a mpl figure instance ---- fig = Figure() fig.patch.set_facecolor('none') fig.set_canvas(FigureCanvasAgg(fig)) renderer = fig.canvas.get_renderer() # ---- plot the mathTex expression ---- ax = fig.add_axes([0, 0, 1, 1]) ax.axis('off') ax.patch.set_facecolor('none') t = ax.text(0, 0, mathTex, ha='left', va='bottom', fontsize=fs) # ---- fit figure size to text artist ---- fwidth, fheight = fig.get_size_inches() fig_bbox = fig.get_window_extent(renderer) text_bbox = t.get_window_extent(renderer) tight_fwidth = text_bbox.width * fwidth / fig_bbox.width tight_fheight = text_bbox.height * fheight / fig_bbox.height fig.set_size_inches(tight_fwidth, tight_fheight) # ---- convert mpl figure to QPixmap ---- buf, size = fig.canvas.print_to_buffer() qimage = QtGui.QImage.rgbSwapped( QtGui.QImage(buf, size[0], size[1], QtGui.QImage.Format_ARGB32)) qpixmap = QtGui.QPixmap(qimage) return qpixmap
def latex_to_qtpixmap(str_, fontsize_=None): figure_ = Figure() figure_.set_canvas(FigureCanvasAgg(figure_)) figure_.patch.set_facecolor("none") ax_ = figure_.add_axes([0, 0, 1, 1]) ax_.axis('off') text_ = ax_.text(0, 0, str_, ha='left', va='bottom', fontsize=fontsize_) f_w_, f_h_ = figure_.get_size_inches() bbox_f_ = figure_.get_window_extent(figure_.canvas.get_renderer()) bbox_t_ = text_.get_window_extent(figure_.canvas.get_renderer()) figure_.set_size_inches((bbox_t_.width / bbox_f_.width) * f_w_, (bbox_t_.height / bbox_f_.height) * f_h_) buf, size = figure_.canvas.print_to_buffer() return QtGui.QImage.rgbSwapped( QtGui.QImage(buf, *size, QtGui.QImage.Format_ARGB32))
class MplCanvas(FigureCanvasQTAgg_modified): """Class to represent the FigureCanvas widget""" def __init__(self): # setup Matplotlib Figure and Axis self.fig = Figure() bbox = self.fig.get_window_extent().transformed( self.fig.dpi_scale_trans.inverted()) width, height = bbox.width * self.fig.dpi, bbox.height * self.fig.dpi self.fig.subplots_adjust( left=40 / width, bottom=20 / height, right=1 - 5 / width, top=1 - 30 / height, hspace=0.0) # left=0.07, right=0.98, # top=0.94, bottom=0.07, hspace=0.0) self._define_axes(1) self.set_toNight(True) FigureCanvasQTAgg_modified.__init__(self, self.fig) FigureCanvasQTAgg_modified.setSizePolicy( self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) FigureCanvasQTAgg_modified.updateGeometry(self) def _define_axes(self, h_cake): self.gs = GridSpec(100, 1) self.ax_pattern = self.fig.add_subplot(self.gs[h_cake + 1:99, 0]) self.ax_cake = self.fig.add_subplot(self.gs[0:h_cake, 0], sharex=self.ax_pattern) self.ax_pattern.set_ylabel('Intensity (arbitrary unit)') self.ax_pattern.ticklabel_format( axis='y', style='sci', scilimits=(-2, 2)) self.ax_pattern.get_yaxis().get_offset_text().set_position( (-0.04, -0.1)) def resize_axes(self, h_cake): self.fig.clf() self._define_axes(h_cake) if h_cake == 1: self.ax_cake.tick_params( axis='y', colors=self.objColor, labelleft=False) self.ax_cake.spines['right'].set_visible(False) self.ax_cake.spines['left'].set_visible(False) self.ax_cake.spines['top'].set_visible(False) self.ax_cake.spines['bottom'].set_visible(False) elif h_cake >= 10: self.ax_cake.set_ylabel("Azimuth (degrees)") def set_toNight(self, NightView=True): if NightView: try: mplstyle.use( os.path.join(os.path.curdir, 'mplstyle', 'night.mplstyle')) except: mplstyle.use('dark_background') self.bgColor = 'black' self.objColor = 'white' else: try: mplstyle.use( os.path.join(os.path.curdir, 'mplstyle', 'day.mplstyle')) except: mplstyle.use('classic') self.bgColor = 'white' self.objColor = 'black' # self.fig.clf() # self.ax_pattern.cla() # Cursor(self.ax, useblit=True, color=self.objColor, linewidth=2 ) self.fig.set_facecolor(self.bgColor) self.ax_cake.tick_params(which='both', axis='x', colors=self.objColor, direction='in', labelbottom=False, labeltop=False) self.ax_cake.tick_params(axis='both', which='both', length=0) self.ax_pattern.xaxis.set_label_position('bottom')
class Panel_Plotting_Helper(wx.Panel): def __init__(self, parent): w, h = parent.GetSize() wx.Panel.__init__(self, parent=parent, size=(w, 0.7 * h), style=wx.SUNKEN_BORDER) self.parent = parent self.legends = [] self.legendpos = [0.5, 1] self.fig = Figure( figsize=(12, 6), dpi=90) # create a figure size 8x6 inches, 80 dots per inches self.splts = [] self.canvas = FigureCanvasWxAgg(self, -1, self.fig) self.toolbar = Toolbar(self.canvas) # matplotlib toolbar # additional toolbar status_txt = wx.StaticText(self.toolbar, label=' Status on hover: ', pos=(230, 7), \ size=(100, 17)) self.status = wx.TextCtrl(self.toolbar, pos=(330,4), size=(300, 22), \ style=wx.TE_READONLY) self.toolbar.Realize() self.figw, self.figh = self.fig.get_window_extent( ).width, self.fig.get_window_extent().height sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.toolbar, 0, wx.GROW) sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.SetSizer(sizer) self.box_width_fraction = 1.0 self.box_height_fraction = 0.9 self.lines = [] self.lined = dict() self.draggableList = ['Text', 'Legend'] # print(self.toolbar.GetBackgroundColour()) self.fig.canvas.mpl_connect('resize_event', self.squeeze_legend) self.fig.canvas.mpl_connect('pick_event', self.on_pick) self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion) self.fig.canvas.mpl_connect('figure_leave_event', self.on_leave) def plot_J(self, J, theta, format, r, count): index = count % 3 + 3 self.splts[index].plot(np.arange(len(J)), J, color=format['color'], linewidth=format['linewidth'], linestyle=format['linestyle'], label=format['label'], picker=True) self.splts[index].set_xlabel("Number of Iteration", fontsize=FONT_SIZE) self.splts[index].set_ylabel("Cost value", fontsize=FONT_SIZE) self.set_ticks(self.splts[index], np.arange(len(J)), J) comment = r + ': [\n' for t in theta: comment += ' ' + str(t) + '\n' comment += ']' props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) annotate = self.splts[index].annotate(comment, xy=(len(J)-1, J[len(J)-1]), xytext=(len(J)/2, (J[0]+J[len(J)-1])/2), \ arrowprops=dict(facecolor='black', shrink=0.05), bbox=props, fontsize=FONT_SIZE, picker=True) annotate.draggable(True) def plot_data_gradient_descent(self, X, y, format): print("Plotting data ...") for i in range(int(round(len(self.splts) / 2))): self.plot_data(self.splts[i], X, y, format) self.update_canvas() def plot_data_normal_equation(self, X, y, format): print("Plotting data ...") for i in range(int(round((len(self.splts) + 1) / 2))): self.plot_data(self.splts[i], X, y, format) self.update_canvas() def plot_data(self, splt, X, y, format): line, = splt.plot(X, y, 'ro', color=format['color'], label=format['label'], picker=True) self.set_ticks(splt, X, y) self.lines.append(line) splt.set_xlabel("X1", fontsize=FONT_SIZE) splt.set_ylabel("Y", fontsize=FONT_SIZE) def set_ticks(self, splt, X, y): xticks = self.make_ticks(X) yticks = self.make_ticks(y) splt.set_xticks(xticks) splt.set_yticks(yticks) for tick in splt.get_xticklabels(): tick.set_rotation(45) tick.set_fontsize(FONT_SIZE) for tick in splt.get_yticklabels(): tick.set_rotation(45) tick.set_fontsize(FONT_SIZE) def plot_all_gradient_descent(self, object): print( "Plotting Linear-Regression (Gradient Descent) and J-Convergence ..." ) count = 0 for r in object: c = self.random_color() self.splts[count].plot(object[r]['data']['x'], object[r]['data']['y'], color=c, linestyle="-", label="Linear Regression (alpha=" + r + ")", picker=True) self.set_ticks(self.splts[count], object[r]['data']['x'], object[r]['data']['y']) self.plot_J( object[r]['J_history'], object[r]['theta'], { "color": c, "linewidth": 5, "linestyle": "-", "label": "Convergence of J" }, r, count) count += 1 self.show_legend() self.update_canvas() def plot_all_normal_equation(self, object): print("Plotting Linear-Regression (Normal Equation) ...") count = 0 for r in object: c = self.random_color() line, = self.splts[count].plot( object[r]['data']['x'], object[r]['data']['y'], color=c, linestyle="-", label="Linear Regression (Normal Equation)", picker=True) self.lines.append(line) self.set_ticks(self.splts[count], object[r]['data']['x'], object[r]['data']['y']) comment = 'Theta: [\n' for t in object[r]['theta']: comment += ' ' + str(t[0]) + '\n' comment += ']' # place a text box in upper left in axes coords props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) annotate = self.splts[count].annotate(comment, xy=(min(object[r]['data']['x']), max(object[r]['data']['y'])), \ xytext=(min(object[r]['data']['x']), max(object[r]['data']['y'])), bbox=props, fontsize=FONT_SIZE, picker=True) annotate.draggable(True) count += 1 self.show_legend() self.update_canvas() def show_legend(self): self.legends = [] for i in range(len(self.splts)): splt = self.splts[i] # Shrink current axis by 20% box = splt.get_position() splt.set_position([box.x0, box.y0, box.width * self.box_width_fraction, \ box.height * self.box_height_fraction]) # Now add the legend with some customizations. legend = splt.legend(loc='upper center', ncol=1, fancybox=True, shadow=True) legend.set_bbox_to_anchor((self.legendpos[0], \ self.legendpos[1] + legend.get_window_extent().height/self.figh + 0.25)) legend.figure.canvas.mpl_connect('pick_event', self.on_pick) legend.draggable(True) # lined = dict() # for legline, origline in zip(legend.get_lines(), self.lines): # legline.set_picker(5) # 5 pts tolerance # self.lined[legline] = origline self.legends.append(legend) if legend: # The frame is matplotlib.patches.Rectangle instance surrounding the legend. frame = legend.get_frame() frame.set_facecolor('0.90') # Set the fontsize for label in legend.get_texts(): label.set_fontsize(FONT_SIZE) for label in legend.get_lines(): label.set_linewidth(0.75) # the legend line width else: pass def make_ticks(self, data): minn = np.min(data) maxx = np.max(data) return np.arange(minn, maxx, int((maxx - minn) / 3)) def squeeze_legend(self, evt): new_height = self.fig.get_window_extent().height self.box_height_fraction = new_height / self.figh self.figh = new_height new_width = self.fig.get_window_extent().width self.box_width_fraction = new_width / self.figw self.figw = new_width self.show_legend() self.update_canvas() def on_pick(self, evt): if isinstance(evt.artist, Text): # box_points = evt.artist.get_position() global TEXT_DRAGGABLE TEXT_DRAGGABLE = not TEXT_DRAGGABLE evt.artist.draggable(TEXT_DRAGGABLE) elif isinstance(evt.artist, Line2D): # box_points = evt.artist.get_clip_box() pass elif isinstance(evt.artist, Legend): # box_points = evt.artist.get_clip_box() global LEGEND_DRAGGABLE LEGEND_DRAGGABLE = not LEGEND_DRAGGABLE evt.artist.draggable(LEGEND_DRAGGABLE) else: print(evt.artist) pass # print("You've clicked on a bar with coords:\n %r, %r" % (box_points , evt.artist)) self.update_canvas() def on_motion(self, mouseevt): w, h = self.canvas.GetSize() if mouseevt.x in range(0, int(w + 1)) and mouseevt.y in range( 0, int(h + 1)): self.status.SetValue('Click on %r for dragging On/Off' % self.draggableList) else: pass def on_leave(self, mouseevt): self.status.SetValue('') def make_figure(self, type): self.fig.clf() if type == 'gd': gs = GridSpec(2, 3) gs.update(hspace=0.7, wspace=0.8) self.splts = [ self.fig.add_subplot(gs[int(i / 3), int(i % 3)]) for i in range(2 * 3) ] # grid nxn elif type == 'ne': gs = GridSpec(1, 1) gs.update(hspace=0.7, wspace=0.8) self.splts = [ self.fig.add_subplot(gs[int(i / 3), int(i % 3)]) for i in range(1 * 1) ] # grid nxn else: pass def random_color(self): rgbl = [0, random.random(), random.random()] return tuple(rgbl) def update_canvas(self): self.fig.canvas.draw() self.canvas.Refresh() self.toolbar.update()
class BackendMatplotlib(BackendBase.BackendBase): """Base class for Matplotlib backend without a FigureCanvas. For interactive on screen plot, see :class:`BackendMatplotlibQt`. See :class:`BackendBase.BackendBase` for public API documentation. """ def __init__(self, plot, parent=None): super(BackendMatplotlib, self).__init__(plot, parent) # matplotlib is handling keep aspect ratio at draw time # When keep aspect ratio is on, and one changes the limits and # ask them *before* next draw has been performed he will get the # limits without applying keep aspect ratio. # This attribute is used to ensure consistent values returned # when getting the limits at the expense of a replot self._dirtyLimits = True self._axesDisplayed = True self._matplotlibVersion = _parse_version(matplotlib.__version__) self.fig = Figure() self.fig.set_facecolor("w") self.ax = self.fig.add_axes([.15, .15, .75, .75], label="left") self.ax2 = self.ax.twinx() self.ax2.set_label("right") # Make sure background of Axes is displayed self.ax2.patch.set_visible(True) # Set axis zorder=0.5 so grid is displayed at 0.5 self.ax.set_axisbelow(True) # disable the use of offsets try: self.ax.get_yaxis().get_major_formatter().set_useOffset(False) self.ax.get_xaxis().get_major_formatter().set_useOffset(False) self.ax2.get_yaxis().get_major_formatter().set_useOffset(False) self.ax2.get_xaxis().get_major_formatter().set_useOffset(False) except: _logger.warning('Cannot disabled axes offsets in %s ' % matplotlib.__version__) # critical for picking!!!! self.ax2.set_zorder(0) self.ax2.set_autoscaley_on(True) self.ax.set_zorder(1) # this works but the figure color is left if self._matplotlibVersion < _parse_version('2'): self.ax.set_axis_bgcolor('none') else: self.ax.set_facecolor('none') self.fig.sca(self.ax) self._background = None self._colormaps = {} self._graphCursor = tuple() self._enableAxis('right', False) self._isXAxisTimeSeries = False def _overlayItems(self): """Generator of backend renderer for overlay items""" for item in self._plot.getItems(): if (item.isOverlay() and item.isVisible() and item._backendRenderer is not None): yield item._backendRenderer def _hasOverlays(self): """Returns whether there is an overlay layer or not. The overlay layers contains overlay items and the crosshair. :rtype: bool """ if self._graphCursor: return True # There is the crosshair for item in self._overlayItems(): return True # There is at least one overlay item return False # Add methods def addCurve(self, x, y, color, symbol, linewidth, linestyle, yaxis, xerror, yerror, z, selectable, fill, alpha, symbolsize): for parameter in (x, y, color, symbol, linewidth, linestyle, yaxis, z, selectable, fill, alpha, symbolsize): assert parameter is not None assert yaxis in ('left', 'right') if (len(color) == 4 and type(color[3]) in [type(1), numpy.uint8, numpy.int8]): color = numpy.array(color, dtype=numpy.float) / 255. if yaxis == "right": axes = self.ax2 self._enableAxis("right", True) else: axes = self.ax picker = 3 if selectable else None artists = [] # All the artists composing the curve # First add errorbars if any so they are behind the curve if xerror is not None or yerror is not None: if hasattr(color, 'dtype') and len(color) == len(x): errorbarColor = 'k' else: errorbarColor = color # Nx1 error array deprecated in matplotlib >=3.1 (removed in 3.3) if (isinstance(xerror, numpy.ndarray) and xerror.ndim == 2 and xerror.shape[1] == 1): xerror = numpy.ravel(xerror) if (isinstance(yerror, numpy.ndarray) and yerror.ndim == 2 and yerror.shape[1] == 1): yerror = numpy.ravel(yerror) errorbars = axes.errorbar(x, y, xerr=xerror, yerr=yerror, linestyle=' ', color=errorbarColor) artists += list(errorbars.get_children()) if hasattr(color, 'dtype') and len(color) == len(x): # scatter plot if color.dtype not in [numpy.float32, numpy.float]: actualColor = color / 255. else: actualColor = color if linestyle not in ["", " ", None]: # scatter plot with an actual line ... # we need to assign a color ... curveList = axes.plot(x, y, linestyle=linestyle, color=actualColor[0], linewidth=linewidth, picker=picker, marker=None) artists += list(curveList) scatter = axes.scatter(x, y, color=actualColor, marker=symbol, picker=picker, s=symbolsize**2) artists.append(scatter) if fill: artists.append(axes.fill_between( x, FLOAT32_MINPOS, y, facecolor=actualColor[0], linestyle='')) else: # Curve curveList = axes.plot(x, y, linestyle=linestyle, color=color, linewidth=linewidth, marker=symbol, picker=picker, markersize=symbolsize) artists += list(curveList) if fill: artists.append( axes.fill_between(x, FLOAT32_MINPOS, y, facecolor=color)) for artist in artists: artist.set_animated(True) artist.set_zorder(z + 1) if alpha < 1: artist.set_alpha(alpha) return _PickableContainer(artists) def addImage(self, data, origin, scale, z, selectable, draggable, colormap, alpha): # Non-uniform image # http://wiki.scipy.org/Cookbook/Histograms # Non-linear axes # http://stackoverflow.com/questions/11488800/non-linear-axes-for-imshow-in-matplotlib for parameter in (data, origin, scale, z, selectable, draggable): assert parameter is not None origin = float(origin[0]), float(origin[1]) scale = float(scale[0]), float(scale[1]) height, width = data.shape[0:2] picker = (selectable or draggable) # All image are shown as RGBA image image = Image(self.ax, interpolation='nearest', picker=picker, zorder=z + 1, origin='lower') if alpha < 1: image.set_alpha(alpha) # Set image extent xmin = origin[0] xmax = xmin + scale[0] * width if scale[0] < 0.: xmin, xmax = xmax, xmin ymin = origin[1] ymax = ymin + scale[1] * height if scale[1] < 0.: ymin, ymax = ymax, ymin image.set_extent((xmin, xmax, ymin, ymax)) # Set image data if scale[0] < 0. or scale[1] < 0.: # For negative scale, step by -1 xstep = 1 if scale[0] >= 0. else -1 ystep = 1 if scale[1] >= 0. else -1 data = data[::ystep, ::xstep] if data.ndim == 2: # Data image, convert to RGBA image data = colormap.applyToData(data) image.set_data(data) image.set_animated(True) self.ax.add_artist(image) return image def addTriangles(self, x, y, triangles, color, z, selectable, alpha): for parameter in (x, y, triangles, color, z, selectable, alpha): assert parameter is not None # 0 enables picking on filled triangle picker = 0 if selectable else None color = numpy.array(color, copy=False) assert color.ndim == 2 and len(color) == len(x) if color.dtype not in [numpy.float32, numpy.float]: color = color.astype(numpy.float32) / 255. collection = TriMesh( Triangulation(x, y, triangles), alpha=alpha, picker=picker, zorder=z + 1) collection.set_color(color) collection.set_animated(True) self.ax.add_collection(collection) return collection def addItem(self, x, y, shape, color, fill, overlay, z, linestyle, linewidth, linebgcolor): if (linebgcolor is not None and shape not in ('rectangle', 'polygon', 'polylines')): _logger.warning( 'linebgcolor not implemented for %s with matplotlib backend', shape) xView = numpy.array(x, copy=False) yView = numpy.array(y, copy=False) linestyle = normalize_linestyle(linestyle) if shape == "line": item = self.ax.plot(x, y, color=color, linestyle=linestyle, linewidth=linewidth, marker=None)[0] elif shape == "hline": if hasattr(y, "__len__"): y = y[-1] item = self.ax.axhline(y, color=color, linestyle=linestyle, linewidth=linewidth) elif shape == "vline": if hasattr(x, "__len__"): x = x[-1] item = self.ax.axvline(x, color=color, linestyle=linestyle, linewidth=linewidth) elif shape == 'rectangle': xMin = numpy.nanmin(xView) xMax = numpy.nanmax(xView) yMin = numpy.nanmin(yView) yMax = numpy.nanmax(yView) w = xMax - xMin h = yMax - yMin item = Rectangle(xy=(xMin, yMin), width=w, height=h, fill=False, color=color, linestyle=linestyle, linewidth=linewidth) if fill: item.set_hatch('.') if linestyle != "solid" and linebgcolor is not None: item = _DoubleColoredLinePatch(item) item.linebgcolor = linebgcolor self.ax.add_patch(item) elif shape in ('polygon', 'polylines'): points = numpy.array((xView, yView)).T if shape == 'polygon': closed = True else: # shape == 'polylines' closed = numpy.all(numpy.equal(points[0], points[-1])) item = Polygon(points, closed=closed, fill=False, color=color, linestyle=linestyle, linewidth=linewidth) if fill and shape == 'polygon': item.set_hatch('/') if linestyle != "solid" and linebgcolor is not None: item = _DoubleColoredLinePatch(item) item.linebgcolor = linebgcolor self.ax.add_patch(item) else: raise NotImplementedError("Unsupported item shape %s" % shape) item.set_zorder(z + 1) item.set_animated(True) return item def addMarker(self, x, y, text, color, selectable, draggable, symbol, linestyle, linewidth, constraint, yaxis): textArtist = None xmin, xmax = self.getGraphXLimits() ymin, ymax = self.getGraphYLimits(axis=yaxis) if yaxis == 'left': ax = self.ax elif yaxis == 'right': ax = self.ax2 else: assert(False) if x is not None and y is not None: line = ax.plot(x, y, linestyle=" ", color=color, marker=symbol, markersize=10.)[-1] if text is not None: if symbol is None: valign = 'baseline' else: valign = 'top' text = " " + text textArtist = ax.text(x, y, text, color=color, horizontalalignment='left', verticalalignment=valign) elif x is not None: line = ax.axvline(x, color=color, linewidth=linewidth, linestyle=linestyle) if text is not None: # Y position will be updated in updateMarkerText call textArtist = ax.text(x, 1., " " + text, color=color, horizontalalignment='left', verticalalignment='top') elif y is not None: line = ax.axhline(y, color=color, linewidth=linewidth, linestyle=linestyle) if text is not None: # X position will be updated in updateMarkerText call textArtist = ax.text(1., y, " " + text, color=color, horizontalalignment='right', verticalalignment='top') else: raise RuntimeError('A marker must at least have one coordinate') if selectable or draggable: line.set_picker(5) # All markers are overlays line.set_animated(True) if textArtist is not None: textArtist.set_animated(True) artists = [line] if textArtist is None else [line, textArtist] container = _MarkerContainer(artists, x, y, yaxis) container.updateMarkerText(xmin, xmax, ymin, ymax) return container def _updateMarkers(self): xmin, xmax = self.ax.get_xbound() ymin1, ymax1 = self.ax.get_ybound() ymin2, ymax2 = self.ax2.get_ybound() for item in self._overlayItems(): if isinstance(item, _MarkerContainer): if item.yAxis == 'left': item.updateMarkerText(xmin, xmax, ymin1, ymax1) else: item.updateMarkerText(xmin, xmax, ymin2, ymax2) # Remove methods def remove(self, item): try: item.remove() except ValueError: pass # Already removed e.g., in set[X|Y]AxisLogarithmic # Interaction methods def setGraphCursor(self, flag, color, linewidth, linestyle): if flag: lineh = self.ax.axhline( self.ax.get_ybound()[0], visible=False, color=color, linewidth=linewidth, linestyle=linestyle) lineh.set_animated(True) linev = self.ax.axvline( self.ax.get_xbound()[0], visible=False, color=color, linewidth=linewidth, linestyle=linestyle) linev.set_animated(True) self._graphCursor = lineh, linev else: if self._graphCursor: lineh, linev = self._graphCursor lineh.remove() linev.remove() self._graphCursor = tuple() # Active curve def setCurveColor(self, curve, color): # Store Line2D and PathCollection for artist in curve.get_children(): if isinstance(artist, (Line2D, LineCollection)): artist.set_color(color) elif isinstance(artist, PathCollection): artist.set_facecolors(color) artist.set_edgecolors(color) else: _logger.warning( 'setActiveCurve ignoring artist %s', str(artist)) # Misc. def getWidgetHandle(self): return self.fig.canvas def _enableAxis(self, axis, flag=True): """Show/hide Y axis :param str axis: Axis name: 'left' or 'right' :param bool flag: Default, True """ assert axis in ('right', 'left') axes = self.ax2 if axis == 'right' else self.ax axes.get_yaxis().set_visible(flag) def replot(self): """Do not perform rendering. Override in subclass to actually draw something. """ # TODO images, markers? scatter plot? move in remove? # Right Y axis only support curve for now # Hide right Y axis if no line is present self._dirtyLimits = False if not self.ax2.lines: self._enableAxis('right', False) def saveGraph(self, fileName, fileFormat, dpi): # fileName can be also a StringIO or file instance if dpi is not None: self.fig.savefig(fileName, format=fileFormat, dpi=dpi) else: self.fig.savefig(fileName, format=fileFormat) self._plot._setDirtyPlot() # Graph labels def setGraphTitle(self, title): self.ax.set_title(title) def setGraphXLabel(self, label): self.ax.set_xlabel(label) def setGraphYLabel(self, label, axis): axes = self.ax if axis == 'left' else self.ax2 axes.set_ylabel(label) # Graph limits def setLimits(self, xmin, xmax, ymin, ymax, y2min=None, y2max=None): # Let matplotlib taking care of keep aspect ratio if any self._dirtyLimits = True self.ax.set_xlim(min(xmin, xmax), max(xmin, xmax)) if y2min is not None and y2max is not None: if not self.isYAxisInverted(): self.ax2.set_ylim(min(y2min, y2max), max(y2min, y2max)) else: self.ax2.set_ylim(max(y2min, y2max), min(y2min, y2max)) if not self.isYAxisInverted(): self.ax.set_ylim(min(ymin, ymax), max(ymin, ymax)) else: self.ax.set_ylim(max(ymin, ymax), min(ymin, ymax)) self._updateMarkers() def getGraphXLimits(self): if self._dirtyLimits and self.isKeepDataAspectRatio(): self.ax.apply_aspect() self.ax2.apply_aspect() self._dirtyLimits = False return self.ax.get_xbound() def setGraphXLimits(self, xmin, xmax): self._dirtyLimits = True self.ax.set_xlim(min(xmin, xmax), max(xmin, xmax)) self._updateMarkers() def getGraphYLimits(self, axis): assert axis in ('left', 'right') ax = self.ax2 if axis == 'right' else self.ax if not ax.get_visible(): return None if self._dirtyLimits and self.isKeepDataAspectRatio(): self.ax.apply_aspect() self.ax2.apply_aspect() self._dirtyLimits = False return ax.get_ybound() def setGraphYLimits(self, ymin, ymax, axis): ax = self.ax2 if axis == 'right' else self.ax if ymax < ymin: ymin, ymax = ymax, ymin self._dirtyLimits = True if self.isKeepDataAspectRatio(): # matplotlib keeps limits of shared axis when keeping aspect ratio # So x limits are kept when changing y limits.... # Change x limits first by taking into account aspect ratio # and then change y limits.. so matplotlib does not need # to make change (to y) to keep aspect ratio xmin, xmax = ax.get_xbound() curYMin, curYMax = ax.get_ybound() newXRange = (xmax - xmin) * (ymax - ymin) / (curYMax - curYMin) xcenter = 0.5 * (xmin + xmax) ax.set_xlim(xcenter - 0.5 * newXRange, xcenter + 0.5 * newXRange) if not self.isYAxisInverted(): ax.set_ylim(ymin, ymax) else: ax.set_ylim(ymax, ymin) self._updateMarkers() # Graph axes def setXAxisTimeZone(self, tz): super(BackendMatplotlib, self).setXAxisTimeZone(tz) # Make new formatter and locator with the time zone. self.setXAxisTimeSeries(self.isXAxisTimeSeries()) def isXAxisTimeSeries(self): return self._isXAxisTimeSeries def setXAxisTimeSeries(self, isTimeSeries): self._isXAxisTimeSeries = isTimeSeries if self._isXAxisTimeSeries: # We can't use a matplotlib.dates.DateFormatter because it expects # the data to be in datetimes. Silx works internally with # timestamps (floats). locator = NiceDateLocator(tz=self.getXAxisTimeZone()) self.ax.xaxis.set_major_locator(locator) self.ax.xaxis.set_major_formatter( NiceAutoDateFormatter(locator, tz=self.getXAxisTimeZone())) else: try: scalarFormatter = ScalarFormatter(useOffset=False) except: _logger.warning('Cannot disabled axes offsets in %s ' % matplotlib.__version__) scalarFormatter = ScalarFormatter() self.ax.xaxis.set_major_formatter(scalarFormatter) def setXAxisLogarithmic(self, flag): # Workaround for matplotlib 2.1.0 when one tries to set an axis # to log scale with both limits <= 0 # In this case a draw with positive limits is needed first if flag and self._matplotlibVersion >= _parse_version('2.1.0'): xlim = self.ax.get_xlim() if xlim[0] <= 0 and xlim[1] <= 0: self.ax.set_xlim(1, 10) self.draw() self.ax2.set_xscale('log' if flag else 'linear') self.ax.set_xscale('log' if flag else 'linear') def setYAxisLogarithmic(self, flag): # Workaround for matplotlib 2.0 issue with negative bounds # before switching to log scale if flag and self._matplotlibVersion >= _parse_version('2.0.0'): redraw = False for axis, dataRangeIndex in ((self.ax, 1), (self.ax2, 2)): ylim = axis.get_ylim() if ylim[0] <= 0 or ylim[1] <= 0: dataRange = self._plot.getDataRange()[dataRangeIndex] if dataRange is None: dataRange = 1, 100 # Fallback axis.set_ylim(*dataRange) redraw = True if redraw: self.draw() self.ax2.set_yscale('log' if flag else 'linear') self.ax.set_yscale('log' if flag else 'linear') def setYAxisInverted(self, flag): if self.ax.yaxis_inverted() != bool(flag): self.ax.invert_yaxis() def isYAxisInverted(self): return self.ax.yaxis_inverted() def isKeepDataAspectRatio(self): return self.ax.get_aspect() in (1.0, 'equal') def setKeepDataAspectRatio(self, flag): self.ax.set_aspect(1.0 if flag else 'auto') self.ax2.set_aspect(1.0 if flag else 'auto') def setGraphGrid(self, which): self.ax.grid(False, which='both') # Disable all grid first if which is not None: self.ax.grid(True, which=which) # Data <-> Pixel coordinates conversion def _mplQtYAxisCoordConversion(self, y): """Qt origin (top) to/from matplotlib origin (bottom) conversion. :rtype: float """ height = self.fig.get_window_extent().height return height - y def dataToPixel(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax pixels = ax.transData.transform_point((x, y)) xPixel, yPixel = pixels.T # Convert from matplotlib origin (bottom) to Qt origin (top) yPixel = self._mplQtYAxisCoordConversion(yPixel) return xPixel, yPixel def pixelToData(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax # Convert from Qt origin (top) to matplotlib origin (bottom) y = self._mplQtYAxisCoordConversion(y) inv = ax.transData.inverted() x, y = inv.transform_point((x, y)) return x, y def getPlotBoundsInPixels(self): bbox = self.ax.get_window_extent() # Warning this is not returning int... return (bbox.xmin, self._mplQtYAxisCoordConversion(bbox.ymax), bbox.width, bbox.height) def setAxesDisplayed(self, displayed): """Display or not the axes. :param bool displayed: If `True` axes are displayed. If `False` axes are not anymore visible and the margin used for them is removed. """ BackendBase.BackendBase.setAxesDisplayed(self, displayed) if displayed: # show axes and viewbox rect self.ax.set_axis_on() self.ax2.set_axis_on() # set the default margins self.ax.set_position([.15, .15, .75, .75]) self.ax2.set_position([.15, .15, .75, .75]) else: # hide axes and viewbox rect self.ax.set_axis_off() self.ax2.set_axis_off() # remove external margins self.ax.set_position([0, 0, 1, 1]) self.ax2.set_position([0, 0, 1, 1]) self._synchronizeBackgroundColors() self._synchronizeForegroundColors() self._plot._setDirtyPlot() def _synchronizeBackgroundColors(self): backgroundColor = self._plot.getBackgroundColor().getRgbF() dataBackgroundColor = self._plot.getDataBackgroundColor() if dataBackgroundColor.isValid(): dataBackgroundColor = dataBackgroundColor.getRgbF() else: dataBackgroundColor = backgroundColor if self.ax2.axison: self.fig.patch.set_facecolor(backgroundColor) if self._matplotlibVersion < _parse_version('2'): self.ax2.set_axis_bgcolor(dataBackgroundColor) else: self.ax2.set_facecolor(dataBackgroundColor) else: self.fig.patch.set_facecolor(dataBackgroundColor) def _synchronizeForegroundColors(self): foregroundColor = self._plot.getForegroundColor().getRgbF() gridColor = self._plot.getGridColor() if gridColor.isValid(): gridColor = gridColor.getRgbF() else: gridColor = foregroundColor for axes in (self.ax, self.ax2): if axes.axison: axes.spines['bottom'].set_color(foregroundColor) axes.spines['top'].set_color(foregroundColor) axes.spines['right'].set_color(foregroundColor) axes.spines['left'].set_color(foregroundColor) axes.tick_params(axis='x', colors=foregroundColor) axes.tick_params(axis='y', colors=foregroundColor) axes.yaxis.label.set_color(foregroundColor) axes.xaxis.label.set_color(foregroundColor) axes.title.set_color(foregroundColor) for line in axes.get_xgridlines(): line.set_color(gridColor) for line in axes.get_ygridlines(): line.set_color(gridColor)
class Panel_Plotting_Helper(wx.Panel): def __init__(self, parent): w, h = parent.GetSize() wx.Panel.__init__(self, parent=parent, size=(w, 0.7*h), style=wx.SUNKEN_BORDER) self.parent = parent self.legends = [] self.legendpos = [0.5, 1] self.fig = Figure(figsize=(12, 6), dpi=90) # create a figure size 8x6 inches, 80 dots per inches self.splts = [] self.canvas = FigureCanvasWxAgg(self, -1, self.fig) self.toolbar = Toolbar(self.canvas) # matplotlib toolbar # additional toolbar status_txt = wx.StaticText(self.toolbar, label=' Status on hover: ', pos=(230, 7), \ size=(100, 17)) self.status = wx.TextCtrl(self.toolbar, pos=(330,4), size=(300, 22), \ style=wx.TE_READONLY) self.toolbar.Realize() self.figw, self.figh = self.fig.get_window_extent().width, self.fig.get_window_extent().height sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.toolbar, 0, wx.GROW) sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.SetSizer(sizer) self.box_width_fraction = 1.0 self.box_height_fraction = 0.9 self.lines = [] self.lined = dict() self.draggableList = ['Text', 'Legend'] # print(self.toolbar.GetBackgroundColour()) self.fig.canvas.mpl_connect('resize_event', self.squeeze_legend) self.fig.canvas.mpl_connect('pick_event', self.on_pick) self.fig.canvas.mpl_connect('motion_notify_event', self.on_motion) self.fig.canvas.mpl_connect('figure_leave_event', self.on_leave) def plot_J(self, J, theta, format, r, count): index = count%3 + 3 self.splts[index].plot(np.arange(len(J)), J, color=format['color'], linewidth=format['linewidth'], linestyle=format['linestyle'], label=format['label'], picker=True) self.splts[index].set_xlabel("Number of Iteration", fontsize=FONT_SIZE) self.splts[index].set_ylabel("Cost value", fontsize = FONT_SIZE) self.set_ticks(self.splts[index], np.arange(len(J)), J) comment = r + ': [\n' for t in theta: comment += ' ' + str(t) + '\n' comment += ']' props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) annotate = self.splts[index].annotate(comment, xy=(len(J)-1, J[len(J)-1]), xytext=(len(J)/2, (J[0]+J[len(J)-1])/2), \ arrowprops=dict(facecolor='black', shrink=0.05), bbox=props, fontsize=FONT_SIZE, picker=True) annotate.draggable(True) def plot_data_gradient_descent(self, X, y, format): print("Plotting data ...") for i in range(int(round(len(self.splts)/2))): self.plot_data(self.splts[i], X, y, format) self.update_canvas() def plot_data_minimize_cost_function(self, X, y, format): print("Plotting data ...") for i in range(int(round((len(self.splts)+1)/2))): self.plot_data(self.splts[i], X, y, format) self.update_canvas() def plot_data(self, splt, X, y, format): line, = splt.plot(X, y, 'ro', color=format['color'], label=format['label'], picker=True) self.set_ticks(splt, X, y) self.lines.append(line) splt.set_xlabel("X1", fontsize=FONT_SIZE) splt.set_ylabel("Y", fontsize=FONT_SIZE) def set_ticks(self, splt, X, y): xticks = self.make_ticks(X) yticks = self.make_ticks(y) splt.set_xticks(xticks) splt.set_yticks(yticks) for tick in splt.get_xticklabels(): tick.set_rotation(45) tick.set_fontsize(FONT_SIZE) for tick in splt.get_yticklabels(): tick.set_rotation(45) tick.set_fontsize(FONT_SIZE) def plot_all_gradient_descent(self, object): print("Plotting Linear-Regression (Gradient Descent) and J-Convergence ...") count = 0 for r in object: c = self.random_color() self.splts[count].plot(object[r]['data']['x'], object[r]['data']['y'], color=c, linestyle="-", label="Linear Regression (alpha="+r+")", picker=True) self.set_ticks(self.splts[count], object[r]['data']['x'], object[r]['data']['y']) self.plot_J(object[r]['J_history'], object[r]['theta'], {"color": c, "linewidth": 5, "linestyle": "-", "label": "Convergence of J"}, r, count) count += 1 self.show_legend() self.update_canvas() def plot_all_minimize_cost_function(self, object): print("Plotting Linear-Regression (Normal Equation) ...") count = 0 for r in object: c = self.random_color() line, = self.splts[count].plot(object[r]['data']['x'], object[r]['data']['y'], color=c, linestyle="-", label="Linear Regression (Normal Equation)", picker=True) self.lines.append(line) self.set_ticks(self.splts[count], object[r]['data']['x'], object[r]['data']['y']) comment = 'Theta: [\n' for t in object[r]['theta']: comment += ' ' + str(t[0]) + '\n' comment += ']' # place a text box in upper left in axes coords props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) annotate = self.splts[count].annotate(comment, xy=(min(object[r]['data']['x']), max(object[r]['data']['y'])), \ xytext=(min(object[r]['data']['x']), max(object[r]['data']['y'])), bbox=props, fontsize=FONT_SIZE, picker=True) annotate.draggable(True) count += 1 self.show_legend() self.update_canvas() def show_legend(self): self.legends = [] for i in range(len(self.splts)): splt = self.splts[i] # Shrink current axis by 20% box = splt.get_position() splt.set_position([box.x0, box.y0, box.width * self.box_width_fraction, \ box.height * self.box_height_fraction]) # Now add the legend with some customizations. legend = splt.legend(loc='upper center', ncol=1, fancybox=True, shadow=True) legend.set_bbox_to_anchor((self.legendpos[0], \ self.legendpos[1] + legend.get_window_extent().height/self.figh + 0.25)) legend.figure.canvas.mpl_connect('pick_event', self.on_pick) legend.draggable(True) # lined = dict() # for legline, origline in zip(legend.get_lines(), self.lines): # legline.set_picker(5) # 5 pts tolerance # self.lined[legline] = origline self.legends.append(legend) if legend: # The frame is matplotlib.patches.Rectangle instance surrounding the legend. frame = legend.get_frame() frame.set_facecolor('0.90') # Set the fontsize for label in legend.get_texts(): label.set_fontsize(FONT_SIZE) for label in legend.get_lines(): label.set_linewidth(0.75) # the legend line width else: pass def make_ticks(self, data): minn = np.min(data) maxx = np.max(data) return np.arange(minn, maxx, int((maxx-minn)/3)) def squeeze_legend(self, evt): new_height = self.fig.get_window_extent().height self.box_height_fraction = new_height / self.figh self.figh = new_height new_width = self.fig.get_window_extent().width self.box_width_fraction = new_width / self.figw self.figw = new_width self.show_legend() self.update_canvas() def on_pick(self, evt): if isinstance(evt.artist, Text): # box_points = evt.artist.get_position() global TEXT_DRAGGABLE TEXT_DRAGGABLE = not TEXT_DRAGGABLE evt.artist.draggable(TEXT_DRAGGABLE) elif isinstance(evt.artist, Line2D): # box_points = evt.artist.get_clip_box() pass elif isinstance(evt.artist, Legend): # box_points = evt.artist.get_clip_box() global LEGEND_DRAGGABLE LEGEND_DRAGGABLE = not LEGEND_DRAGGABLE evt.artist.draggable(LEGEND_DRAGGABLE) else: print(evt.artist) pass # print("You've clicked on a bar with coords:\n %r, %r" % (box_points , evt.artist)) self.update_canvas() def on_motion(self, mouseevt): w, h = self.canvas.GetSize() if mouseevt.x in range(0, int(w+1)) and mouseevt.y in range(0, int(h+1)): self.status.SetValue('Click on %r for dragging On/Off' % self.draggableList) else: pass def on_leave(self, mouseevt): self.status.SetValue('') def make_figure(self, type): self.fig.clf() if type == 'gd': pass elif type == 'mc': gs = GridSpec(1, 1) gs.update(hspace=0.7, wspace=0.8) self.splts = [self.fig.add_subplot(gs[int(i/3), int(i%3)]) for i in range(1*1)] # grid nxn else: pass def random_color(self): rgbl = [0, random.random(), random.random()] return tuple(rgbl) def update_canvas(self): self.fig.canvas.draw() self.canvas.Refresh() self.toolbar.update()
class BackendMatplotlib(BackendBase.BackendBase): """Base class for Matplotlib backend without a FigureCanvas. For interactive on screen plot, see :class:`BackendMatplotlibQt`. See :class:`BackendBase.BackendBase` for public API documentation. """ def __init__(self, plot, parent=None): super(BackendMatplotlib, self).__init__(plot, parent) # matplotlib is handling keep aspect ratio at draw time # When keep aspect ratio is on, and one changes the limits and # ask them *before* next draw has been performed he will get the # limits without applying keep aspect ratio. # This attribute is used to ensure consistent values returned # when getting the limits at the expense of a replot self._dirtyLimits = True self._axesDisplayed = True self._matplotlibVersion = _parse_version(matplotlib.__version__) self.fig = Figure() self.fig.set_facecolor("w") self.ax = self.fig.add_axes([.15, .15, .75, .75], label="left") self.ax2 = self.ax.twinx() self.ax2.set_label("right") # disable the use of offsets try: self.ax.get_yaxis().get_major_formatter().set_useOffset(False) self.ax.get_xaxis().get_major_formatter().set_useOffset(False) self.ax2.get_yaxis().get_major_formatter().set_useOffset(False) self.ax2.get_xaxis().get_major_formatter().set_useOffset(False) except: _logger.warning('Cannot disabled axes offsets in %s ' % matplotlib.__version__) # critical for picking!!!! self.ax2.set_zorder(0) self.ax2.set_autoscaley_on(True) self.ax.set_zorder(1) # this works but the figure color is left if self._matplotlibVersion < _parse_version('2'): self.ax.set_axis_bgcolor('none') else: self.ax.set_facecolor('none') self.fig.sca(self.ax) self._overlays = set() self._background = None self._colormaps = {} self._graphCursor = tuple() self._enableAxis('right', False) self._isXAxisTimeSeries = False # Add methods def addCurve(self, x, y, legend, color, symbol, linewidth, linestyle, yaxis, xerror, yerror, z, selectable, fill, alpha, symbolsize): for parameter in (x, y, legend, color, symbol, linewidth, linestyle, yaxis, z, selectable, fill, alpha, symbolsize): assert parameter is not None assert yaxis in ('left', 'right') if (len(color) == 4 and type(color[3]) in [type(1), numpy.uint8, numpy.int8]): color = numpy.array(color, dtype=numpy.float) / 255. if yaxis == "right": axes = self.ax2 self._enableAxis("right", True) else: axes = self.ax picker = 3 if selectable else None artists = [] # All the artists composing the curve # First add errorbars if any so they are behind the curve if xerror is not None or yerror is not None: if hasattr(color, 'dtype') and len(color) == len(x): errorbarColor = 'k' else: errorbarColor = color # On Debian 7 at least, Nx1 array yerr does not seems supported if (isinstance(yerror, numpy.ndarray) and yerror.ndim == 2 and yerror.shape[1] == 1 and len(x) != 1): yerror = numpy.ravel(yerror) errorbars = axes.errorbar(x, y, label=legend, xerr=xerror, yerr=yerror, linestyle=' ', color=errorbarColor) artists += list(errorbars.get_children()) if hasattr(color, 'dtype') and len(color) == len(x): # scatter plot if color.dtype not in [numpy.float32, numpy.float]: actualColor = color / 255. else: actualColor = color if linestyle not in ["", " ", None]: # scatter plot with an actual line ... # we need to assign a color ... curveList = axes.plot(x, y, label=legend, linestyle=linestyle, color=actualColor[0], linewidth=linewidth, picker=picker, marker=None) artists += list(curveList) scatter = axes.scatter(x, y, label=legend, color=actualColor, marker=symbol, picker=picker, s=symbolsize**2) artists.append(scatter) if fill: artists.append(axes.fill_between( x, FLOAT32_MINPOS, y, facecolor=actualColor[0], linestyle='')) else: # Curve curveList = axes.plot(x, y, label=legend, linestyle=linestyle, color=color, linewidth=linewidth, marker=symbol, picker=picker, markersize=symbolsize) artists += list(curveList) if fill: artists.append( axes.fill_between(x, FLOAT32_MINPOS, y, facecolor=color)) for artist in artists: artist.set_zorder(z) if alpha < 1: artist.set_alpha(alpha) return Container(artists) def addImage(self, data, legend, origin, scale, z, selectable, draggable, colormap, alpha): # Non-uniform image # http://wiki.scipy.org/Cookbook/Histograms # Non-linear axes # http://stackoverflow.com/questions/11488800/non-linear-axes-for-imshow-in-matplotlib for parameter in (data, legend, origin, scale, z, selectable, draggable): assert parameter is not None origin = float(origin[0]), float(origin[1]) scale = float(scale[0]), float(scale[1]) height, width = data.shape[0:2] picker = (selectable or draggable) # Debian 7 specific support # No transparent colormap with matplotlib < 1.2.0 # Add support for transparent colormap for uint8 data with # colormap with 256 colors, linear norm, [0, 255] range if self._matplotlibVersion < _parse_version('1.2.0'): if (len(data.shape) == 2 and colormap.getName() is None and colormap.getColormapLUT() is not None): colors = colormap.getColormapLUT() if (colors.shape[-1] == 4 and not numpy.all(numpy.equal(colors[3], 255))): # This is a transparent colormap if (colors.shape == (256, 4) and colormap.getNormalization() == 'linear' and not colormap.isAutoscale() and colormap.getVMin() == 0 and colormap.getVMax() == 255 and data.dtype == numpy.uint8): # Supported case, convert data to RGBA data = colors[data.reshape(-1)].reshape( data.shape + (4,)) else: _logger.warning( 'matplotlib %s does not support transparent ' 'colormap.', matplotlib.__version__) if ((height * width) > 5.0e5 and origin == (0., 0.) and scale == (1., 1.)): imageClass = ModestImage else: imageClass = AxesImage # All image are shown as RGBA image image = imageClass(self.ax, label="__IMAGE__" + legend, interpolation='nearest', picker=picker, zorder=z, origin='lower') if alpha < 1: image.set_alpha(alpha) # Set image extent xmin = origin[0] xmax = xmin + scale[0] * width if scale[0] < 0.: xmin, xmax = xmax, xmin ymin = origin[1] ymax = ymin + scale[1] * height if scale[1] < 0.: ymin, ymax = ymax, ymin image.set_extent((xmin, xmax, ymin, ymax)) # Set image data if scale[0] < 0. or scale[1] < 0.: # For negative scale, step by -1 xstep = 1 if scale[0] >= 0. else -1 ystep = 1 if scale[1] >= 0. else -1 data = data[::ystep, ::xstep] if self._matplotlibVersion < _parse_version('2.1'): # matplotlib 1.4.2 do not support float128 dtype = data.dtype if dtype.kind == "f" and dtype.itemsize >= 16: _logger.warning("Your matplotlib version do not support " "float128. Data converted to float64.") data = data.astype(numpy.float64) if data.ndim == 2: # Data image, convert to RGBA image data = colormap.applyToData(data) image.set_data(data) self.ax.add_artist(image) return image def addItem(self, x, y, legend, shape, color, fill, overlay, z, linestyle, linewidth, linebgcolor): if (linebgcolor is not None and shape not in ('rectangle', 'polygon', 'polylines')): _logger.warning( 'linebgcolor not implemented for %s with matplotlib backend', shape) xView = numpy.array(x, copy=False) yView = numpy.array(y, copy=False) linestyle = normalize_linestyle(linestyle) if shape == "line": item = self.ax.plot(x, y, label=legend, color=color, linestyle=linestyle, linewidth=linewidth, marker=None)[0] elif shape == "hline": if hasattr(y, "__len__"): y = y[-1] item = self.ax.axhline(y, label=legend, color=color, linestyle=linestyle, linewidth=linewidth) elif shape == "vline": if hasattr(x, "__len__"): x = x[-1] item = self.ax.axvline(x, label=legend, color=color, linestyle=linestyle, linewidth=linewidth) elif shape == 'rectangle': xMin = numpy.nanmin(xView) xMax = numpy.nanmax(xView) yMin = numpy.nanmin(yView) yMax = numpy.nanmax(yView) w = xMax - xMin h = yMax - yMin item = Rectangle(xy=(xMin, yMin), width=w, height=h, fill=False, color=color, linestyle=linestyle, linewidth=linewidth) if fill: item.set_hatch('.') if linestyle != "solid" and linebgcolor is not None: item = _DoubleColoredLinePatch(item) item.linebgcolor = linebgcolor self.ax.add_patch(item) elif shape in ('polygon', 'polylines'): points = numpy.array((xView, yView)).T if shape == 'polygon': closed = True else: # shape == 'polylines' closed = numpy.all(numpy.equal(points[0], points[-1])) item = Polygon(points, closed=closed, fill=False, label=legend, color=color, linestyle=linestyle, linewidth=linewidth) if fill and shape == 'polygon': item.set_hatch('/') if linestyle != "solid" and linebgcolor is not None: item = _DoubleColoredLinePatch(item) item.linebgcolor = linebgcolor self.ax.add_patch(item) else: raise NotImplementedError("Unsupported item shape %s" % shape) item.set_zorder(z) if overlay: item.set_animated(True) self._overlays.add(item) return item def addMarker(self, x, y, legend, text, color, selectable, draggable, symbol, linestyle, linewidth, constraint): legend = "__MARKER__" + legend textArtist = None xmin, xmax = self.getGraphXLimits() ymin, ymax = self.getGraphYLimits(axis='left') if x is not None and y is not None: line = self.ax.plot(x, y, label=legend, linestyle=" ", color=color, marker=symbol, markersize=10.)[-1] if text is not None: if symbol is None: valign = 'baseline' else: valign = 'top' text = " " + text textArtist = self.ax.text(x, y, text, color=color, horizontalalignment='left', verticalalignment=valign) elif x is not None: line = self.ax.axvline(x, label=legend, color=color, linewidth=linewidth, linestyle=linestyle) if text is not None: # Y position will be updated in updateMarkerText call textArtist = self.ax.text(x, 1., " " + text, color=color, horizontalalignment='left', verticalalignment='top') elif y is not None: line = self.ax.axhline(y, label=legend, color=color, linewidth=linewidth, linestyle=linestyle) if text is not None: # X position will be updated in updateMarkerText call textArtist = self.ax.text(1., y, " " + text, color=color, horizontalalignment='right', verticalalignment='top') else: raise RuntimeError('A marker must at least have one coordinate') if selectable or draggable: line.set_picker(5) # All markers are overlays line.set_animated(True) if textArtist is not None: textArtist.set_animated(True) artists = [line] if textArtist is None else [line, textArtist] container = _MarkerContainer(artists, x, y) container.updateMarkerText(xmin, xmax, ymin, ymax) self._overlays.add(container) return container def _updateMarkers(self): xmin, xmax = self.ax.get_xbound() ymin, ymax = self.ax.get_ybound() for item in self._overlays: if isinstance(item, _MarkerContainer): item.updateMarkerText(xmin, xmax, ymin, ymax) # Remove methods def remove(self, item): # Warning: It also needs to remove extra stuff if added as for markers self._overlays.discard(item) try: item.remove() except ValueError: pass # Already removed e.g., in set[X|Y]AxisLogarithmic # Interaction methods def setGraphCursor(self, flag, color, linewidth, linestyle): if flag: lineh = self.ax.axhline( self.ax.get_ybound()[0], visible=False, color=color, linewidth=linewidth, linestyle=linestyle) lineh.set_animated(True) linev = self.ax.axvline( self.ax.get_xbound()[0], visible=False, color=color, linewidth=linewidth, linestyle=linestyle) linev.set_animated(True) self._graphCursor = lineh, linev else: if self._graphCursor is not None: lineh, linev = self._graphCursor lineh.remove() linev.remove() self._graphCursor = tuple() # Active curve def setCurveColor(self, curve, color): # Store Line2D and PathCollection for artist in curve.get_children(): if isinstance(artist, (Line2D, LineCollection)): artist.set_color(color) elif isinstance(artist, PathCollection): artist.set_facecolors(color) artist.set_edgecolors(color) else: _logger.warning( 'setActiveCurve ignoring artist %s', str(artist)) # Misc. def getWidgetHandle(self): return self.fig.canvas def _enableAxis(self, axis, flag=True): """Show/hide Y axis :param str axis: Axis name: 'left' or 'right' :param bool flag: Default, True """ assert axis in ('right', 'left') axes = self.ax2 if axis == 'right' else self.ax axes.get_yaxis().set_visible(flag) def replot(self): """Do not perform rendering. Override in subclass to actually draw something. """ # TODO images, markers? scatter plot? move in remove? # Right Y axis only support curve for now # Hide right Y axis if no line is present self._dirtyLimits = False if not self.ax2.lines: self._enableAxis('right', False) def saveGraph(self, fileName, fileFormat, dpi): # fileName can be also a StringIO or file instance if dpi is not None: self.fig.savefig(fileName, format=fileFormat, dpi=dpi) else: self.fig.savefig(fileName, format=fileFormat) self._plot._setDirtyPlot() # Graph labels def setGraphTitle(self, title): self.ax.set_title(title) def setGraphXLabel(self, label): self.ax.set_xlabel(label) def setGraphYLabel(self, label, axis): axes = self.ax if axis == 'left' else self.ax2 axes.set_ylabel(label) # Graph limits def setLimits(self, xmin, xmax, ymin, ymax, y2min=None, y2max=None): # Let matplotlib taking care of keep aspect ratio if any self._dirtyLimits = True self.ax.set_xlim(min(xmin, xmax), max(xmin, xmax)) if y2min is not None and y2max is not None: if not self.isYAxisInverted(): self.ax2.set_ylim(min(y2min, y2max), max(y2min, y2max)) else: self.ax2.set_ylim(max(y2min, y2max), min(y2min, y2max)) if not self.isYAxisInverted(): self.ax.set_ylim(min(ymin, ymax), max(ymin, ymax)) else: self.ax.set_ylim(max(ymin, ymax), min(ymin, ymax)) self._updateMarkers() def getGraphXLimits(self): if self._dirtyLimits and self.isKeepDataAspectRatio(): self.replot() # makes sure we get the right limits return self.ax.get_xbound() def setGraphXLimits(self, xmin, xmax): self._dirtyLimits = True self.ax.set_xlim(min(xmin, xmax), max(xmin, xmax)) self._updateMarkers() def getGraphYLimits(self, axis): assert axis in ('left', 'right') ax = self.ax2 if axis == 'right' else self.ax if not ax.get_visible(): return None if self._dirtyLimits and self.isKeepDataAspectRatio(): self.replot() # makes sure we get the right limits return ax.get_ybound() def setGraphYLimits(self, ymin, ymax, axis): ax = self.ax2 if axis == 'right' else self.ax if ymax < ymin: ymin, ymax = ymax, ymin self._dirtyLimits = True if self.isKeepDataAspectRatio(): # matplotlib keeps limits of shared axis when keeping aspect ratio # So x limits are kept when changing y limits.... # Change x limits first by taking into account aspect ratio # and then change y limits.. so matplotlib does not need # to make change (to y) to keep aspect ratio xmin, xmax = ax.get_xbound() curYMin, curYMax = ax.get_ybound() newXRange = (xmax - xmin) * (ymax - ymin) / (curYMax - curYMin) xcenter = 0.5 * (xmin + xmax) ax.set_xlim(xcenter - 0.5 * newXRange, xcenter + 0.5 * newXRange) if not self.isYAxisInverted(): ax.set_ylim(ymin, ymax) else: ax.set_ylim(ymax, ymin) self._updateMarkers() # Graph axes def setXAxisTimeZone(self, tz): super(BackendMatplotlib, self).setXAxisTimeZone(tz) # Make new formatter and locator with the time zone. self.setXAxisTimeSeries(self.isXAxisTimeSeries()) def isXAxisTimeSeries(self): return self._isXAxisTimeSeries def setXAxisTimeSeries(self, isTimeSeries): self._isXAxisTimeSeries = isTimeSeries if self._isXAxisTimeSeries: # We can't use a matplotlib.dates.DateFormatter because it expects # the data to be in datetimes. Silx works internally with # timestamps (floats). locator = NiceDateLocator(tz=self.getXAxisTimeZone()) self.ax.xaxis.set_major_locator(locator) self.ax.xaxis.set_major_formatter( NiceAutoDateFormatter(locator, tz=self.getXAxisTimeZone())) else: try: scalarFormatter = ScalarFormatter(useOffset=False) except: _logger.warning('Cannot disabled axes offsets in %s ' % matplotlib.__version__) scalarFormatter = ScalarFormatter() self.ax.xaxis.set_major_formatter(scalarFormatter) def setXAxisLogarithmic(self, flag): # Workaround for matplotlib 2.1.0 when one tries to set an axis # to log scale with both limits <= 0 # In this case a draw with positive limits is needed first if flag and self._matplotlibVersion >= _parse_version('2.1.0'): xlim = self.ax.get_xlim() if xlim[0] <= 0 and xlim[1] <= 0: self.ax.set_xlim(1, 10) self.draw() self.ax2.set_xscale('log' if flag else 'linear') self.ax.set_xscale('log' if flag else 'linear') def setYAxisLogarithmic(self, flag): # Workaround for matplotlib 2.0 issue with negative bounds # before switching to log scale if flag and self._matplotlibVersion >= _parse_version('2.0.0'): redraw = False for axis, dataRangeIndex in ((self.ax, 1), (self.ax2, 2)): ylim = axis.get_ylim() if ylim[0] <= 0 or ylim[1] <= 0: dataRange = self._plot.getDataRange()[dataRangeIndex] if dataRange is None: dataRange = 1, 100 # Fallback axis.set_ylim(*dataRange) redraw = True if redraw: self.draw() self.ax2.set_yscale('log' if flag else 'linear') self.ax.set_yscale('log' if flag else 'linear') def setYAxisInverted(self, flag): if self.ax.yaxis_inverted() != bool(flag): self.ax.invert_yaxis() def isYAxisInverted(self): return self.ax.yaxis_inverted() def isKeepDataAspectRatio(self): return self.ax.get_aspect() in (1.0, 'equal') def setKeepDataAspectRatio(self, flag): self.ax.set_aspect(1.0 if flag else 'auto') self.ax2.set_aspect(1.0 if flag else 'auto') def setGraphGrid(self, which): self.ax.grid(False, which='both') # Disable all grid first if which is not None: self.ax.grid(True, which=which) # Data <-> Pixel coordinates conversion def _mplQtYAxisCoordConversion(self, y): """Qt origin (top) to/from matplotlib origin (bottom) conversion. :rtype: float """ height = self.fig.get_window_extent().height return height - y def dataToPixel(self, x, y, axis): ax = self.ax2 if axis == "right" else self.ax pixels = ax.transData.transform_point((x, y)) xPixel, yPixel = pixels.T # Convert from matplotlib origin (bottom) to Qt origin (top) yPixel = self._mplQtYAxisCoordConversion(yPixel) return xPixel, yPixel def pixelToData(self, x, y, axis, check): ax = self.ax2 if axis == "right" else self.ax # Convert from Qt origin (top) to matplotlib origin (bottom) y = self._mplQtYAxisCoordConversion(y) inv = ax.transData.inverted() x, y = inv.transform_point((x, y)) if check: xmin, xmax = self.getGraphXLimits() ymin, ymax = self.getGraphYLimits(axis=axis) if x > xmax or x < xmin or y > ymax or y < ymin: return None # (x, y) is out of plot area return x, y def getPlotBoundsInPixels(self): bbox = self.ax.get_window_extent() # Warning this is not returning int... return (bbox.xmin, self._mplQtYAxisCoordConversion(bbox.ymax), bbox.width, bbox.height) def setAxesDisplayed(self, displayed): """Display or not the axes. :param bool displayed: If `True` axes are displayed. If `False` axes are not anymore visible and the margin used for them is removed. """ BackendBase.BackendBase.setAxesDisplayed(self, displayed) if displayed: # show axes and viewbox rect self.ax.set_axis_on() self.ax2.set_axis_on() # set the default margins self.ax.set_position([.15, .15, .75, .75]) self.ax2.set_position([.15, .15, .75, .75]) else: # hide axes and viewbox rect self.ax.set_axis_off() self.ax2.set_axis_off() # remove external margins self.ax.set_position([0, 0, 1, 1]) self.ax2.set_position([0, 0, 1, 1]) self._synchronizeBackgroundColors() self._synchronizeForegroundColors() self._plot._setDirtyPlot() def _synchronizeBackgroundColors(self): backgroundColor = self._plot.getBackgroundColor().getRgbF() dataBackgroundColor = self._plot.getDataBackgroundColor() if dataBackgroundColor.isValid(): dataBackgroundColor = dataBackgroundColor.getRgbF() else: dataBackgroundColor = backgroundColor if self.ax.axison: self.fig.patch.set_facecolor(backgroundColor) if self._matplotlibVersion < _parse_version('2'): self.ax.set_axis_bgcolor(dataBackgroundColor) else: self.ax.set_facecolor(dataBackgroundColor) else: self.fig.patch.set_facecolor(dataBackgroundColor) def _synchronizeForegroundColors(self): foregroundColor = self._plot.getForegroundColor().getRgbF() gridColor = self._plot.getGridColor() if gridColor.isValid(): gridColor = gridColor.getRgbF() else: gridColor = foregroundColor if self.ax.axison: self.ax.spines['bottom'].set_color(foregroundColor) self.ax.spines['top'].set_color(foregroundColor) self.ax.spines['right'].set_color(foregroundColor) self.ax.spines['left'].set_color(foregroundColor) self.ax.tick_params(axis='x', colors=foregroundColor) self.ax.tick_params(axis='y', colors=foregroundColor) self.ax.yaxis.label.set_color(foregroundColor) self.ax.xaxis.label.set_color(foregroundColor) self.ax.title.set_color(foregroundColor) for line in self.ax.get_xgridlines(): line.set_color(gridColor) for line in self.ax.get_ygridlines(): line.set_color(gridColor)