diff --git a/docs/source/release/v4.3.0/muon.rst b/docs/source/release/v4.3.0/muon.rst index 65f52095033dd63c926dc93b785445acd6b0b49d..9e1a577df08797a86362f55129d65929c9147e1c 100644 --- a/docs/source/release/v4.3.0/muon.rst +++ b/docs/source/release/v4.3.0/muon.rst @@ -5,13 +5,23 @@ MuSR Changes .. contents:: Table of Contents :local: -.. warning:: **Developers:** Sort changes under appropriate heading - putting new features at the top of the section, followed by - improvements, followed by bug fixes. +New +### Improvements ############ +Algorithms +------------- + +Muon Analysis 2 and Frequency Domain Interfaces +--------------------------------------------------- +- The plotting window within the Muon Analysis 2 and Frequency Domain Interfaces has been converted into a dockable window, + which can be docked and undocked at will. This change should improve the user experience for those working on laptops. + +Bug Fixes +######### + - The elemental analysis GUI can now handle legacy data which is missing a response dataset, e.g Delayed. :ref:`Release 4.3.0 <v4.3.0>` \ No newline at end of file diff --git a/qt/python/mantidqt/plotting/functions.py b/qt/python/mantidqt/plotting/functions.py index 3f32789105885a1c72a7f14c0d0445ac401552e8..a78adbfeb6ced635dda7cbad478fec3b185f921b 100644 --- a/qt/python/mantidqt/plotting/functions.py +++ b/qt/python/mantidqt/plotting/functions.py @@ -31,7 +31,6 @@ from mantid.plots import MantidAxes from mantidqt.plotting.figuretype import figure_type, FigureType from mantid.py3compat import is_text_string, string_types from mantidqt.dialogs.spectraselectorutils import get_spectra_selection - # ----------------------------------------------------------------------------- # Constants # ----------------------------------------------------------------------------- @@ -206,7 +205,6 @@ def plot(workspaces, spectrum_nums=None, wksp_indices=None, errors=False, if plot_kwargs is None: plot_kwargs = {} _validate_plot_inputs(workspaces, spectrum_nums, wksp_indices, tiled, overplot) - if spectrum_nums is not None: kw, nums = 'specNum', spectrum_nums else: @@ -235,14 +233,21 @@ def plot(workspaces, spectrum_nums=None, wksp_indices=None, errors=False, if not overplot: fig.canvas.set_window_title(figure_title(workspaces, fig.number)) - # This updates the toolbar so the home button now takes you back to this point, the try catch is in case the manager does not - # have a toolbar attatched. + # This updates the toolbar so the home button now takes you back to this point. + # The try catch is in case the manager does not have a toolbar attached. try: fig.canvas.manager.toolbar.update() except AttributeError: pass + fig.canvas.draw() - fig.show() + # This displays the figure, but only works if a manager is attached to the figure. + # The try catch is in case a figure manager is not present + try: + fig.show() + except AttributeError: + pass + return fig @@ -253,7 +258,6 @@ def _do_single_plot(ax, workspaces, errors, set_title, nums, kw, plot_kwargs): for num in nums: plot_kwargs[kw] = num plot_fn(ws, **plot_kwargs) - ax.make_legend() if set_title: title = workspaces[0].name() diff --git a/scripts/Muon/GUI/Common/contexts/muon_context.py b/scripts/Muon/GUI/Common/contexts/muon_context.py index 03f942245bcd519ee2af6bc73885b6f9cc72b87b..6718b8fee7d3090446990d947eda2d38148f4643 100644 --- a/scripts/Muon/GUI/Common/contexts/muon_context.py +++ b/scripts/Muon/GUI/Common/contexts/muon_context.py @@ -36,7 +36,8 @@ class MuonContext(object): self.ads_observer = MuonContextADSObserver( self.remove_workspace_by_name, - self.clear_context) + self.clear_context, + self.workspace_replaced) self.gui_context.update( {'DeadTimeSource': 'None', @@ -44,6 +45,7 @@ class MuonContext(object): 'selected_group_pair': ''}) self.update_view_from_model_notifier = Observable() + self.update_plots_notifier = Observable() def __del__(self): self.ads_observer.unsubscribe() @@ -385,3 +387,6 @@ class MuonContext(object): self.phase_context.clear() self.fitting_context.clear() self.update_view_from_model_notifier.notify_subscribers() + + def workspace_replaced(self, workspace): + self.update_plots_notifier.notify_subscribers(workspace) diff --git a/scripts/Muon/GUI/Common/contexts/muon_context_ADS_observer.py b/scripts/Muon/GUI/Common/contexts/muon_context_ADS_observer.py index e88e0d5693713df98cb679e142418e26cbf346c1..446002b9b264bf26f45cd3b1d386bbd287f91d59 100644 --- a/scripts/Muon/GUI/Common/contexts/muon_context_ADS_observer.py +++ b/scripts/Muon/GUI/Common/contexts/muon_context_ADS_observer.py @@ -28,13 +28,15 @@ def _catch_exceptions(func): class MuonContextADSObserver(AnalysisDataServiceObserver): - def __init__(self, delete_callback, clear_callback): + def __init__(self, delete_callback, clear_callback, replace_callback): super(MuonContextADSObserver, self).__init__() self.delete_callback = delete_callback self.clear_callback = clear_callback + self.replace_callback = replace_callback self.observeDelete(True) self.observeRename(True) + self.observeReplace(True) self.observeClear(True) @_catch_exceptions @@ -63,6 +65,16 @@ class MuonContextADSObserver(AnalysisDataServiceObserver): """ self.clear_callback() + @_catch_exceptions + def replaceHandle(self, name, workspace): + """ + Called when the ADS has replaced a workspace with one of the same name. + If this workspace is attached to this figure then its data is updated + :param name: The name of the workspace. + :param workspace: A reference to the new workspace (Unused) + """ + self.replace_callback(name) + def unsubscribe(self): self.observeDelete(False) self.observeRename(False) diff --git a/scripts/Muon/GUI/Common/home_tab/home_tab_widget.py b/scripts/Muon/GUI/Common/home_tab/home_tab_widget.py index 76aae34202db744e30c0cb18b9dec43807eb64ff..aeddc778bd3887fd34eec804990f3789b8e466c2 100644 --- a/scripts/Muon/GUI/Common/home_tab/home_tab_widget.py +++ b/scripts/Muon/GUI/Common/home_tab/home_tab_widget.py @@ -22,17 +22,12 @@ from Muon.GUI.Common.home_tab.home_tab_model import HomeTabModel from Muon.GUI.Common.home_tab.home_tab_view import HomeTabView from Muon.GUI.Common.home_tab.home_tab_presenter import HomeTabPresenter -from Muon.GUI.Common.home_plot_widget.home_plot_widget_model import HomePlotWidgetModel -from Muon.GUI.Common.home_plot_widget.home_plot_widget_presenter import HomePlotWidgetPresenter -from Muon.GUI.Common.home_plot_widget.home_plot_widget_view import HomePlotWidgetView - class HomeTabWidget(object): def __init__(self, context, parent): self.inst_view = InstrumentWidgetView(parent) self.grp_view = HomeGroupingWidgetView(parent) self.run_info_view = HomeRunInfoWidgetView(parent) - self.plot_view = HomePlotWidgetView(parent) # keep a handle to the presenters of sub-widgets self.instrument_widget = InstrumentWidgetPresenter(self.inst_view, @@ -40,17 +35,14 @@ class HomeTabWidget(object): self.group_widget = HomeGroupingWidgetPresenter(self.grp_view, HomeGroupingWidgetModel(context=context)) self.run_info_widget = HomeRunInfoWidgetPresenter(self.run_info_view, HomeRunInfoWidgetModel(context=context)) - self.plot_widget = HomePlotWidgetPresenter(self.plot_view, HomePlotWidgetModel(), context) self.home_tab_view = HomeTabView(parent=parent, widget_list=[self.inst_view, self.grp_view, - self.plot_view, self.run_info_view]) self.home_tab_model = HomeTabModel(context=context) self.home_tab_widget = HomeTabPresenter(self.home_tab_view, self.home_tab_model, subwidgets=[self.instrument_widget, self.group_widget, - self.plot_widget, self.run_info_widget]) context.update_view_from_model_notifier.add_subscriber(self.home_tab_widget.update_view_from_model_observer) diff --git a/scripts/Muon/GUI/Common/home_plot_widget/__init__.py b/scripts/Muon/GUI/Common/plotting_widget/__init__.py similarity index 100% rename from scripts/Muon/GUI/Common/home_plot_widget/__init__.py rename to scripts/Muon/GUI/Common/plotting_widget/__init__.py diff --git a/scripts/Muon/GUI/Common/plotting_widget/plotting_widget.py b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget.py new file mode 100644 index 0000000000000000000000000000000000000000..fdbc2e15fef4391dad17f001d65d5bbf84e31244 --- /dev/null +++ b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget.py @@ -0,0 +1,30 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,k +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, unicode_literals) + +from Muon.GUI.Common.plotting_widget.plotting_widget_view import PlotWidgetView +from Muon.GUI.Common.plotting_widget.plotting_widget_presenter import PlotWidgetPresenter +from Muon.GUI.Common.plotting_widget.plotting_widget_model import PlotWidgetModel + + +class PlottingWidget(object): + def __init__(self, context=None): + # initialise the view, presenter and model. + # view + self.view = PlotWidgetView(parent=None) + # model + self.model = PlotWidgetModel(self.view.get_fig()) + # presenter + self.presenter = PlotWidgetPresenter(self.view, + self.model, + context) + + context.update_view_from_model_notifier.add_subscriber(self.presenter.workspace_deleted_from_ads_observer) + context.update_plots_notifier.add_subscriber(self.presenter.workspace_replaced_in_ads_observer) + + def close(self): + self.view.close() diff --git a/scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_model.py b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget_model.py similarity index 71% rename from scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_model.py rename to scripts/Muon/GUI/Common/plotting_widget/plotting_widget_model.py index 3d23b521d60da13053a7f91e554982ba68e5cd71..6f8c42f4b01b8bfec75e777d167f2b45ae2cf8a0 100644 --- a/scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_model.py +++ b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget_model.py @@ -11,12 +11,12 @@ from mantidqt.plotting.functions import plot from mantid.api import AnalysisDataService -class HomePlotWidgetModel(object): - def __init__(self): +class PlotWidgetModel(object): + def __init__(self, dockable_plot_window=None): """ :param plotting_window_model: This is the plotting manager class to use """ - self.plot_figure = None + self.plot_figure = dockable_plot_window self._plotted_workspaces = [] self._plotted_workspaces_inverse_binning = {} self._plotted_fit_workspaces = [] @@ -35,7 +35,8 @@ class HomePlotWidgetModel(object): @property def plotted_workspaces_inverse_binning(self): - self._plotted_workspaces_inverse_binning = {key: item for key, item in self._plotted_workspaces_inverse_binning.items() + self._plotted_workspaces_inverse_binning = {key: item for key, item in + self._plotted_workspaces_inverse_binning.items() if key in self.plot_figure.gca().tracked_workspaces.keys()} return self._plotted_workspaces_inverse_binning @@ -57,7 +58,7 @@ class HomePlotWidgetModel(object): def plotted_fit_workspaces(self, value): self._plotted_fit_workspaces = value - def plot(self, workspace_list, title, domain, force_redraw, window_title): + def plot(self, workspace_list, title, domain, window_title): """ Plots a list of workspaces in a new plot window, closing any existing plot windows. :param workspace_list: A list of workspace name to plot. They must be in the ADS @@ -68,57 +69,47 @@ class HomePlotWidgetModel(object): :return: A reference to the newly created plot window is passed back """ if not workspace_list: - self.plotted_workspaces = [] - self.plotted_workspaces_inverse_binning = {} - self.plotted_fit_workspaces = [] - self.plot_figure.clear() - self.plot_figure.canvas.draw() - return self.plot_figure + return try: workspaces = AnalysisDataService.Instance().retrieveWorkspaces(workspace_list, unrollGroups=True) except RuntimeError: return - if (force_redraw or self.plotted_workspaces == []) and self.plot_figure: - - self.plot_figure.clear() - self.plotted_workspaces = [] - self.plotted_workspaces_inverse_binning = {} - self.plotted_fit_workspaces = [] - self.plot_figure = plot(workspaces,wksp_indices=[0], fig=self.plot_figure, window_title=title, - plot_kwargs={'distribution': True, 'autoscale_on_update': False}, errors=True) - self.set_x_lim(domain) - - elif self.plot_figure: - axis = self.plot_figure.gca() - xlim = axis.get_xlim() - ylim = axis.get_ylim() - self._remove_all_data_workspaces_from_plot() - self.plot_figure = plot(workspaces, wksp_indices=[0], fig=self.plot_figure, window_title=title, - plot_kwargs={'distribution': True, 'autoscale_on_update': False}, errors=True) - axis = self.plot_figure.gca() - axis.set_xlim(xlim) - axis.set_ylim(ylim) - else: - self.plot_figure = plot(workspaces, wksp_indices=[0], window_title=title, plot_kwargs={'distribution': True, - 'autoscale_on_update': False}, errors=True) - self.set_x_lim(domain) + # Clean up previous plot + self._remove_all_data_workspaces_from_plot() + # clear the figure + self.plotted_fit_workspaces = [] + self.plotted_workspaces = [] + self.plotted_workspaces_inverse_binning = {} + # plot new workspace + plot(workspaces, wksp_indices=[0], fig=self.plot_figure, window_title=title, + overplot=True, + plot_kwargs={'distribution': True, 'autoscale_on_update': False}, errors=True) + # set x and y limits + self.set_x_lim(domain) + # update the toolbar + toolbar = self.plot_figure.canvas.toolbar + toolbar.update() + + # set title and adjust plot size, and legend scale self.plot_figure.canvas.set_window_title(window_title) self.plot_figure.gca().set_title(title) - - self.plot_figure.canvas.window().closing.connect(self._clear_plot_references) - - workspaces_to_remove = [workspace for workspace in self.plotted_workspaces if workspace not in workspace_list] - for workspace in workspaces_to_remove: - self.remove_workpace_from_plot(workspace) + self.plot_figure.tight_layout() + ax = self.plot_figure.gca() + ax.legend(prop=dict(size=7)) + self.plot_figure.canvas.draw() self.plotted_workspaces = workspace_list - def set_x_lim(self,domain): + def set_x_lim(self, domain): if domain == "Time": self.plot_figure.gca().set_xlim(left=0.0, right=15.0) self.autoscale_y_to_data_in_view() self.plot_figure.canvas.draw() + if domain == "Frequency": + self.plot_figure.gca().set_xlim(left=0.0, right=50.0) + self.autoscale_y_to_data_in_view() + self.plot_figure.canvas.draw() def add_workspace_to_plot(self, workspace_name, workspace_index, label): """ @@ -136,10 +127,17 @@ class HomePlotWidgetModel(object): workspace_index = 3 self.plot_figure = plot(workspaces, wksp_indices=[workspace_index], fig=self.plot_figure, overplot=True, - plot_kwargs={'distribution': True, 'zorder': 4, 'autoscale_on_update': False, 'label': label}) + plot_kwargs={'distribution': True, 'zorder': 4, 'autoscale_on_update': False, + 'label': label}) + + if workspace_name not in self.plotted_fit_workspaces: + self.plotted_fit_workspaces.append(workspace_name) - if workspace_name not in self._plotted_fit_workspaces: - self._plotted_fit_workspaces.append(workspace_name) + # update the legend + ax = self.plot_figure.gca() + ax.legend(prop=dict(size=7)) + + self.plot_figure.canvas.draw() def remove_workpace_from_plot(self, workspace_name): """ @@ -156,6 +154,20 @@ class HomePlotWidgetModel(object): if workspace_name in self.plotted_workspaces_inverse_binning: self.plotted_workspaces_inverse_binning.pop(workspace_name) + def remove_workspace_from_plot_by_name(self, workspace_name): + + ax = self.plot_figure.gca() + artist_info = ax.tracked_workspaces.pop(workspace_name) + for workspace_artist in artist_info: + workspace_artist.remove(ax) + + self.plotted_workspaces = [item for item in self.plotted_workspaces if item != workspace_name] + self.plotted_fit_workspaces = [item for item in self.plotted_fit_workspaces if item != workspace_name] + if workspace_name in self.plotted_workspaces_inverse_binning: + self.plotted_workspaces_inverse_binning.pop(workspace_name) + + self.plot_figure.canvas.draw() + def _clear_plot_references(self): """ callback to call when the plot window is closed. Removes the reference and resets plotted workspaces @@ -179,7 +191,7 @@ class HomePlotWidgetModel(object): for line in axis.lines: x, y = line.get_data() start, stop = np.searchsorted(x, xlim) - y_within_range = y[max(start-1,0):(stop+1)] + y_within_range = y[max(start - 1, 0):(stop + 1)] ylim = min(ylim[0], np.nanmin(y_within_range)), max(ylim[1], np.nanmax(y_within_range)) new_bottom = ylim[0] * 1.3 if ylim[0] < 0.0 else ylim[0] * 0.7 @@ -191,3 +203,6 @@ class HomePlotWidgetModel(object): workspaces_to_remove = self.plotted_workspaces for workspace in workspaces_to_remove: self.remove_workpace_from_plot(workspace) + workspaces_to_remove = self.plotted_fit_workspaces + for workspace in workspaces_to_remove: + self.remove_workpace_from_plot(workspace) diff --git a/scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_presenter.py b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget_presenter.py similarity index 84% rename from scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_presenter.py rename to scripts/Muon/GUI/Common/plotting_widget/plotting_widget_presenter.py index 91de513bf717d8d53695e74bb786e63d13a39e87..bae264b22f3c3111f4b8645d3a0473b086c7b222 100644 --- a/scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_presenter.py +++ b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget_presenter.py @@ -7,18 +7,17 @@ from __future__ import (absolute_import, division, print_function) from Muon.GUI.Common.home_tab.home_tab_presenter import HomeTabSubWidget -from mantidqt.utils.observer_pattern import GenericObservable, GenericObserver +from mantidqt.utils.observer_pattern import GenericObservable, GenericObserver, GenericObserverWithArgPassing from Muon.GUI.Common.muon_pair import MuonPair from Muon.GUI.Common.utilities.run_string_utils import run_list_to_string from Muon.GUI.FrequencyDomainAnalysis.frequency_context import FREQUENCY_EXTENSIONS - COUNTS_PLOT_TYPE = 'Counts' ASYMMETRY_PLOT_TYPE = 'Asymmetry' FREQ_PLOT_TYPE = "Frequency " -class HomePlotWidgetPresenter(HomeTabSubWidget): +class PlotWidgetPresenter(HomeTabSubWidget): def __init__(self, view, model, context): """ @@ -39,13 +38,16 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): self.plot_type_observer = GenericObserver(self.handle_group_pair_to_plot_changed) self.rebin_options_set_observer = GenericObserver(self.handle_rebin_options_set) self.plot_guess_observer = GenericObserver(self.handle_plot_guess_changed) + self.workspace_deleted_from_ads_observer = GenericObserverWithArgPassing(self.handle_workspace_deleted_from_ads) + self.workspace_replaced_in_ads_observer = GenericObserverWithArgPassing(self.handle_workspace_replaced_in_ads) + self.plot_type_changed_notifier = GenericObservable() self._force_redraw = False if self.context._frequency_context: for ext in FREQUENCY_EXTENSIONS.keys(): - self._view.addItem(FREQ_PLOT_TYPE+FREQUENCY_EXTENSIONS[ext]) - self._view.addItem(FREQ_PLOT_TYPE+"All") + self._view.addItem(FREQ_PLOT_TYPE + FREQUENCY_EXTENSIONS[ext]) + self._view.addItem(FREQ_PLOT_TYPE + "All") self.instrument_observer = GenericObserver(self.handle_instrument_changed) self.keep = False @@ -75,6 +77,15 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): self.plot_standard_workspaces() + def handle_workspace_deleted_from_ads(self, workspace): + if workspace in self._model.plotted_workspaces: + self._model.remove_workspace_from_plot_by_name(workspace) + + def handle_workspace_replaced_in_ads(self, workspace): + if not self._view.if_raw(): + if workspace in self._model.plotted_workspaces: + self.plot_standard_workspaces() + def handle_use_raw_workspaces_changed(self): """ Handles the use raw workspaces being changed. If @@ -93,7 +104,6 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): current_group_pair = self.context.group_pair_context[ self.context.group_pair_context.selected] current_plot_type = self._view.get_selected() - if isinstance(current_group_pair, MuonPair) and current_plot_type == COUNTS_PLOT_TYPE: self._view.plot_selector.blockSignals(True) self._view.plot_selector.setCurrentText(ASYMMETRY_PLOT_TYPE) @@ -131,37 +141,48 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): workspace_list = self.get_workspaces_to_plot( self.context.group_pair_context.selected, self._view.if_raw(), self._view.get_selected()) - self._model.plot(workspace_list, self.get_plot_title(), self.get_domain(), self._force_redraw, self.context.window_title) - self._force_redraw = False - self._model.plotted_workspaces_inverse_binning = {workspace: self.context.group_pair_context.get_equivalent_group_pair(workspace) - for workspace in workspace_list - if self.context.group_pair_context.get_equivalent_group_pair(workspace)} + self._model.plot(workspace_list, self.get_plot_title(), self.get_domain(), + self.context.window_title) - if self.context.fitting_context.fit_list: + # if we are redrawing, keep previous fit + if self._force_redraw: self.handle_fit_completed() - + else: + self.context.fitting_context.clear() self.handle_plot_guess_changed() + # reset redraw flag + self._force_redraw = False + + self._model.plotted_workspaces_inverse_binning = { + workspace: self.context.group_pair_context.get_equivalent_group_pair(workspace) + for workspace in workspace_list + if self.context.group_pair_context.get_equivalent_group_pair(workspace)} def handle_fit_completed(self): """ When a new fit is done adds the fit to the plotted workspaces if appropriate :return: """ + if self._model.plot_figure is None: return + label = self._model.plot_figure.gca().get_ylabel() + if self.context.fitting_context.number_of_fits <= 1: for workspace_name in self._model.plotted_fit_workspaces: self._model.remove_workpace_from_plot(workspace_name) + self._model.plot_figure.gca().legend(prop=dict(size=7)) if self.context.fitting_context.fit_list: current_fit = self.context.fitting_context.fit_list[-1] - combined_ws_list = self._model.plotted_workspaces + list(self._model.plotted_workspaces_inverse_binning.values()) + combined_ws_list = self._model.plotted_workspaces + list( + self._model.plotted_workspaces_inverse_binning.values()) list_of_output_workspaces_to_plot = [output for output, input in zip(current_fit.output_workspace_names, current_fit.input_workspaces) if input in combined_ws_list] - list_of_output_workspaces_to_plot = list_of_output_workspaces_to_plot if list_of_output_workspaces_to_plot\ + list_of_output_workspaces_to_plot = list_of_output_workspaces_to_plot if list_of_output_workspaces_to_plot \ else [current_fit.output_workspace_names[-1]] else: list_of_output_workspaces_to_plot = [] @@ -169,6 +190,7 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): for workspace_name in list_of_output_workspaces_to_plot: self._model.add_workspace_to_plot(workspace_name, 1, workspace_name + ': Fit') self._model.add_workspace_to_plot(workspace_name, 2, workspace_name + ': Diff') + self._model.plot_figure.gca().set_ylabel(label) self._model.force_redraw() @@ -203,7 +225,7 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): except AttributeError: return [] - def get_time_workspaces_to_plot(self,current_group_pair, is_raw, plot_type): + def get_time_workspaces_to_plot(self, current_group_pair, is_raw, plot_type): """ :param current_group_pair: The group/pair currently selected :param is_raw: Whether to use raw or rebinned data @@ -215,7 +237,8 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): workspace_list = self.context.group_pair_context[current_group_pair].get_asymmetry_workspace_names( self.context.data_context.current_runs) else: - workspace_list = self.context.group_pair_context[current_group_pair].get_asymmetry_workspace_names_rebinned( + workspace_list = self.context.group_pair_context[ + current_group_pair].get_asymmetry_workspace_names_rebinned( self.context.data_context.current_runs) if plot_type == COUNTS_PLOT_TYPE: @@ -259,10 +282,11 @@ class HomePlotWidgetPresenter(HomeTabSubWidget): for guess in [ws for ws in self._model.plotted_fit_workspaces if '_guess' in ws]: self._model.remove_workpace_from_plot(guess) + # refresh legend + self._model.plot_figure.gca().legend(prop=dict(size=7)) if self.context.fitting_context.plot_guess and self.context.fitting_context.guess_ws is not None: self._model.add_workspace_to_plot(self.context.fitting_context.guess_ws, workspace_index=1, label='Fit Function Guess') - self._model.force_redraw() diff --git a/scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_view.py b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget_view.py similarity index 78% rename from scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_view.py rename to scripts/Muon/GUI/Common/plotting_widget/plotting_widget_view.py index 4e923496c3aedb43c8cd0958d06ffc1729551132..19ea2cbaa8efa85e49778562d8d4363d9c94750d 100644 --- a/scripts/Muon/GUI/Common/home_plot_widget/home_plot_widget_view.py +++ b/scripts/Muon/GUI/Common/plotting_widget/plotting_widget_view.py @@ -8,21 +8,32 @@ from __future__ import (absolute_import, division, print_function) from qtpy import QtWidgets import Muon.GUI.Common.message_box as message_box +from matplotlib.figure import Figure +from mantidqt.plotting.functions import get_plot_fig +from matplotlib.backends.qt_compat import is_pyqt5 +if is_pyqt5(): + from matplotlib.backends.backend_qt5agg import ( + FigureCanvas, NavigationToolbar2QT as NavigationToolbar) +else: + from matplotlib.backends.backend_qt4agg import ( + FigureCanvas, NavigationToolbar2QT as NavigationToolbar) -class HomePlotWidgetView(QtWidgets.QWidget): +class PlotWidgetView(QtWidgets.QWidget): @staticmethod def warning_popup(message): message_box.warning(str(message)) def __init__(self, parent=None): - super(HomePlotWidgetView, self).__init__(parent) + super(PlotWidgetView, self).__init__(parent) self.plot_label = None self.plot_selector = None self.plot_button = None self.horizontal_layout = None self.vertical_layout = None + self.fig = None + self.toolBar = None self.group = None self.widget_layout = None @@ -89,8 +100,21 @@ class HomePlotWidgetView(QtWidgets.QWidget): self.group.setLayout(self.vertical_layout) + # create the figure + self.fig = Figure() + self.fig.canvas = FigureCanvas(self.fig) + self.toolBar = NavigationToolbar(self.fig.canvas, self) + # set size policy + self.toolBar.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + + # Create a set of mantid axis for the figure + self.fig, axes = get_plot_fig(overplot=False, ax_properties=None, axes_num=1, + fig=self.fig) + self.widget_layout = QtWidgets.QVBoxLayout(self) self.widget_layout.addWidget(self.group) + self.vertical_layout.addWidget(self.toolBar) + self.vertical_layout.addWidget(self.fig.canvas) self.setLayout(self.widget_layout) @@ -124,3 +148,9 @@ class HomePlotWidgetView(QtWidgets.QWidget): def addItem(self, plot_type): self.plot_selector.addItem(plot_type) + + def get_fig(self): + return self.fig + + def close(self): + self.fig.canvas.close() diff --git a/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_context.py b/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_context.py index 6907974e9437e4cb56f46f57c8ef0f8c9914d86a..d567608d1c27c05b73322fdcc4adcd33328d79b7 100644 --- a/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_context.py +++ b/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_context.py @@ -42,7 +42,7 @@ class FrequencyContext(object): """ A simple class for identifing the current run and it can return the name, run and instrument. - The current run is the same as the one in MonAnalysis + The current run is the same as the one in MuonAnalysis """ def __init__(self): diff --git a/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py b/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py index 919133e355249978ee6184e6eb5b3549189f89b4..0d5d73047fd6fb5b44f52794dfc305364bd31061 100644 --- a/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py +++ b/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py @@ -31,6 +31,7 @@ from Muon.GUI.MuonAnalysis.load_widget.load_widget import LoadWidget from Muon.GUI.Common.phase_table_widget.phase_table_widget import PhaseTabWidget from Muon.GUI.Common.results_tab_widget.results_tab_widget import ResultsTabWidget from Muon.GUI.Common.fitting_tab_widget.fitting_tab_widget import FittingTabWidget +from Muon.GUI.Common.plotting_widget.plotting_widget import PlottingWidget SUPPORTED_FACILITIES = ["ISIS", "SmuS"] @@ -85,6 +86,18 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow): fitting_context=self.fitting_context, workspace_suffix=' FD', frequency_context=self.frequency_context) + # create the dockable widget + self.dockable_plot_widget_window = QtWidgets.QDockWidget() + self.dockable_plot_widget_window.setMinimumWidth(550) + self.dockable_plot_widget_window.setFeatures( + QtWidgets.QDockWidget.DockWidgetFloatable | QtWidgets.QDockWidget.DockWidgetMovable) + self.dockable_plot_widget_window.setAllowedAreas(QtCore.Qt.RightDockWidgetArea | QtCore.Qt.LeftDockWidgetArea) + # create the widget to be stored in the dock + self.dockable_plot_widget = PlottingWidget(self.context) + # add dock widget to main muon analysis window + self.dockable_plot_widget_window.setWidget(self.dockable_plot_widget.view) + self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dockable_plot_widget_window) + # construct all the widgets. self.load_widget = LoadWidget(self.loaded_data, self.context, self) self.grouping_tab_widget = GroupingTabWidget(self.context) @@ -143,7 +156,7 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow): self.transform.new_data_observer( self.fitting_tab.fitting_tab_presenter.input_workspace_observer) self.transform.new_data_observer( - self.home_tab.plot_widget.input_workspace_observer) + self.dockable_plot_widget.presenter.input_workspace_observer) self.context.data_context.message_notifier.add_subscriber(self.grouping_tab_widget.group_tab_presenter.message_observer) @@ -178,7 +191,7 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow): self.phase_tab.phase_table_presenter.run_change_observer) self.grouping_tab_widget.group_tab_presenter.calculation_finished_notifier.add_subscriber( - self.home_tab.plot_widget.input_workspace_observer) + self.dockable_plot_widget.presenter.input_workspace_observer) def setup_gui_variable_observers(self): self.context.gui_context.gui_variables_notifier.add_subscriber( @@ -194,11 +207,11 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow): self.home_tab.group_widget.selected_group_pair_changed_notifier.add_subscriber( self.fitting_tab.fitting_tab_presenter.selected_group_pair_observer) - self.home_tab.plot_widget.plot_type_changed_notifier.add_subscriber( + self.dockable_plot_widget.presenter.plot_type_changed_notifier.add_subscriber( self.fitting_tab.fitting_tab_presenter.selected_plot_type_observer) self.home_tab.group_widget.selected_group_pair_changed_notifier.add_subscriber( - self.home_tab.plot_widget.group_pair_observer) + self.dockable_plot_widget.presenter.group_pair_observer) def setup_alpha_recalculated_observers(self): self.home_tab.group_widget.pairAlphaNotifier.add_subscriber( @@ -275,10 +288,10 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow): self.fitting_tab.fitting_tab_presenter.input_workspace_observer) self.grouping_tab_widget.group_tab_presenter.calculation_finished_notifier.add_subscriber( - self.home_tab.plot_widget.input_workspace_observer) + self.dockable_plot_widget.presenter.input_workspace_observer) self.grouping_tab_widget.group_tab_presenter.calculation_finished_notifier.add_subscriber( - self.home_tab.plot_widget.rebin_options_set_observer) + self.dockable_plot_widget.presenter.rebin_options_set_observer) def setup_phase_quad_changed_notifer(self): self.phase_tab.phase_table_presenter.phase_quad_calculation_complete_nofifier.add_subscriber( @@ -294,9 +307,9 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow): self.results_tab.results_tab_presenter.new_fit_performed_observer) self.fitting_context.new_fit_notifier.add_subscriber( - self.home_tab.plot_widget.fit_observer) + self.dockable_plot_widget.presenter.fit_observer) - self.fitting_context.plot_guess_notifier.add_subscriber(self.home_tab.plot_widget.plot_guess_observer) + self.fitting_context.plot_guess_notifier.add_subscriber(self.dockable_plot_widget.presenter.plot_guess_observer) def closeEvent(self, event): self.tabs.closeEvent(event) diff --git a/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py b/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py index c61c490cf92f7f4a72f2c0524a67f77fcecc88a8..ae3d3626686785b539fc55ece0bbda729c35174a 100644 --- a/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py +++ b/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py @@ -7,6 +7,7 @@ # pylint: disable=invalid-name from __future__ import (absolute_import, division, print_function) +from Muon.GUI.Common.plotting_widget.plotting_widget import PlottingWidget from qtpy import QtWidgets, QtCore from mantid.kernel import ConfigServiceImpl @@ -78,10 +79,22 @@ class MuonAnalysisGui(QtWidgets.QMainWindow): fitting_context=self.fitting_context, workspace_suffix=' MA') - # construct all the widgets. + # create the dockable widget + self.dockable_plot_widget_window = QtWidgets.QDockWidget("Plotting window") + self.dockable_plot_widget_window.setMinimumWidth(550) + self.dockable_plot_widget_window.setFeatures( + QtWidgets.QDockWidget.DockWidgetFloatable | QtWidgets.QDockWidget.DockWidgetMovable) + self.dockable_plot_widget_window.setAllowedAreas(QtCore.Qt.RightDockWidgetArea | QtCore.Qt.LeftDockWidgetArea) + # create the widget to be stored in the dock + self.dockable_plot_widget = PlottingWidget(self.context) + # add dock widget to main muon analysis window + self.dockable_plot_widget_window.setWidget(self.dockable_plot_widget.view) + self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.dockable_plot_widget_window) + + # set up other widgets self.load_widget = LoadWidget(self.loaded_data, self.context, self) - self.grouping_tab_widget = GroupingTabWidget(self.context) self.home_tab = HomeTabWidget(self.context, self) + self.grouping_tab_widget = GroupingTabWidget(self.context) self.phase_tab = PhaseTabWidget(self.context, self) self.fitting_tab = FittingTabWidget(self.context, self) self.results_tab = ResultsTabWidget(self.context.fitting_context, self.context, self) @@ -167,7 +180,7 @@ class MuonAnalysisGui(QtWidgets.QMainWindow): self.fitting_tab.fitting_tab_presenter.selected_group_pair_observer) self.home_tab.group_widget.selected_group_pair_changed_notifier.add_subscriber( - self.home_tab.plot_widget.group_pair_observer) + self.dockable_plot_widget.presenter.group_pair_observer) def setup_alpha_recalculated_observers(self): self.home_tab.group_widget.pairAlphaNotifier.add_subscriber( @@ -194,7 +207,7 @@ class MuonAnalysisGui(QtWidgets.QMainWindow): self.phase_tab.phase_table_presenter.instrument_changed_observer) self.context.data_context.instrumentNotifier.add_subscriber( - self.home_tab.plot_widget.instrument_observer) + self.dockable_plot_widget.presenter.instrument_observer) def setup_group_calculation_enable_notifer(self): self.grouping_tab_widget.group_tab_presenter.enable_editing_notifier.add_subscriber( @@ -241,10 +254,10 @@ class MuonAnalysisGui(QtWidgets.QMainWindow): self.fitting_tab.fitting_tab_presenter.input_workspace_observer) self.grouping_tab_widget.group_tab_presenter.calculation_finished_notifier.add_subscriber( - self.home_tab.plot_widget.input_workspace_observer) + self.dockable_plot_widget.presenter.input_workspace_observer) self.grouping_tab_widget.group_tab_presenter.calculation_finished_notifier.add_subscriber( - self.home_tab.plot_widget.rebin_options_set_observer) + self.dockable_plot_widget.presenter.rebin_options_set_observer) def setup_phase_quad_changed_notifer(self): pass @@ -257,11 +270,14 @@ class MuonAnalysisGui(QtWidgets.QMainWindow): self.fitting_context.new_fit_notifier.add_subscriber( self.results_tab.results_tab_presenter.new_fit_performed_observer) - self.fitting_context.new_fit_notifier.add_subscriber(self.home_tab.plot_widget.fit_observer) + self.fitting_context.new_fit_notifier.add_subscriber(self.dockable_plot_widget.presenter.fit_observer) - self.fitting_context.plot_guess_notifier.add_subscriber(self.home_tab.plot_widget.plot_guess_observer) + self.fitting_context.plot_guess_notifier.add_subscriber( + self.dockable_plot_widget.presenter.plot_guess_observer) def closeEvent(self, event): self.tabs.closeEvent(event) + self.dockable_plot_widget_window.widget().close() + self.removeDockWidget(self.dockable_plot_widget_window) self.context.ads_observer = None super(MuonAnalysisGui, self).closeEvent(event) diff --git a/scripts/test/Muon/CMakeLists.txt b/scripts/test/Muon/CMakeLists.txt index d2d5d6fd6c50eb1081dacd1211916c153dc51115..79b54d47c9febb77df187dc2eecbcfcf2d1308be 100644 --- a/scripts/test/Muon/CMakeLists.txt +++ b/scripts/test/Muon/CMakeLists.txt @@ -13,9 +13,9 @@ set ( TEST_PY_FILES fitting_context_test.py home_grouping_widget_test.py home_instrument_widget_test.py - home_plot_widget_test.py - home_plot_widget_freq_test.py - home_plot_widget_model_test.py + plotting_widget_test.py + plotting_widget_freq_test.py + plotting_widget_model_test.py home_runinfo_presenter_test.py list_selector/list_selector_view_test.py list_selector/list_selector_presenter_test.py @@ -91,8 +91,8 @@ set ( TEST_PY_FILES_QT4 fitting_context_test.py home_grouping_widget_test.py home_instrument_widget_test.py - home_plot_widget_test.py - home_plot_widget_freq_test.py + plotting_widget_test.py + plotting_widget_freq_test.py home_runinfo_presenter_test.py list_selector/list_selector_view_test.py list_selector/list_selector_presenter_test.py diff --git a/scripts/test/Muon/home_plot_widget_model_test.py b/scripts/test/Muon/home_plot_widget_model_test.py deleted file mode 100644 index 5378050a63e2e8b8b5dd74f1b42cc64a2b1517ea..0000000000000000000000000000000000000000 --- a/scripts/test/Muon/home_plot_widget_model_test.py +++ /dev/null @@ -1,78 +0,0 @@ -# Mantid Repository : https://github.com/mantidproject/mantid -# -# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, -# NScD Oak Ridge National Laboratory, European Spallation Source -# & Institut Laue - Langevin -# SPDX - License - Identifier: GPL - 3.0 + -import unittest - -from mantid.py3compat import mock - -from Muon.GUI.Common.home_plot_widget.home_plot_widget_model import HomePlotWidgetModel -from mantid.simpleapi import CreateWorkspace - - -class HomeTabPlotModelTest(unittest.TestCase): - def setUp(self): - self.model = HomePlotWidgetModel() - - def create_workspace(self, name): - x_range = range(1, 100) - y_range = [x * x for x in x_range] - return CreateWorkspace(DataX=x_range, DataY=y_range, OutputWorkspace=name) - - @mock.patch('Muon.GUI.Common.home_plot_widget.home_plot_widget_model.plot') - def test_plot_creates_new_plot_window_and_plots_workspace_list(self, mock_plot): - - workspace_list = ['MUSR62260; Group; bottom; Asymmetry; MA', 'MUSR62261; Group; bottom; Asymmetry; MA'] - workspace_object_list = [self.create_workspace(workspace) for workspace in workspace_list] - subplot_title = 'MUSR62260 bottom' - - self.model.plot(workspace_list, subplot_title, 'Time', False, 'Muon Analysis') - - mock_plot.assert_called_once_with(mock.ANY, wksp_indices=[0], window_title=subplot_title, errors=True, - plot_kwargs={'distribution': True, 'autoscale_on_update': False}) - self.assertEqual(str(mock_plot.call_args[0][0][0]), str(workspace_object_list[0])) - self.assertEqual(str(mock_plot.call_args[0][0][1]), str(workspace_object_list[1])) - - - def test_plot_logic_no_plot(self): - mock_plot = mock.MagicMock() - self.model.plot_figure = mock_plot - workspace_list = [] - subplot_title = 'MUSR62260 bottom' - - self.assertEquals(self.model.plot(workspace_list, subplot_title, 'Time', False, 'Muon Analysis'), mock_plot) - self.assertEquals(self.model.plotted_workspaces, []) - self.assertEquals(self.model.plotted_workspaces_inverse_binning,{}) - self.assertEquals(self.model.plotted_fit_workspaces, []) - - @mock.patch('Muon.GUI.Common.home_plot_widget.home_plot_widget_model.plot') - def test_plot_logic_force_new(self,mock_plot): - - workspace_list = ['MUSR62260; Group; bottom; Asymmetry; MA', 'MUSR62261; Group; bottom; Asymmetry; MA'] - workspace_object_list = [self.create_workspace(workspace) for workspace in workspace_list] - subplot_title = 'MUSR62260 bottom' - self.model.set_x_lim = mock.Mock() - self.model.plot(workspace_list, subplot_title, 'Time', False, 'Muon Analysis') - - - mock_plot.assert_called_once_with(mock.ANY, wksp_indices=[0], window_title=subplot_title, errors=True, - plot_kwargs={'distribution': True, 'autoscale_on_update': False}) - - self.assertEquals(self.model.plot_figure.clear.call_count, 0) - self.assertEquals(self.model.set_x_lim.call_count, 1) - workspace_list = ['MUSR62260; Group; top; Asymmetry; MA', 'MUSR62261; Group; top; Asymmetry; MA'] - workspace_object_list = [self.create_workspace(workspace) for workspace in workspace_list] - subplot_title = 'MUSR62260 top' - - self.model.plot(workspace_list, subplot_title, 'Time', True, 'Muon Analysis') - self.assertEquals(self.model.plot_figure.clear.call_count, 1) - self.assertEquals(self.model.set_x_lim.call_count, 2) - - self.assertEquals(mock_plot.call_count,2) - mock_plot.assert_any_call(mock.ANY, wksp_indices=[0], fig= self.model.plot_figure, window_title = subplot_title, plot_kwargs={'distribution':True, 'autoscale_on_update': False}, errors=True) - - -if __name__ == '__main__': - unittest.main(buffer=False, verbosity=2) diff --git a/scripts/test/Muon/home_plot_widget_freq_test.py b/scripts/test/Muon/plotting_widget_freq_test.py similarity index 93% rename from scripts/test/Muon/home_plot_widget_freq_test.py rename to scripts/test/Muon/plotting_widget_freq_test.py index d742c6d9b18519a55f17766a8cd65c79f86e4b95..ec3a62ad76ff4a12d9245457f51779313b64d5f9 100644 --- a/scripts/test/Muon/home_plot_widget_freq_test.py +++ b/scripts/test/Muon/plotting_widget_freq_test.py @@ -8,7 +8,7 @@ import unittest from mantid.py3compat import mock from mantidqt.utils.qt.testing import start_qapplication -from Muon.GUI.Common.home_plot_widget.home_plot_widget_presenter import HomePlotWidgetPresenter +from Muon.GUI.Common.plotting_widget.plotting_widget_presenter import PlotWidgetPresenter from Muon.GUI.Common.muon_pair import MuonPair from Muon.GUI.Common.muon_group import MuonGroup from Muon.GUI.Common.contexts.fitting_context import FitInformation @@ -16,7 +16,7 @@ from Muon.GUI.Common.test_helpers.context_setup import setup_context @start_qapplication -class HomeTabPlotPresenterFreqTest(unittest.TestCase): +class PlottingWidgetPresenterFreqTest(unittest.TestCase): def setUp(self): self.context = setup_context(True) self.plotting_window_model = mock.MagicMock() @@ -29,7 +29,7 @@ class HomeTabPlotPresenterFreqTest(unittest.TestCase): 'FFT; Re MUSR62260; Group; fwd; Asymmetry; FD; Im MUSR62260; Group; fwd; Asymmetry; FD_mod' 'MUSR62260_raw_data FD; MaxEnt'] - self.presenter = HomePlotWidgetPresenter(self.view, self.model, self.context) + self.presenter = PlotWidgetPresenter(self.view, self.model, self.context) self.presenter.get_plot_title = mock.MagicMock(return_value='MUSR62260-62261 bottom') def test_time_plot_in_FDA(self): @@ -39,7 +39,7 @@ class HomeTabPlotPresenterFreqTest(unittest.TestCase): self.presenter.handle_use_raw_workspaces_changed() self.model.plot.assert_called_once_with(['MUSR62260; Group; bottom; Asymmetry; FD', - 'MUSR62261; Group; bottom; Asymmetry; FD'], 'MUSR62260-62261 bottom', 'Time', False, "Frequency Domain Analysis") + 'MUSR62261; Group; bottom; Asymmetry; FD'], 'MUSR62260-62261 bottom', 'Time', "Frequency Domain Analysis") def test_plot_type_changed(self): diff --git a/scripts/test/Muon/plotting_widget_model_test.py b/scripts/test/Muon/plotting_widget_model_test.py new file mode 100644 index 0000000000000000000000000000000000000000..3f106b6704a555cdaf206cead2c0499170f59652 --- /dev/null +++ b/scripts/test/Muon/plotting_widget_model_test.py @@ -0,0 +1,55 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +import unittest + +from mantid.py3compat import mock + +from Muon.GUI.Common.plotting_widget.plotting_widget_model import PlotWidgetModel +from mantid.simpleapi import CreateWorkspace + + +class PlottingWidgetModelTest(unittest.TestCase): + def setUp(self): + plot_figure = mock.MagicMock() + self.model = PlotWidgetModel(plot_figure) + + def create_workspace(self, name): + x_range = range(1, 100) + y_range = [x * x for x in x_range] + return CreateWorkspace(DataX=x_range, DataY=y_range, OutputWorkspace=name) + + @mock.patch('Muon.GUI.Common.plotting_widget.plotting_widget_model.plot') + def test_plot_plots_workspace_list(self, mock_plot): + workspace_list = ['MUSR62260; Group; bottom; Asymmetry; MA', 'MUSR62261; Group; bottom; Asymmetry; MA'] + workspace_object_list = [self.create_workspace(workspace) for workspace in workspace_list] + subplot_title = 'MUSR62260 bottom' + + self.model.plot(workspace_list, subplot_title, 'Time', 'Muon Analysis') + + mock_plot.assert_called_once_with(mock.ANY, wksp_indices=[0], window_title=subplot_title, errors=True, + overplot=True, + fig=self.model.plot_figure, + plot_kwargs={'distribution': True, 'autoscale_on_update': False}) + + self.assertEqual(str(mock_plot.call_args[0][0][0]), str(workspace_object_list[0])) + self.assertEqual(str(mock_plot.call_args[0][0][1]), str(workspace_object_list[1])) + + def test_plot_logic_no_ws_to_plot(self): + workspace_list = [] + subplot_title = 'MUSR62260 bottom' + self.plotted_workspaces = ['ws1', 'ws2', 'ws3'] + + self.model.plot(workspace_list, subplot_title, 'Time', 'Muon Analysis') + + self.assertEquals(self.model.plotted_workspaces, []) + self.assertEquals(self.model.plotted_workspaces_inverse_binning, {}) + self.assertEquals(self.model.plotted_fit_workspaces, []) + + + +if __name__ == '__main__': + unittest.main(buffer=False, verbosity=2) diff --git a/scripts/test/Muon/home_plot_widget_test.py b/scripts/test/Muon/plotting_widget_test.py similarity index 94% rename from scripts/test/Muon/home_plot_widget_test.py rename to scripts/test/Muon/plotting_widget_test.py index 79738be9db97f2429d42b873bc44f5a3375de854..dcd2b2ab0d76f956cd2a86ba3b5e251304e9e8b5 100644 --- a/scripts/test/Muon/home_plot_widget_test.py +++ b/scripts/test/Muon/plotting_widget_test.py @@ -8,14 +8,14 @@ import unittest from mantid.py3compat import mock from mantidqt.utils.qt.testing import start_qapplication -from Muon.GUI.Common.home_plot_widget.home_plot_widget_presenter import HomePlotWidgetPresenter +from Muon.GUI.Common.plotting_widget.plotting_widget_presenter import PlotWidgetPresenter from Muon.GUI.Common.muon_pair import MuonPair from Muon.GUI.Common.muon_group import MuonGroup from Muon.GUI.Common.contexts.fitting_context import FitInformation @start_qapplication -class HomeTabPlotPresenterTest(unittest.TestCase): +class PlottingWidgetPresenterTest(unittest.TestCase): def setUp(self): self.context = mock.MagicMock() self.context.fitting_context.number_of_fits = 1 @@ -25,7 +25,7 @@ class HomeTabPlotPresenterTest(unittest.TestCase): self.workspace_list = ['MUSR62260; Group; bottom; Asymmetry; MA', 'MUSR62261; Group; bottom; Asymmetry; MA'] - self.presenter = HomePlotWidgetPresenter(self.view, self.model, self.context) + self.presenter = PlotWidgetPresenter(self.view, self.model, self.context) self.presenter.get_plot_title = mock.MagicMock(return_value='MUSR62260-62261 bottom') def test_use_rebin_changed_resets_use_raw_to_true_if_no_rebin_specified(self): @@ -44,7 +44,7 @@ class HomeTabPlotPresenterTest(unittest.TestCase): self.presenter.handle_use_raw_workspaces_changed() self.model.plot.assert_called_once_with(['MUSR62260; Group; bottom; Asymmetry; MA', - 'MUSR62261; Group; bottom; Asymmetry; MA'], 'MUSR62260-62261 bottom', 'Time', False, mock.ANY) + 'MUSR62261; Group; bottom; Asymmetry; MA'], 'MUSR62260-62261 bottom', 'Time', mock.ANY) def test_handle_data_updated_does_nothing_if_workspace_list_has_not_changed(self): self.presenter.get_workspaces_to_plot = mock.MagicMock(return_value=self.workspace_list) @@ -61,7 +61,7 @@ class HomeTabPlotPresenterTest(unittest.TestCase): self.presenter.handle_data_updated() - self.model.plot.assert_called_once_with(self.workspace_list, 'MUSR62260-62261 bottom', 'Time', False, mock.ANY) + self.model.plot.assert_called_once_with(self.workspace_list, 'MUSR62260-62261 bottom', 'Time', mock.ANY) def test_handle_plot_type_changed_displays_a_warning_if_trying_to_plot_counts_on_a_pair(self): self.context.group_pair_context.__getitem__.return_value = MuonPair('long', 'bwd', 'fwd') @@ -79,7 +79,7 @@ class HomeTabPlotPresenterTest(unittest.TestCase): self.presenter.handle_plot_type_changed() - self.model.plot.assert_called_once_with(self.workspace_list, 'MUSR62260-62261 bottom', 'Time', True, mock.ANY) + self.model.plot.assert_called_once_with(self.workspace_list, 'MUSR62260-62261 bottom', 'Time', mock.ANY) def test_handle_group_pair_to_plot_changed_does_nothing_if_group_not_changed(self): self.model.plotted_group = 'bottom' @@ -105,7 +105,7 @@ class HomeTabPlotPresenterTest(unittest.TestCase): self.presenter.handle_group_pair_to_plot_changed() - self.model.plot.assert_called_once_with(self.workspace_list, 'MUSR62260-62261 bottom', 'Time', False, mock.ANY) + self.model.plot.assert_called_once_with(self.workspace_list, 'MUSR62260-62261 bottom', 'Time', mock.ANY) def test_handle_fit_completed_adds_appropriate_fits_to_plot(self): self.model.plotted_workspaces = self.workspace_list