예제 #1
0
    def __init__(self):
        super(PlotView, self).__init__()
        self.plots = OrderedDict({})
        self.errors_list = set()
        self.plot_storage = {}  # stores lines and info to create lines
        self.current_grid = None
        self.gridspecs = {
            1: gridspec.GridSpec(1, 1),
            2: gridspec.GridSpec(1, 2),
            3: gridspec.GridSpec(3, 1),
            4: gridspec.GridSpec(2, 2)
        }
        self.figure = Figure()
        self.figure.set_facecolor("none")
        self.canvas = FigureCanvas(self.figure)

        self.plot_selector = QtWidgets.QComboBox()
        self._update_plot_selector()
        self.plot_selector.currentIndexChanged[str].connect(self._set_bounds)

        button_layout = QtWidgets.QHBoxLayout()
        self.x_axis_changer = AxisChangerPresenter(AxisChangerView("X"))
        self.x_axis_changer.on_upper_bound_changed(self._update_x_axis_upper)
        self.x_axis_changer.on_lower_bound_changed(self._update_x_axis_lower)

        self.y_axis_changer = AxisChangerPresenter(AxisChangerView("Y"))
        self.y_axis_changer.on_upper_bound_changed(self._update_y_axis_upper)
        self.y_axis_changer.on_lower_bound_changed(self._update_y_axis_lower)

        self.errors = QtWidgets.QCheckBox("Errors")
        self.errors.stateChanged.connect(self._errors_changed)

        button_layout.addWidget(self.plot_selector)
        button_layout.addWidget(self.x_axis_changer.view)
        button_layout.addWidget(self.y_axis_changer.view)
        button_layout.addWidget(self.errors)

        grid = QtWidgets.QGridLayout()

        self.toolbar = myToolbar(self.canvas, self)
        self.toolbar.update()

        grid.addWidget(self.toolbar, 0, 0)
        grid.addWidget(self.canvas, 1, 0)
        grid.addLayout(button_layout, 2, 0)
        self.setLayout(grid)
예제 #2
0
    def __init__(self):
        super(PlotView, self).__init__()
        self.plots = OrderedDict({})
        self.errors_list = set()
        self.plot_storage = {}  # stores lines and info to create lines
        self.current_grid = None
        self.gridspecs = {
            1: gridspec.GridSpec(1, 1),
            2: gridspec.GridSpec(1, 2),
            3: gridspec.GridSpec(3, 1),
            4: gridspec.GridSpec(2, 2)
        }
        self.figure = Figure()
        self.figure.set_facecolor("none")
        self.canvas = FigureCanvas(self.figure)

        self.plot_selector = QtWidgets.QComboBox()
        self._update_plot_selector()
        self.plot_selector.currentIndexChanged[str].connect(self._set_bounds)

        button_layout = QtWidgets.QHBoxLayout()
        self.x_axis_changer = AxisChangerPresenter(AxisChangerView("X"))
        self.x_axis_changer.on_upper_bound_changed(self._update_x_axis_upper)
        self.x_axis_changer.on_lower_bound_changed(self._update_x_axis_lower)

        self.y_axis_changer = AxisChangerPresenter(AxisChangerView("Y"))
        self.y_axis_changer.on_upper_bound_changed(self._update_y_axis_upper)
        self.y_axis_changer.on_lower_bound_changed(self._update_y_axis_lower)

        self.errors = QtWidgets.QCheckBox("Errors")
        self.errors.stateChanged.connect(self._errors_changed)

        button_layout.addWidget(self.plot_selector)
        button_layout.addWidget(self.x_axis_changer.view)
        button_layout.addWidget(self.y_axis_changer.view)
        button_layout.addWidget(self.errors)

        grid = QtWidgets.QGridLayout()

        self.toolbar = myToolbar(self.canvas, self)
        self.toolbar.update()

        grid.addWidget(self.toolbar, 0, 0)
        grid.addWidget(self.canvas, 1, 0)
        grid.addLayout(button_layout, 2, 0)
        self.setLayout(grid)
