예제 #1
0
 def test_disconnect_called_for_each_registered_handler(self):
     fig_manager = MagicMock()
     canvas = MagicMock()
     fig_manager.canvas = canvas
     interactor = FigureInteraction(fig_manager)
     interactor.disconnect()
     self.assertEqual(interactor.nevents, canvas.mpl_disconnect.call_count)
 def test_disconnect_called_for_each_registered_handler(self):
     fig_manager = MagicMock()
     canvas = MagicMock()
     fig_manager.canvas = canvas
     interactor = FigureInteraction(fig_manager)
     interactor.disconnect()
     self.assertEqual(interactor.nevents, canvas.mpl_disconnect.call_count)
예제 #3
0
class FigureManagerWorkbench(FigureManagerBase, QObject):
    """
    Attributes
    ----------
    canvas : `FigureCanvas`
        The FigureCanvas instance
    num : int or str
        The Figure number
    toolbar : qt.QToolBar
        The qt.QToolBar
    window : qt.QMainWindow
        The qt.QMainWindow

    """
    def __init__(self, canvas, num):
        assert QAppThreadCall.is_qapp_thread(
        ), "FigureManagerWorkbench cannot be created outside of the QApplication thread"
        QObject.__init__(self)

        parent, flags = get_window_config()
        self.window = FigureWindow(canvas, parent=parent, window_flags=flags)
        self.window.activated.connect(self._window_activated)
        self.window.closing.connect(canvas.close_event)
        self.window.closing.connect(self.destroy)
        self.window.visibility_changed.connect(self.fig_visibility_changed)

        self.window.setWindowTitle("Figure %d" % num)
        canvas.figure.set_label("Figure %d" % num)

        FigureManagerBase.__init__(self, canvas, num)
        # Give the keyboard focus to the figure instead of the
        # manager; StrongFocus accepts both tab and click to focus and
        # will enable the canvas to process event w/o clicking.
        # ClickFocus only takes the focus is the window has been
        # clicked
        # on. http://qt-project.org/doc/qt-4.8/qt.html#FocusPolicy-enum or
        # http://doc.qt.digia.com/qt/qt.html#FocusPolicy-enum
        canvas.setFocusPolicy(Qt.StrongFocus)
        canvas.setFocus()

        self.window._destroying = False

        # add text label to status bar
        self.statusbar_label = QLabel()
        self.window.statusBar().addWidget(self.statusbar_label)

        self.plot_options_dialog = None
        self.toolbar = self._get_toolbar(canvas, self.window)
        if self.toolbar is not None:
            self.window.addToolBar(self.toolbar)
            self.toolbar.message.connect(self.statusbar_label.setText)
            self.toolbar.sig_grid_toggle_triggered.connect(self.grid_toggle)
            self.toolbar.sig_toggle_fit_triggered.connect(self.fit_toggle)
            self.toolbar.sig_toggle_superplot_triggered.connect(
                self.superplot_toggle)
            self.toolbar.sig_copy_to_clipboard_triggered.connect(
                self.copy_to_clipboard)
            self.toolbar.sig_plot_options_triggered.connect(
                self.launch_plot_options)
            self.toolbar.sig_plot_help_triggered.connect(self.launch_plot_help)
            self.toolbar.sig_generate_plot_script_clipboard_triggered.connect(
                self.generate_plot_script_clipboard)
            self.toolbar.sig_generate_plot_script_file_triggered.connect(
                self.generate_plot_script_file)
            self.toolbar.sig_home_clicked.connect(
                self.set_figure_zoom_to_display_all)
            self.toolbar.sig_waterfall_reverse_order_triggered.connect(
                self.waterfall_reverse_line_order)
            self.toolbar.sig_waterfall_offset_amount_triggered.connect(
                self.launch_waterfall_offset_options)
            self.toolbar.sig_waterfall_fill_area_triggered.connect(
                self.launch_waterfall_fill_area_options)
            self.toolbar.sig_waterfall_conversion.connect(
                self.update_toolbar_waterfall_plot)
            self.toolbar.sig_change_line_collection_colour_triggered.connect(
                self.change_line_collection_colour)
            self.toolbar.setFloatable(False)
            tbs_height = self.toolbar.sizeHint().height()
        else:
            tbs_height = 0

        # resize the main window so it will display the canvas with the
        # requested size:
        cs = canvas.sizeHint()
        sbs = self.window.statusBar().sizeHint()
        self._status_and_tool_height = tbs_height + sbs.height()
        height = cs.height() + self._status_and_tool_height
        self.window.resize(cs.width(), height)

        self.fit_browser = FitPropertyBrowser(
            canvas, ToolbarStateManager(self.toolbar))
        self.fit_browser.closing.connect(self.handle_fit_browser_close)
        self.window.setCentralWidget(canvas)
        self.window.addDockWidget(Qt.LeftDockWidgetArea, self.fit_browser)

        self.superplot = None

        # Need this line to stop the bug where the dock window snaps back to its original size after resizing.
        # 0 argument is arbitrary and has no effect on fit widget size
        # This is a qt bug reported at (https://bugreports.qt.io/browse/QTBUG-65592)
        if QT_VERSION >= LooseVersion("5.6"):
            self.window.resizeDocks([self.fit_browser], [1], Qt.Horizontal)
        self.fit_browser.hide()

        if matplotlib.is_interactive():
            self.window.show()
            canvas.draw_idle()

        def notify_axes_change(fig):
            # This will be called whenever the current axes is changed
            if self.toolbar is not None:
                self.toolbar.update()

        canvas.figure.add_axobserver(notify_axes_change)

        # Register canvas observers
        self._fig_interaction = FigureInteraction(self)
        self._ads_observer = FigureManagerADSObserver(self)

        self.window.raise_()

    def full_screen_toggle(self):
        if self.window.isFullScreen():
            self.window.showNormal()
        else:
            self.window.showFullScreen()

    def _window_activated(self):
        GlobalFigureManager.set_active(self)

    def _get_toolbar(self, canvas, parent):
        return WorkbenchNavigationToolbar(canvas, parent, False)

    def resize(self, width, height):
        """Set the canvas size in pixels"""
        self.window.resize(width, height + self._status_and_tool_height)

    def show(self):
        self.window.show()
        self.window.activateWindow()
        self.window.raise_()
        if self.window.windowState() & Qt.WindowMinimized:
            # windowState() stores a combination of window state enums
            # and multiple window states can be valid. On Windows
            # a window can be both minimized and maximized at the
            # same time, so we make a check here. For more info see:
            # http://doc.qt.io/qt-5/qt.html#WindowState-enum
            if self.window.windowState() & Qt.WindowMaximized:
                self.window.setWindowState(Qt.WindowMaximized)
            else:
                self.window.setWindowState(Qt.WindowNoState)

        # Hack to ensure the canvas is up to date
        self.canvas.draw_idle()

        if self.toolbar:
            self.toolbar.set_buttons_visibility(self.canvas.figure)

    def destroy(self, *args):
        # check for qApp first, as PySide deletes it in its atexit handler
        if QApplication.instance() is None:
            return

        if self.window._destroying:
            return
        self.window._destroying = True

        if self.toolbar:
            self.toolbar.destroy()

        self._ads_observer.observeAll(False)
        self._ads_observer = None
        # disconnect window events before calling GlobalFigureManager.destroy. window.close is not guaranteed to
        # delete the object and do this for us. On macOS it was observed that closing the figure window
        # would produce an extraneous activated event that would add a new figure to the plots list
        # right after deleted the old one.
        self.window.disconnect()
        self._fig_interaction.disconnect()
        self.window.close()
        if self.superplot:
            self.superplot.close()

        try:
            GlobalFigureManager.destroy(self.num)
        except AttributeError:
            pass
            # It seems that when the python session is killed,
            # GlobalFigureManager can get destroyed before the GlobalFigureManager.destroy
            # line is run, leading to a useless AttributeError.

    def launch_plot_options(self):
        self.plot_options_dialog = PlotConfigDialogPresenter(
            self.canvas.figure, parent=self.window)

    def launch_plot_options_on_curves_tab(self, axes, curve):
        self.plot_options_dialog = PlotConfigDialogPresenter(
            self.canvas.figure, parent=self.window)
        self.plot_options_dialog.configure_curves_tab(axes, curve)

    def launch_plot_help(self):
        PlotHelpPages.show_help_page_for_figure(self.canvas.figure)

    def copy_to_clipboard(self):
        """Copy the current figure image to clipboard"""
        # store the image in a buffer using savefig(), this has the
        # advantage of applying all the default savefig parameters
        # such as background color; those would be ignored if you simply
        # grab the canvas using Qt
        buf = io.BytesIO()
        self.canvas.figure.savefig(buf)
        QApplication.clipboard().setImage(QImage.fromData(buf.getvalue()))
        buf.close()

    def grid_toggle(self, on):
        """Toggle grid lines on/off"""
        canvas = self.canvas
        axes = canvas.figure.get_axes()
        for ax in axes:
            if type(ax) == Axes:
                # Colorbar
                continue
            elif isinstance(ax, Axes3D):
                # The grid toggle function for 3D plots doesn't let you choose between major and minor lines.
                ax.grid(on)
            else:
                which = 'both' if hasattr(
                    ax, 'show_minor_gridlines'
                ) and ax.show_minor_gridlines else 'major'
                ax.grid(on, which=which)
        canvas.draw_idle()

    def fit_toggle(self):
        """Toggle fit browser and tool on/off"""
        if self.fit_browser.isVisible():
            self.fit_browser.hide()
            self.toolbar._actions["toggle_fit"].setChecked(False)
        else:
            if self.toolbar._actions["toggle_superplot"].isChecked():
                self._superplot_hide()
            self.fit_browser.show()

    def _superplot_show(self):
        """Show the superplot"""
        self.superplot = Superplot(self.canvas, self.window)
        if not self.superplot.is_valid():
            logger.warning("Superplot cannot be opened on data not linked "
                           "to a workspace.")
            self.superplot = None
            self.toolbar._actions["toggle_superplot"].setChecked(False)
        else:
            self.superplot.show()
            self.toolbar._actions["toggle_superplot"].setChecked(True)

    def _superplot_hide(self):
        """Hide the superplot"""
        if self.superplot is None:
            return
        self.superplot.close()
        self.superplot = None
        self.toolbar._actions["toggle_superplot"].setChecked(False)

    def superplot_toggle(self):
        """Toggle superplot dockwidgets on/off"""
        if self.superplot:
            self._superplot_hide()
        else:
            if self.toolbar._actions["toggle_fit"].isChecked():
                self.fit_toggle()
            self._superplot_show()

    def handle_fit_browser_close(self):
        """
        Respond to a signal that user closed self.fit_browser by
        clicking the [x] button.
        """
        self.toolbar.trigger_fit_toggle_action()

    def hold(self):
        """ Mark this figure as held"""
        self.toolbar.hold()

    def get_window_title(self):
        return self.window.windowTitle()

    def set_window_title(self, title):
        self.window.setWindowTitle(title)
        # We need to add a call to the figure manager here to call
        # notify methods when a figure is renamed, to update our
        # plot list.
        GlobalFigureManager.figure_title_changed(self.num)

        # For the workbench we also keep the label in sync, this is
        # to allow getting a handle as plt.figure('Figure Name')
        self.canvas.figure.set_label(title)

    def fig_visibility_changed(self):
        """
        Make a notification in the global figure manager that
        plot visibility was changed. This method is added to this
        class so that it can be wrapped in a QAppThreadCall.
        """
        GlobalFigureManager.figure_visibility_changed(self.num)

    def generate_plot_script_clipboard(self):
        script = generate_script(self.canvas.figure, exclude_headers=True)
        QApplication.clipboard().setText(script)
        logger.notice("Plotting script copied to clipboard.")

    def generate_plot_script_file(self):
        script = generate_script(self.canvas.figure)
        filepath = open_a_file_dialog(parent=self.canvas,
                                      default_suffix=".py",
                                      file_filter="Python Files (*.py)",
                                      accept_mode=QFileDialog.AcceptSave,
                                      file_mode=QFileDialog.AnyFile)
        if filepath:
            try:
                with open(filepath, 'w') as f:
                    f.write(script)
            except IOError as io_error:
                logger.error("Could not write file: {}\n{}"
                             "".format(filepath, io_error))

    def set_figure_zoom_to_display_all(self):
        axes = self.canvas.figure.get_axes()
        if axes:
            for ax in axes:
                # We check for axes type below as a pseudo check for an axes being
                # a colorbar. this is based on the same check in
                # FigureManagerADSObserver.deleteHandle.
                if type(ax) is not Axes:
                    if ax.lines:  # Relim causes issues with colour plots, which have no lines.
                        ax.relim()
                    elif isinstance(ax, Axes3D):
                        if hasattr(ax, 'original_data_surface'):
                            ax.collections[0]._vec = copy.deepcopy(
                                ax.original_data_surface)
                        elif hasattr(ax, 'original_data_wireframe'):
                            ax.collections[0].set_segments(
                                copy.deepcopy(ax.original_data_wireframe))
                        else:
                            ax.view_init()
                    elif ax.images:
                        axesfunctions.update_colorplot_datalimits(
                            ax, ax.images)
                        continue

                    ax.autoscale()

            self.canvas.draw()

    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 launch_waterfall_offset_options(self):
        WaterfallPlotOffsetDialogPresenter(self.canvas.figure,
                                           parent=self.window)

    def launch_waterfall_fill_area_options(self):
        WaterfallPlotFillAreaDialogPresenter(self.canvas.figure,
                                             parent=self.window)

    def update_toolbar_waterfall_plot(self, is_waterfall):
        self.toolbar.set_waterfall_options_enabled(is_waterfall)
        self.toolbar.set_fit_enabled(not is_waterfall)
        self.toolbar.set_generate_plot_script_enabled(not is_waterfall)

    def change_line_collection_colour(self, colour):
        for col in self.canvas.figure.get_axes()[0].collections:
            if isinstance(col, LineCollection):
                col.set_color(colour.name())

        self.canvas.draw()
