def waterfall_reverse_line_order(self): ax = self.canvas.figure.get_axes()[0] x, y = ax.waterfall_x_offset, ax.waterfall_y_offset fills = datafunctions.get_waterfall_fills(ax) ax.update_waterfall(0, 0) errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines( ax) ax.lines.reverse() for cap in errorbar_cap_lines: ax.add_line(cap) if LooseVersion("3.7") > LooseVersion( matplotlib.__version__) >= LooseVersion("3.2"): for line_fill in fills: if line_fill not in ax.collections: ax.add_collection(line_fill) elif LooseVersion(matplotlib.__version__) < LooseVersion("3.2"): ax.collections += fills else: raise NotImplementedError( "ArtistList will become an immutable tuple in matplotlib 3.7 and thus, " "this code doesn't work anymore.") ax.collections.reverse() ax.update_waterfall(x, y) if ax.get_legend(): ax.make_legend() self.canvas.draw()
def errorbar_caps_removed(ax): # Error bar caps are considered lines so they are removed before checking the number of lines on the axes so # they aren't confused for "actual" lines. error_bar_caps = datafunctions.remove_and_return_errorbar_cap_lines(ax) yield # Re-add error bar caps ax.lines += error_bar_caps
def _add_plot_type_option_menu(self, menu, ax): # Error bar caps are considered lines so they are removed before checking the number of lines on the axes so # they aren't confused for "actual" lines. error_bar_caps = datafunctions.remove_and_return_errorbar_cap_lines(ax) # Able to change the plot type to waterfall if there is only one axes, it is a MantidAxes, and there is more # than one line on the axes. if len(ax.get_figure().get_axes()) > 1 or not isinstance( ax, MantidAxes) or len(ax.get_lines()) <= 1: return # Re-add error bar caps ax.lines += error_bar_caps plot_type_menu = QMenu("Plot Type", menu) plot_type_action_group = QActionGroup(plot_type_menu) standard = plot_type_menu.addAction( "1D", lambda: self._change_plot_type( ax, plot_type_action_group.checkedAction())) waterfall = plot_type_menu.addAction( "Waterfall", lambda: self._change_plot_type( ax, plot_type_action_group.checkedAction())) for action in [waterfall, standard]: plot_type_action_group.addAction(action) action.setCheckable(True) if ax.is_waterfall(): waterfall.setChecked(True) else: standard.setChecked(True) menu.addMenu(plot_type_menu)
def _replot_selected_curve(self, plot_kwargs): """Replot the selected curve with the given plot kwargs""" ax = self.get_selected_ax() curve = self.get_selected_curve() waterfall = False if isinstance(ax, MantidAxes): waterfall = ax.is_waterfall() check_line_colour = False # If the plot is a waterfall plot and the user has set it so the area under each line is filled, and the fill # colour for each line is set as the line colour, after the curve is updated we need to check if its colour has # changed so the fill can be updated accordingly. if waterfall and ax.waterfall_has_fill( ) and datafunctions.waterfall_fill_is_line_colour(ax): check_line_colour = True if isinstance(curve, Line2D): curve_index = ax.get_lines().index(curve) errorbar = False else: curve_index = ax.get_lines().index(curve[0]) errorbar = True new_curve = FigureErrorsManager.replot_curve(ax, curve, plot_kwargs) self.curve_names_dict[self.view.get_selected_curve_name()] = new_curve if isinstance(ax, MantidAxes): errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines( ax) else: errorbar_cap_lines = [] # When a curve is redrawn it is moved to the back of the list of curves so here it is moved back to its previous # position. This is so that the correct offset is applied to the curve if the plot is a waterfall plot, but it # also just makes sense for the curve order to remain unchanged. ax.lines.insert(curve_index, ax.lines.pop()) if waterfall: if check_line_colour: # curve can be either a Line2D or an ErrorContainer and the colour is accessed differently for each. if not errorbar: # if the line colour hasn't changed then the fill colour doesn't need to be updated. update_fill = curve.get_color() != new_curve[0].get_color() else: update_fill = curve[0].get_color( ) != new_curve[0].get_color() datafunctions.convert_single_line_to_waterfall( ax, curve_index, need_to_update_fill=update_fill) else: # the curve has been reset to its original position so for a waterfall plot it needs to be re-offset. datafunctions.convert_single_line_to_waterfall(ax, curve_index) datafunctions.set_waterfall_fill_visible(ax, curve_index) ax.lines += errorbar_cap_lines
def waterfall_reverse_line_order(self): ax = self.canvas.figure.get_axes()[0] x, y = ax.waterfall_x_offset, ax.waterfall_y_offset fills = datafunctions.get_waterfall_fills(ax) ax.update_waterfall(0, 0) errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines(ax) ax.lines.reverse() ax.lines += errorbar_cap_lines ax.collections += fills ax.collections.reverse() ax.update_waterfall(x, y) if ax.get_legend(): ax.make_legend()
def _overplot_waterfall(ax, no_of_lines): """ If overplotting onto a waterfall axes, convert lines to waterfall (add x and y offset) before overplotting. :param ax: object of MantidAxes type to overplot onto. :param no_of_lines: number of lines to overplot onto input axes. """ for i in range(no_of_lines): errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines( ax) datafunctions.convert_single_line_to_waterfall( ax, len(ax.get_lines()) - (i + 1)) if ax.waterfall_has_fill(): datafunctions.waterfall_update_fill(ax) for cap in errorbar_cap_lines: ax.add_line(cap)
def toggle_error_bars_for(cls, ax, curve, make_visible=None): # get legend properties if ax.legend_: legend_props = LegendProperties.from_legend(ax.legend_) else: legend_props = None if isinstance(curve, Line2D): curve_index = ax.get_lines().index(curve) else: curve_index = ax.get_lines().index(curve[0]) # get all curve properties curve_props = CurveProperties.from_curve(curve) # and remove the ones that matplotlib doesn't recognise plot_kwargs = curve_props.get_plot_kwargs() new_curve = cls.replot_curve(ax, curve, plot_kwargs) if isinstance(ax, MantidAxes): errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines( ax) else: errorbar_cap_lines = [] ax.lines.insert(curve_index, ax.lines.pop()) if isinstance(ax, MantidAxes) and ax.is_waterfall(): datafunctions.convert_single_line_to_waterfall(ax, curve_index) for cap in errorbar_cap_lines: ax.add_line(cap) # Inverts either the current state of hide_errors # or the make_visible kwarg that forces a state: # If make visible is True, then hide_errors must be False # for the intended effect curve_props.hide_errors = not curve_props.hide_errors if make_visible is None else not make_visible cls.toggle_errors(new_curve, curve_props) cls.update_limits_and_legend(ax, legend_props)
def plot(workspaces, spectrum_nums=None, wksp_indices=None, errors=False, overplot=False, fig=None, plot_kwargs=None, ax_properties=None, window_title=None, tiled=False, waterfall=False, log_name=None, log_values=None): """ Create a figure with a single subplot and for each workspace/index add a line plot to the new axes. show() is called before returning the figure instance. A legend is added. :param workspaces: A list of workspace handles or strings :param spectrum_nums: A list of spectrum number identifiers (general start from 1) :param wksp_indices: A list of workspace indexes (starts from 0) :param errors: If true then error bars are added for each plot :param overplot: If true then overplot over the current figure if one exists. If an axis object the overplotting will be done on the axis passed in :param fig: If not None then use this Figure object to plot :param plot_kwargs: Arguments that will be passed onto the plot function :param ax_properties: A dict of axes properties. E.g. {'yscale': 'log'} :param window_title: A string denoting name of the GUI window which holds the graph :param tiled: An optional flag controlling whether to do a tiled or overlayed plot :param waterfall: An optional flag controlling whether or not to do a waterfall plot :param log_name: The optional log being plotted against. :param log_values: An optional list of log values to plot against. :return: The figure containing the plots """ plot_font = ConfigService.getString('plots.font') if plot_font: if len(mpl.rcParams['font.family']) > 1: mpl.rcParams['font.family'][0] = plot_font else: mpl.rcParams['font.family'].insert(0, plot_font) if plot_kwargs is None: plot_kwargs = {} _validate_plot_inputs(workspaces, spectrum_nums, wksp_indices, tiled, overplot) workspaces = [ws for ws in workspaces if isinstance(ws, MatrixWorkspace)] if spectrum_nums is not None: kw, nums = 'specNum', spectrum_nums else: kw, nums = 'wkspIndex', wksp_indices _add_default_plot_kwargs_from_settings(plot_kwargs, errors) num_axes = len(workspaces) * len(nums) if tiled else 1 fig, axes = get_plot_fig(overplot, ax_properties, window_title, num_axes, fig) # Convert to a MantidAxes if it isn't already. Ignore legend since # a new one will be drawn later axes = [ MantidAxes.from_mpl_axes(ax, ignore_artists=[Legend]) if not isinstance(ax, MantidAxes) else ax for ax in axes ] assert axes, "No axes are associated with this plot" if tiled: ws_index = [(ws, index) for ws in workspaces for index in nums] for index, ax in enumerate(axes): if index < len(ws_index): _do_single_plot(ax, [ws_index[index][0]], errors, False, [ws_index[index][1]], kw, plot_kwargs) else: ax.axis('off') else: show_title = ("on" == ConfigService.getString( "plots.ShowTitle").lower()) and not overplot ax = overplot if isinstance(overplot, MantidAxes) else axes[0] ax.axis('on') _do_single_plot(ax, workspaces, errors, show_title, nums, kw, plot_kwargs, log_name, log_values) # Can't have a waterfall plot with only one line. if len(nums) * len(workspaces) == 1 and waterfall: waterfall = False # The plot's initial xlim and ylim are used to offset each curve in a waterfall plot. # Need to do this whether the current curve is a waterfall plot or not because it may be converted later. if not overplot: datafunctions.set_initial_dimensions(ax) if waterfall: ax.set_waterfall(True) if not overplot: fig.canvas.set_window_title(figure_title(workspaces, fig.number)) else: if ax.is_waterfall(): for i in range(len(nums) * len(workspaces)): errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines( ax) datafunctions.convert_single_line_to_waterfall( ax, len(ax.get_lines()) - (i + 1)) if ax.waterfall_has_fill(): datafunctions.waterfall_update_fill(ax) ax.lines += errorbar_cap_lines # update and show figure return _update_show_figure(fig)
def _replot_current_curve(self, plot_kwargs): """Replot the selected curve with the given plot kwargs""" ax = self.get_selected_ax() curve = self.get_current_curve() waterfall = False if isinstance(ax, MantidAxes): waterfall = ax.is_waterfall() check_line_colour = False # If the plot is a waterfall plot and the user has set it so the area under each line is filled, and the fill # colour for each line is set as the line colour, after the curve is updated we need to check if its colour has # changed so the fill can be updated accordingly. if waterfall and ax.waterfall_has_fill() and datafunctions.waterfall_fill_is_line_colour(ax): check_line_colour = True if isinstance(curve, Line2D): curve_index = ax.get_lines().index(curve) errorbar = False else: curve_index = ax.get_lines().index(curve[0]) errorbar = True # When you remove the curve on a waterfall plot, the remaining curves are repositioned so that they are # equally spaced apart. However since the curve is being replotted we don't want that to happen, so here # the waterfall offsets are set to 0 so the plot appears to be non-waterfall. The offsets are then re-set # after the curve is replotted. if waterfall: x_offset, y_offset = ax.waterfall_x_offset, ax.waterfall_y_offset ax.waterfall_x_offset = ax.waterfall_y_offset = 0 new_curve = FigureErrorsManager.replot_curve(ax, curve, plot_kwargs) self.curve_names_dict[self.view.get_current_curve_name()] = new_curve if isinstance(ax, MantidAxes): errorbar_cap_lines = datafunctions.remove_and_return_errorbar_cap_lines(ax) else: errorbar_cap_lines = [] # TODO: Accessing the ax.lines property is deprecated in mpl 3.5. It must be removed by mpl 3.7. with warnings.catch_warnings(): warnings.simplefilter("ignore") # When a curve is redrawn it is moved to the back of the list of curves so here it is moved back to its previous # position. This is so that the correct offset is applied to the curve if the plot is a waterfall plot, but it # also just makes sense for the curve order to remain unchanged. ax.lines.insert(curve_index, ax.lines.pop()) if waterfall: # Set the waterfall offsets to what they were previously. ax.waterfall_x_offset, ax.waterfall_y_offset = x_offset, y_offset if check_line_colour: # curve can be either a Line2D or an ErrorContainer and the colour is accessed differently for each. if not errorbar: # if the line colour hasn't changed then the fill colour doesn't need to be updated. update_fill = curve.get_color() != new_curve[0].get_color() else: update_fill = curve[0].get_color() != new_curve[0].get_color() datafunctions.convert_single_line_to_waterfall(ax, curve_index, need_to_update_fill=update_fill) else: # the curve has been reset to its original position so for a waterfall plot it needs to be re-offset. datafunctions.convert_single_line_to_waterfall(ax, curve_index) datafunctions.set_waterfall_fill_visible(ax, curve_index) for cap in errorbar_cap_lines: ax.add_line(cap)