예제 #3
0
 def setUp(self):
     view = mock.create_autospec(AxisChangerView)
     self.acp = AxisChangerPresenter(view)
     self.view = self.acp.view
     self.slot = mock.Mock()
     self.bounds = mock.Mock()
예제 #4
0
class AxisChangerPresenterTest(unittest.TestCase):
    def setUp(self):
        view = mock.create_autospec(AxisChangerView)
        self.acp = AxisChangerPresenter(view)
        self.view = self.acp.view
        self.slot = mock.Mock()
        self.bounds = mock.Mock()

    def test_get_bounds(self):
        self.acp.get_bounds()
        self.assertEquals(self.view.get_bounds.call_count, 1)

    def test_set_bounds(self):
        self.acp.set_bounds(self.bounds)
        self.view.set_bounds.assert_called_with(self.bounds)

    def test_clear_bounds(self):
        self.acp.clear_bounds()
        self.assertEquals(self.view.clear_bounds.call_count, 1)

    def test_on_lower_bound_changed(self):
        self.acp.on_lower_bound_changed(self.slot)
        self.view.on_lower_bound_changed.assert_called_with(self.slot)

    def test_on_upper_bound_changed(self):
        self.acp.on_upper_bound_changed(self.slot)
        self.view.on_upper_bound_changed.assert_called_with(self.slot)

    def test_unreg_on_lower_bound_changed(self):
        self.acp.unreg_on_lower_bound_changed(self.slot)
        self.view.unreg_on_lower_bound_changed.assert_called_with(self.slot)

    def test_unreg_on_upper_bound_changed(self):
        self.acp.unreg_on_upper_bound_changed(self.slot)
        self.view.unreg_on_upper_bound_changed.assert_called_with(self.slot)