예제 #4
0
class FigureManagerWorkbench(FigureManagerBase, QObject):
    """
    Attributes
    ----------
    canvas : `FigureCanvas`
        The FigureCanvas instance
    num : int or str
        The Figure number
    toolbar : qt.QToolBar
        The qt.QToolBar
    window : qt.QMainWindow
        The qt.QMainWindow

    """
    def __init__(self, canvas, num):
        QObject.__init__(self)
        FigureManagerBase.__init__(self, canvas, num)
        # Patch show/destroy to be thread aware
        self._destroy_orig = self.destroy
        self.destroy = QAppThreadCall(self._destroy_orig)
        self._show_orig = self.show
        self.show = QAppThreadCall(self._show_orig)
        self._window_activated_orig = self._window_activated
        self._window_activated = QAppThreadCall(self._window_activated_orig)
        self.set_window_title_orig = self.set_window_title
        self.set_window_title = QAppThreadCall(self.set_window_title_orig)
        self.fig_visibility_changed_orig = self.fig_visibility_changed
        self.fig_visibility_changed = QAppThreadCall(
            self.fig_visibility_changed_orig)

        self.window = FigureWindow(canvas)
        self.window.activated.connect(self._window_activated)
        self.window.closing.connect(canvas.close_event)
        self.window.closing.connect(self.destroy)
        self.window.visibility_changed.connect(self.fig_visibility_changed)

        self.window.setWindowTitle("Figure %d" % num)
        canvas.figure.set_label("Figure %d" % num)

        # Give the keyboard focus to the figure instead of the
        # manager; StrongFocus accepts both tab and click to focus and
        # will enable the canvas to process event w/o clicking.
        # ClickFocus only takes the focus is the window has been
        # clicked
        # on. http://qt-project.org/doc/qt-4.8/qt.html#FocusPolicy-enum or
        # http://doc.qt.digia.com/qt/qt.html#FocusPolicy-enum
        canvas.setFocusPolicy(Qt.StrongFocus)
        canvas.setFocus()

        self.window._destroying = False

        # add text label to status bar
        self.statusbar_label = QLabel()
        self.window.statusBar().addWidget(self.statusbar_label)

        self.plot_options_dialog = None
        self.toolbar = self._get_toolbar(canvas, self.window)
        if self.toolbar is not None:
            self.window.addToolBar(self.toolbar)
            self.toolbar.message.connect(self.statusbar_label.setText)
            self.toolbar.sig_grid_toggle_triggered.connect(self.grid_toggle)
            self.toolbar.sig_toggle_fit_triggered.connect(self.fit_toggle)
            self.toolbar.sig_plot_options_triggered.connect(
                self.launch_plot_options)
            self.toolbar.sig_generate_plot_script_clipboard_triggered.connect(
                self.generate_plot_script_clipboard)
            self.toolbar.sig_generate_plot_script_file_triggered.connect(
                self.generate_plot_script_file)
            self.toolbar.sig_home_clicked.connect(
                self.set_figure_zoom_to_display_all)
            self.toolbar.sig_waterfall_reverse_order_triggered.connect(
                self.waterfall_reverse_line_order)
            self.toolbar.sig_waterfall_offset_amount_triggered.connect(
                self.launch_waterfall_offset_options)
            self.toolbar.sig_waterfall_fill_area_triggered.connect(
                self.launch_waterfall_fill_area_options)
            self.toolbar.sig_waterfall_conversion.connect(
                self.update_toolbar_waterfall_plot)
            self.toolbar.setFloatable(False)
            tbs_height = self.toolbar.sizeHint().height()
        else:
            tbs_height = 0

        # resize the main window so it will display the canvas with the
        # requested size:
        cs = canvas.sizeHint()
        sbs = self.window.statusBar().sizeHint()
        self._status_and_tool_height = tbs_height + sbs.height()
        height = cs.height() + self._status_and_tool_height
        self.window.resize(cs.width(), height)

        self.fit_browser = FitPropertyBrowser(
            canvas, ToolbarStateManager(self.toolbar))
        self.fit_browser.closing.connect(self.handle_fit_browser_close)
        self.window.setCentralWidget(canvas)
        self.window.addDockWidget(Qt.LeftDockWidgetArea, self.fit_browser)
        self.fit_browser.hide()

        if matplotlib.is_interactive():
            self.window.show()
            canvas.draw_idle()

        def notify_axes_change(fig):
            # This will be called whenever the current axes is changed
            if self.toolbar is not None:
                self.toolbar.update()

        canvas.figure.add_axobserver(notify_axes_change)

        # Register canvas observers
        self._fig_interaction = FigureInteraction(self)
        self._ads_observer = FigureManagerADSObserver(self)

        self.window.raise_()

    def full_screen_toggle(self):
        if self.window.isFullScreen():
            self.window.showNormal()
        else:
            self.window.showFullScreen()

    def _window_activated(self):
        Gcf.set_active(self)

    def _get_toolbar(self, canvas, parent):
        return WorkbenchNavigationToolbar(canvas, parent, False)

    def resize(self, width, height):
        """Set the canvas size in pixels"""
        self.window.resize(width, height + self._status_and_tool_height)

    def show(self):
        self.window.show()
        self.window.activateWindow()
        self.window.raise_()
        if self.window.windowState() & Qt.WindowMinimized:
            # windowState() stores a combination of window state enums
            # and multiple window states can be valid. On Windows
            # a window can be both minimized and maximized at the
            # same time, so we make a check here. For more info see:
            # http://doc.qt.io/qt-5/qt.html#WindowState-enum
            if self.window.windowState() & Qt.WindowMaximized:
                self.window.setWindowState(Qt.WindowMaximized)
            else:
                self.window.setWindowState(Qt.WindowNoState)

        # Hack to ensure the canvas is up to date
        self.canvas.draw_idle()
        if figure_type(self.canvas.figure) not in [FigureType.Line, FigureType.Errorbar] \
                or self.toolbar is not None and len(self.canvas.figure.get_axes()) > 1:
            self._set_fit_enabled(False)

        # For plot-to-script button to show, we must have a MantidAxes with lines in it
        # Plot-to-script currently doesn't work with waterfall plots so the button is hidden for that plot type.
        if not any((isinstance(ax, MantidAxes) and curve_in_ax(ax))
                   for ax in self.canvas.figure.get_axes()
                   ) or self.canvas.figure.get_axes()[0].is_waterfall():
            self.toolbar.set_generate_plot_script_enabled(False)

        # Only show options specific to waterfall plots if the axes is a MantidAxes and is a waterfall plot.
        if not isinstance(self.canvas.figure.get_axes()[0],
                          MantidAxes) or not self.canvas.figure.get_axes(
                          )[0].is_waterfall():
            self.toolbar.set_waterfall_options_enabled(False)

    def destroy(self, *args):
        # check for qApp first, as PySide deletes it in its atexit handler
        if QApplication.instance() is None:
            return

        if self.window._destroying:
            return
        self.window._destroying = True

        if self.toolbar:
            self.toolbar.destroy()
        self._ads_observer.observeAll(False)
        del self._ads_observer
        self._fig_interaction.disconnect()
        self.window.close()

        try:
            Gcf.destroy(self.num)
        except AttributeError:
            pass
            # It seems that when the python session is killed,
            # Gcf can get destroyed before the Gcf.destroy
            # line is run, leading to a useless AttributeError.

    def launch_plot_options(self):
        self.plot_options_dialog = PlotConfigDialogPresenter(
            self.canvas.figure, parent=self.window)

    def grid_toggle(self):
        """Toggle grid lines on/off"""
        canvas = self.canvas
        axes = canvas.figure.get_axes()
        for ax in axes:
            ax.grid()
        canvas.draw_idle()

    def fit_toggle(self):
        """Toggle fit browser and tool on/off"""
        if self.fit_browser.isVisible():
            self.fit_browser.hide()
        else:
            self.fit_browser.show()

    def handle_fit_browser_close(self):
        """
        Respond to a signal that user closed self.fit_browser by
        clicking the [x] button.
        """
        self.toolbar.trigger_fit_toggle_action()

    def hold(self):
        """ Mark this figure as held"""
        self.toolbar.hold()

    def get_window_title(self):
        return text_type(self.window.windowTitle())

    def set_window_title(self, title):
        self.window.setWindowTitle(title)
        # We need to add a call to the figure manager here to call
        # notify methods when a figure is renamed, to update our
        # plot list.
        Gcf.figure_title_changed(self.num)

        # For the workbench we also keep the label in sync, this is
        # to allow getting a handle as plt.figure('Figure Name')
        self.canvas.figure.set_label(title)

    def fig_visibility_changed(self):
        """
        Make a notification in the global figure manager that
        plot visibility was changed. This method is added to this
        class so that it can be wrapped in a QAppThreadCall.
        """
        Gcf.figure_visibility_changed(self.num)

    def _set_fit_enabled(self, on):
        action = self.toolbar._actions['toggle_fit']
        action.setEnabled(on)
        action.setVisible(on)

    def generate_plot_script_clipboard(self):
        script = generate_script(self.canvas.figure, exclude_headers=True)
        QApplication.clipboard().setText(script)
        logger.notice("Plotting script copied to clipboard.")

    def generate_plot_script_file(self):
        script = generate_script(self.canvas.figure)
        filepath = open_a_file_dialog(parent=self.canvas,
                                      default_suffix=".py",
                                      file_filter="Python Files (*.py)",
                                      accept_mode=QFileDialog.AcceptSave,
                                      file_mode=QFileDialog.AnyFile)
        if filepath:
            try:
                with open(filepath, 'w') as f:
                    f.write(script)
            except IOError as io_error:
                logger.error("Could not write file: {}\n{}"
                             "".format(filepath, io_error))

    def set_figure_zoom_to_display_all(self):
        axes = self.canvas.figure.get_axes()
        if axes:
            for ax in axes:
                # We check for axes type below as a pseudo check for an axes being
                # a colorbar. this is based on the same check in
                # FigureManagerADSObserver.deleteHandle.
                if type(ax) is not Axes:
                    if ax.lines:  # Relim causes issues with colour plots, which have no lines.
                        ax.relim()
                    ax.autoscale()

            self.canvas.draw()

    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 launch_waterfall_offset_options(self):
        WaterfallPlotOffsetDialogPresenter(self.canvas.figure,
                                           parent=self.window)

    def launch_waterfall_fill_area_options(self):
        WaterfallPlotFillAreaDialogPresenter(self.canvas.figure,
                                             parent=self.window)

    def update_toolbar_waterfall_plot(self, is_waterfall):
        self.toolbar.set_waterfall_options_enabled(is_waterfall)
        self.toolbar.set_generate_plot_script_enabled(not is_waterfall)