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
class FigureErrorsManagerTest(unittest.TestCase): """ Test class that covers the interaction of the FigureErrorsManager with plots that use the mantid projection and have MantidAxes """ @classmethod def setUpClass(cls): cls.ws2d_histo = CreateWorkspace( DataX=[10, 20, 30, 10, 20, 30, 10, 20, 30], DataY=[2, 3, 4, 5, 3, 5], DataE=[1, 2, 3, 4, 1, 1], NSpec=3, Distribution=True, UnitX='Wavelength', VerticalAxisUnit='DeltaE', VerticalAxisValues=[4, 6, 8], OutputWorkspace='ws2d_histo') # initialises the QApplication super(cls, FigureErrorsManagerTest).setUpClass() def setUp(self): self.fig, self.ax = plt.subplots(subplot_kw={'projection': 'mantid'}) self.errors_manager = FigureErrorsManager(self.fig.canvas) def tearDown(self): plt.close('all') del self.fig del self.ax del self.errors_manager @plot_things(make_them_errors=False) def test_show_all_errors(self): # assert plot does not have errors self.assertEqual(0, len(self.ax.containers)) self.errors_manager.toggle_all_errors(self.ax, make_visible=True) # check that the errors have been added self.assertEqual(2, len(self.ax.containers)) @plot_things(make_them_errors=True) def test_hide_all_errors(self): self.assertEqual(2, len(self.ax.containers)) self.errors_manager.toggle_all_errors(self.ax, make_visible=False) # errors still exist self.assertEqual(2, len(self.ax.containers)) # they are just invisible self.assertFalse(self.ax.containers[0][2][0].get_visible()) @plot_things(make_them_errors=True) def test_hide_all_errors_retains_legend_properties(self): # create a legend with a title self.ax.legend(title="Test") self.errors_manager.toggle_all_errors(self.ax, make_visible=False) # check that the legend still has a title self.assertEqual(self.ax.get_legend().get_title().get_text(), "Test") @plot_things(make_them_errors=False) def test_show_all_errors_retains_legend_properties(self): # create a legend with a title self.ax.legend(title="Test") self.errors_manager.toggle_all_errors(self.ax, make_visible=True) # check that the legend still has a title self.assertEqual(self.ax.get_legend().get_title().get_text(), "Test") def test_curve_has_all_errorbars_on_replot_after_error_every_increase( self): curve = self.ax.errorbar([0, 1, 2, 4], [0, 1, 2, 4], yerr=[0.1, 0.2, 0.3, 0.4]) new_curve = FigureErrorsManager._replot_mpl_curve( self.ax, curve, {'errorevery': 2}) self.assertEqual(2, len(new_curve[2][0].get_segments())) new_curve = FigureErrorsManager._replot_mpl_curve( self.ax, new_curve, {'errorevery': 1}) self.assertTrue(hasattr(new_curve, 'errorbar_data')) self.assertEqual(4, len(new_curve[2][0].get_segments())) def test_show_all_errors_on_waterfall_plot_retains_waterfall(self): self.ax.plot([0, 1], [0, 1]) self.ax.plot([0, 1], [0, 1]) self.ax.set_waterfall(True) self.errors_manager.toggle_all_errors(self.ax, make_visible=True) self.assertFalse( array_equal(self.ax.get_lines()[0].get_data(), self.ax.get_lines()[1].get_data())) def test_hide_all_errors_on_waterfall_plot_retains_waterfall(self): self.ax.plot([0, 1], [0, 1]) self.ax.plot([0, 1], [0, 1]) self.ax.set_waterfall(True) self.errors_manager.toggle_all_errors(self.ax, make_visible=True) self.errors_manager.toggle_all_errors(self.ax, make_visible=False) self.assertFalse( array_equal(self.ax.get_lines()[0].get_data(), self.ax.get_lines()[1].get_data())) def test_creation_args_not_accessed_for_non_workspace_plots(self): self.ax.plot([1, 2], [1, 2]) self.errors_manager.replot_curve(self.ax, self.ax.lines[0], {}) self.assertEqual(0, len(self.ax.creation_args))
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)