예제 #5
0
class PlotView(QtWidgets.QWidget):
    subplotRemovedSignal = QtCore.Signal(object)
    plotCloseSignal = QtCore.Signal()

    def __init__(self):
        super(PlotView, self).__init__()
        self.plots = OrderedDict({})
        self.errors_list = set()
        self.plot_storage = {}  # stores lines and info to create lines
        self.current_grid = None
        self.gridspecs = {
            1: gridspec.GridSpec(1, 1),
            2: gridspec.GridSpec(1, 2),
            3: gridspec.GridSpec(3, 1),
            4: gridspec.GridSpec(2, 2)
        }
        self.figure = Figure()
        self.figure.set_facecolor("none")
        self.canvas = FigureCanvas(self.figure)

        self.plot_selector = QtWidgets.QComboBox()
        self._update_plot_selector()
        self.plot_selector.currentIndexChanged[str].connect(self._set_bounds)

        button_layout = QtWidgets.QHBoxLayout()
        self.x_axis_changer = AxisChangerPresenter(AxisChangerView("X"))
        self.x_axis_changer.on_upper_bound_changed(self._update_x_axis_upper)
        self.x_axis_changer.on_lower_bound_changed(self._update_x_axis_lower)

        self.y_axis_changer = AxisChangerPresenter(AxisChangerView("Y"))
        self.y_axis_changer.on_upper_bound_changed(self._update_y_axis_upper)
        self.y_axis_changer.on_lower_bound_changed(self._update_y_axis_lower)

        self.errors = QtWidgets.QCheckBox("Errors")
        self.errors.stateChanged.connect(self._errors_changed)

        button_layout.addWidget(self.plot_selector)
        button_layout.addWidget(self.x_axis_changer.view)
        button_layout.addWidget(self.y_axis_changer.view)
        button_layout.addWidget(self.errors)

        grid = QtWidgets.QGridLayout()

        self.toolbar = myToolbar(self.canvas, self)
        self.toolbar.update()

        grid.addWidget(self.toolbar, 0, 0)
        grid.addWidget(self.canvas, 1, 0)
        grid.addLayout(button_layout, 2, 0)
        self.setLayout(grid)

    def setAddConnection(self, slot):
        self.toolbar.setAddConnection(slot)

    def setRmConnection(self, slot):
        self.toolbar.setRmConnection(slot)

    def _redo_layout(func):
        """
        Simple decorator (@_redo_layout) to call tight_layout() on plots
         and to redraw the canvas.
        (https://www.python.org/dev/peps/pep-0318/)
        """

        def wraps(self, *args, **kwargs):
            output = func(self, *args, **kwargs)
            if len(self.plots):
                self.figure.tight_layout()
            self.canvas.draw()
            return output
        return wraps

    def _silent_checkbox_check(self, state):
        """ Checks a checkbox without emitting a checked event. """
        self.errors.blockSignals(True)
        self.errors.setChecked(state)
        self.errors.blockSignals(False)

    def _set_plot_bounds(self, name, plot):
        """
        Sets AxisChanger bounds to the given plot bounds and updates
            the plot-specific error checkbox.
        """
        self.x_axis_changer.set_bounds(plot.get_xlim())
        self.y_axis_changer.set_bounds(plot.get_ylim())
        self._silent_checkbox_check(name in self.errors_list)

    def _set_bounds(self, new_plot):
        """
        Sets AxisChanger bounds if a new plot is added, or removes the AxisChanger
            fields if a plot is removed.
        """
        new_plot = str(new_plot)
        if new_plot and new_plot != "All":
            plot = self.get_subplot(new_plot)
            self._set_plot_bounds(new_plot, plot)
        elif not new_plot:
            self.x_axis_changer.clear_bounds()
            self.y_axis_changer.clear_bounds()

    def _get_current_plot_name(self):
        """ Returns the 'current' plot name based on the dropdown selector. """
        return str(self.plot_selector.currentText())

    def _get_current_plots(self):
        """
        Returns a list of the current plot, or all plots if 'All' is selected.
        """
        name = self._get_current_plot_name()
        return self.plots.values() if name == "All" else [
            self.get_subplot(name)]

    @_redo_layout
    def _update_x_axis(self, bound):
        """ Updates the plot's x limits with the specified bound. """
        try:
            for plot in self._get_current_plots():
                plot.set_xlim(**bound)
        except KeyError:
            return

    def _update_x_axis_lower(self, bound):
        """ Updates the lower x axis limit. """
        self._update_x_axis({"left": bound})

    def _update_x_axis_upper(self, bound):
        """ Updates the upper x axis limit. """
        self._update_x_axis({"right": bound})

    @_redo_layout
    def _update_y_axis(self, bound):
        """ Updates the plot's y limits with the specified bound. """
        try:
            for plot in self._get_current_plots():
                plot.set_ylim(**bound)
        except KeyError:
            return

    def _update_y_axis_lower(self, bound):
        """ Updates the lower y axis limit. """
        self._update_y_axis({"bottom": bound})

    def _update_y_axis_upper(self, bound):
        """ Updates the upper y axis limit. """
        self._update_y_axis({"top": bound})

    def _modify_errors_list(self, name, state):
        """
        Adds/Removes a plot name to the errors set depending on the 'state' bool.
        """
        if state:
            self.errors_list.add(name)
        else:
            try:
                self.errors_list.remove(name)
            except KeyError:
                return

    def _change_plot_errors(self, name, plot, state):
        """
        Removes the previous plot and redraws with/without errors depending on the state.
        """
        self._modify_errors_list(name, state)
        # get a copy of all the workspaces
        workspaces = copy(self.plot_storage[name].ws)
        # get the limits before replotting, so they appear unchanged.
        x, y = plot.get_xlim(), plot.get_ylim()
        # clear out the old container
        self.plot_storage[name].delete()
        for workspace in workspaces:
            self.plot(name, workspace)
        plot.set_xlim(x)
        plot.set_ylim(y)
        self._set_bounds(name)  # set AxisChanger bounds again.

    @_redo_layout
    def _errors_changed(self, state):
        """ Replots subplots with errors depending on the current selection. """
        current_name = self._get_current_plot_name()
        if current_name == "All":
            for name, plot in iteritems(self.plots):
                self._change_plot_errors(name, plot, state)
        else:
            self._change_plot_errors(
                current_name, self.get_subplot(current_name), state)

    def _set_positions(self, positions):
        """ Moves all subplots based on a gridspec change. """
        for plot, pos in zip(self.plots.values(), positions):
            grid_pos = self.current_grid[pos[0], pos[1]]
            plot.set_position(
                grid_pos.get_position(
                    self.figure))  # sets plot position, magic?
            # required because tight_layout() is used.
            plot.set_subplotspec(grid_pos)

    @_redo_layout
    def _update_gridspec(self, new_plots, last=None):
        """ Updates the gridspec; adds a 'last' subplot if one is supplied. """
        if new_plots:
            self.current_grid = self.gridspecs[new_plots]
            positions = putils.get_layout(new_plots)
            self._set_positions(positions)
            if last is not None:
                # label is necessary to fix
                # https://github.com/matplotlib/matplotlib/issues/4786
                pos = self.current_grid[positions[-1][0], positions[-1][1]]
                self.plots[last] = self.figure.add_subplot(pos, label=last)
                self.plots[last].set_subplotspec(pos)
        self._update_plot_selector()

    def _update_plot_selector(self):
        """ Updates plot selector (dropdown). """
        self.plot_selector.clear()
        self.plot_selector.addItem("All")
        self.plot_selector.addItems(list(self.plots.keys()))

    @_redo_layout
    def plot(self, name, workspace):
        """ Plots a workspace to a subplot (with errors, if necessary). """
        if name in self.errors_list:
            self.plot_workspace_errors(name, workspace)
        else:
            self.plot_workspace(name, workspace)
        self._set_bounds(name)

    def _add_plotted_line(self, name, label, lines, workspace):
        """ Appends plotted lines to the related subplot list. """
        self.plot_storage[name].addLine(label, lines, workspace)

    def plot_workspace_errors(self, name, workspace):
        """ Plots a workspace with errors, and appends caps/bars to the subplot list. """
        subplot = self.get_subplot(name)
        line, cap_lines, bar_lines = plots.plotfunctions.errorbar(
            subplot, workspace, specNum=1)
        # make a tmp plot to get auto generated legend name
        tmp, = plots.plotfunctions.plot(subplot, workspace, specNum=1)
        label = tmp.get_label()
        # remove the tmp line
        tmp.remove()
        del tmp
        # collect results
        all_lines = [line]
        all_lines.extend(cap_lines)
        all_lines.extend(bar_lines)
        self._add_plotted_line(name, label, all_lines, workspace)

    def plot_workspace(self, name, workspace):
        """ Plots a workspace normally. """
        subplot = self.get_subplot(name)
        line, = plots.plotfunctions.plot(subplot, workspace, specNum=1)
        self._add_plotted_line(name, line.get_label(), [line], workspace)

    def get_subplot(self, name):
        """ Returns the subplot corresponding to a given name """
        return self.plots[name]

    def get_subplots(self):
        """ Returns all subplots. """
        return self.plots

    def add_subplot(self, name):
        """ will raise KeyError if: plots exceed 4 """
        self._update_gridspec(len(self.plots) + 1, last=name)
        self.plot_storage[name] = subPlot(name)
        return self.get_subplot(name)

    def remove_subplot(self, name):
        """ will raise KeyError if: 'name' isn't a plot; there are no plots """
        self.figure.delaxes(self.get_subplot(name))
        del self.plots[name]
        del self.plot_storage[name]
        self._update_gridspec(len(self.plots))
        self.subplotRemovedSignal.emit(name)

    def removeLine(self, subplot, label):
        self.plot_storage[subplot].removeLine(label)

    @_redo_layout
    def add_moveable_vline(self, plot_name, x_value, y_minx, y_max, **kwargs):
        pass

    @_redo_layout
    def add_moveable_hline(self, plot_name, y_value, x_min, x_max, **kwargs):
        pass

    def closeEvent(self, event):
        self.plotCloseSignal.emit()

    def plotCloseConnection(self, slot):
        self.plotCloseSignal.connect(slot)

    @property
    def subplot_names(self):
        return self.plot_storage.keys()

    def line_labels(self, subplot):
        return self.plot_storage[subplot].lines.keys()
예제 #6
0
class PlotView(QtWidgets.QWidget):
    subplotRemovedSignal = QtCore.Signal(object)
    plotCloseSignal = QtCore.Signal()

    def __init__(self):
        super(PlotView, self).__init__()
        self.plots = OrderedDict({})
        self.errors_list = set()
        self.plot_storage = {}  # stores lines and info to create lines
        self.current_grid = None
        self.gridspecs = {
            1: gridspec.GridSpec(1, 1),
            2: gridspec.GridSpec(1, 2),
            3: gridspec.GridSpec(3, 1),
            4: gridspec.GridSpec(2, 2)
        }
        self.figure = Figure()
        self.figure.set_facecolor("none")
        self.canvas = FigureCanvas(self.figure)

        self.plot_selector = QtWidgets.QComboBox()
        self._update_plot_selector()
        self.plot_selector.currentIndexChanged[str].connect(self._set_bounds)

        button_layout = QtWidgets.QHBoxLayout()
        self.x_axis_changer = AxisChangerPresenter(AxisChangerView("X"))
        self.x_axis_changer.on_upper_bound_changed(self._update_x_axis_upper)
        self.x_axis_changer.on_lower_bound_changed(self._update_x_axis_lower)

        self.y_axis_changer = AxisChangerPresenter(AxisChangerView("Y"))
        self.y_axis_changer.on_upper_bound_changed(self._update_y_axis_upper)
        self.y_axis_changer.on_lower_bound_changed(self._update_y_axis_lower)

        self.errors = QtWidgets.QCheckBox("Errors")
        self.errors.stateChanged.connect(self._errors_changed)

        button_layout.addWidget(self.plot_selector)
        button_layout.addWidget(self.x_axis_changer.view)
        button_layout.addWidget(self.y_axis_changer.view)
        button_layout.addWidget(self.errors)

        grid = QtWidgets.QGridLayout()

        self.toolbar = myToolbar(self.canvas, self)
        self.toolbar.update()

        grid.addWidget(self.toolbar, 0, 0)
        grid.addWidget(self.canvas, 1, 0)
        grid.addLayout(button_layout, 2, 0)
        self.setLayout(grid)

    def setAddConnection(self, slot):
        self.toolbar.setAddConnection(slot)

    def setRmConnection(self, slot):
        self.toolbar.setRmConnection(slot)

    def _redo_layout(func):
        """
        Simple decorator (@_redo_layout) to call tight_layout() on plots
         and to redraw the canvas.
        (https://www.python.org/dev/peps/pep-0318/)
        """
        def wraps(self, *args, **kwargs):
            output = func(self, *args, **kwargs)
            if len(self.plots):
                self.figure.tight_layout()
            self.canvas.draw()
            return output

        return wraps

    def _silent_checkbox_check(self, state):
        """ Checks a checkbox without emitting a checked event. """
        self.errors.blockSignals(True)
        self.errors.setChecked(state)
        self.errors.blockSignals(False)

    def _set_plot_bounds(self, name, plot):
        """
        Sets AxisChanger bounds to the given plot bounds and updates
            the plot-specific error checkbox.
        """
        self.x_axis_changer.set_bounds(plot.get_xlim())
        self.y_axis_changer.set_bounds(plot.get_ylim())
        self._silent_checkbox_check(name in self.errors_list)

    def _set_bounds(self, new_plot):
        """
        Sets AxisChanger bounds if a new plot is added, or removes the AxisChanger
            fields if a plot is removed.
        """
        new_plot = str(new_plot)
        if new_plot and new_plot != "All":
            plot = self.get_subplot(new_plot)
            self._set_plot_bounds(new_plot, plot)
        elif not new_plot:
            self.x_axis_changer.clear_bounds()
            self.y_axis_changer.clear_bounds()

    def _get_current_plot_name(self):
        """ Returns the 'current' plot name based on the dropdown selector. """
        return str(self.plot_selector.currentText())

    def _get_current_plots(self):
        """
        Returns a list of the current plot, or all plots if 'All' is selected.
        """
        name = self._get_current_plot_name()
        return self.plots.values() if name == "All" else [
            self.get_subplot(name)
        ]

    @_redo_layout
    def _update_x_axis(self, bound):
        """ Updates the plot's x limits with the specified bound. """
        try:
            for plot in self._get_current_plots():
                plot.set_xlim(**bound)
        except KeyError:
            return

    def _update_x_axis_lower(self, bound):
        """ Updates the lower x axis limit. """
        self._update_x_axis({"left": bound})

    def _update_x_axis_upper(self, bound):
        """ Updates the upper x axis limit. """
        self._update_x_axis({"right": bound})

    @_redo_layout
    def _update_y_axis(self, bound):
        """ Updates the plot's y limits with the specified bound. """
        try:
            for plot in self._get_current_plots():
                plot.set_ylim(**bound)
        except KeyError:
            return

    def _update_y_axis_lower(self, bound):
        """ Updates the lower y axis limit. """
        self._update_y_axis({"bottom": bound})

    def _update_y_axis_upper(self, bound):
        """ Updates the upper y axis limit. """
        self._update_y_axis({"top": bound})

    def _modify_errors_list(self, name, state):
        """
        Adds/Removes a plot name to the errors set depending on the 'state' bool.
        """
        if state:
            self.errors_list.add(name)
        else:
            try:
                self.errors_list.remove(name)
            except KeyError:
                return

    def _change_plot_errors(self, name, plot, state):
        """
        Removes the previous plot and redraws with/without errors depending on the state.
        """
        self._modify_errors_list(name, state)
        # get a copy of all the workspaces
        workspaces = copy(self.plot_storage[name].ws)
        # get the limits before replotting, so they appear unchanged.
        x, y = plot.get_xlim(), plot.get_ylim()
        # clear out the old container
        self.plot_storage[name].delete()
        for workspace in workspaces:
            self.plot(name, workspace)
        plot.set_xlim(x)
        plot.set_ylim(y)
        self._set_bounds(name)  # set AxisChanger bounds again.

    @_redo_layout
    def _errors_changed(self, state):
        """ Replots subplots with errors depending on the current selection. """
        current_name = self._get_current_plot_name()
        if current_name == "All":
            for name, plot in iteritems(self.plots):
                self._change_plot_errors(name, plot, state)
        else:
            self._change_plot_errors(current_name,
                                     self.get_subplot(current_name), state)

    def _set_positions(self, positions):
        """ Moves all subplots based on a gridspec change. """
        for plot, pos in zip(self.plots.values(), positions):
            grid_pos = self.current_grid[pos[0], pos[1]]
            plot.set_position(grid_pos.get_position(
                self.figure))  # sets plot position, magic?
            # required because tight_layout() is used.
            plot.set_subplotspec(grid_pos)

    @_redo_layout
    def _update_gridspec(self, new_plots, last=None):
        """ Updates the gridspec; adds a 'last' subplot if one is supplied. """
        if new_plots:
            self.current_grid = self.gridspecs[new_plots]
            positions = putils.get_layout(new_plots)
            self._set_positions(positions)
            if last is not None:
                # label is necessary to fix
                # https://github.com/matplotlib/matplotlib/issues/4786
                pos = self.current_grid[positions[-1][0], positions[-1][1]]
                self.plots[last] = self.figure.add_subplot(pos, label=last)
                self.plots[last].set_subplotspec(pos)
        self._update_plot_selector()

    def _update_plot_selector(self):
        """ Updates plot selector (dropdown). """
        self.plot_selector.clear()
        self.plot_selector.addItem("All")
        self.plot_selector.addItems(list(self.plots.keys()))

    @_redo_layout
    def plot(self, name, workspace):
        """ Plots a workspace to a subplot (with errors, if necessary). """
        if name in self.errors_list:
            self.plot_workspace_errors(name, workspace)
        else:
            self.plot_workspace(name, workspace)
        self._set_bounds(name)

    def _add_plotted_line(self, name, label, lines, workspace):
        """ Appends plotted lines to the related subplot list. """
        self.plot_storage[name].addLine(label, lines, workspace)

    def plot_workspace_errors(self, name, workspace):
        """ Plots a workspace with errors, and appends caps/bars to the subplot list. """
        subplot = self.get_subplot(name)
        line, cap_lines, bar_lines = plots.plotfunctions.errorbar(subplot,
                                                                  workspace,
                                                                  specNum=1)
        # make a tmp plot to get auto generated legend name
        tmp, = plots.plotfunctions.plot(subplot, workspace, specNum=1)
        label = tmp.get_label()
        # remove the tmp line
        tmp.remove()
        del tmp
        # collect results
        all_lines = [line]
        all_lines.extend(cap_lines)
        all_lines.extend(bar_lines)
        self._add_plotted_line(name, label, all_lines, workspace)

    def plot_workspace(self, name, workspace):
        """ Plots a workspace normally. """
        subplot = self.get_subplot(name)
        line, = plots.plotfunctions.plot(subplot, workspace, specNum=1)
        self._add_plotted_line(name, line.get_label(), [line], workspace)

    def get_subplot(self, name):
        """ Returns the subplot corresponding to a given name """
        return self.plots[name]

    def get_subplots(self):
        """ Returns all subplots. """
        return self.plots

    def add_subplot(self, name):
        """ will raise KeyError if: plots exceed 4 """
        self._update_gridspec(len(self.plots) + 1, last=name)
        self.plot_storage[name] = subPlot(name)
        return self.get_subplot(name)

    def remove_subplot(self, name):
        """ will raise KeyError if: 'name' isn't a plot; there are no plots """
        self.figure.delaxes(self.get_subplot(name))
        del self.plots[name]
        del self.plot_storage[name]
        self._update_gridspec(len(self.plots))
        self.subplotRemovedSignal.emit(name)

    def removeLine(self, subplot, label):
        self.plot_storage[subplot].removeLine(label)

    @_redo_layout
    def add_moveable_vline(self, plot_name, x_value, y_minx, y_max, **kwargs):
        pass

    @_redo_layout
    def add_moveable_hline(self, plot_name, y_value, x_min, x_max, **kwargs):
        pass

    def closeEvent(self, event):
        self.plotCloseSignal.emit()

    def plotCloseConnection(self, slot):
        self.plotCloseSignal.connect(slot)

    @property
    def subplot_names(self):
        return self.plot_storage.keys()

    def line_labels(self, subplot):
        return self.plot_storage[subplot].lines.keys()
 def setUp(self):
     view = mock.create_autospec(AxisChangerView)
     self.acp = AxisChangerPresenter(view)
     self.view = self.acp.view
     self.slot = mock.Mock()
     self.bounds = mock.Mock()
class AxisChangerPresenterTest(unittest.TestCase):
    def setUp(self):
        view = mock.create_autospec(AxisChangerView)
        self.acp = AxisChangerPresenter(view)
        self.view = self.acp.view
        self.slot = mock.Mock()
        self.bounds = mock.Mock()

    def test_get_bounds(self):
        self.acp.get_bounds()
        self.assertEquals(self.view.get_bounds.call_count, 1)

    def test_set_bounds(self):
        self.acp.set_bounds(self.bounds)
        self.view.set_bounds.assert_called_with(self.bounds)

    def test_clear_bounds(self):
        self.acp.clear_bounds()
        self.assertEquals(self.view.clear_bounds.call_count, 1)

    def test_on_lower_bound_changed(self):
        self.acp.on_lower_bound_changed(self.slot)
        self.view.on_lower_bound_changed.assert_called_with(self.slot)

    def test_on_upper_bound_changed(self):
        self.acp.on_upper_bound_changed(self.slot)
        self.view.on_upper_bound_changed.assert_called_with(self.slot)

    def test_unreg_on_lower_bound_changed(self):
        self.acp.unreg_on_lower_bound_changed(self.slot)
        self.view.unreg_on_lower_bound_changed.assert_called_with(self.slot)

    def test_unreg_on_upper_bound_changed(self):
        self.acp.unreg_on_upper_bound_changed(self.slot)
        self.view.unreg_on_upper_bound_changed.assert_called_with(self.slot)