Newer
Older
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source
# & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +
# This file is part of the mantid workbench.
from __future__ import (absolute_import, unicode_literals)
from matplotlib.axes import ErrorbarContainer
from matplotlib.lines import Line2D
from mantid.plots.helperfunctions import get_data_from_errorbar_container
from mantidqt.utils import BlockQSignals
from mantidqt.widgets.plotconfigdialog import get_axes_names_dict, curve_in_ax
from mantidqt.widgets.plotconfigdialog.curvestabwidget import (CurveProperties, set_curve_hidden,
set_errorbars_hidden, curve_has_errors,
remove_curve_from_ax)
from mantidqt.widgets.plotconfigdialog.curvestabwidget.view import CurvesTabWidgetView
class CurvesTabWidgetPresenter:
def __init__(self, fig, view=None, parent=None):
self.fig = fig
if not view:
self.view = CurvesTabWidgetView(parent)
else:
self.view = view
# Fill the fields in the view
self.axes_names_dict = get_axes_names_dict(self.fig, curves_only=True)
self.populate_select_axes_combo_box()
self.populate_curve_combo_box_and_update_view()
# Signals
self.view.select_axes_combo_box.currentIndexChanged.connect(
self.populate_curve_combo_box_and_update_view)
self.view.select_curve_combo_box.currentIndexChanged.connect(
self.view.remove_curve_button.clicked.connect(
self.remove_selected_curve)
def apply_properties(self):
"""Take properties from views and set them on the selected curve"""
view_props = self.get_view_properties()
self.replot_selected_curve(view_props.get_plot_kwargs())
# Set the curve's label, hide if necessary and redraw legend
curve = self.get_selected_curve()
self.set_curve_label(curve, view_props.label)
set_curve_hidden(curve, view_props.hide)
# No need to hide errors if they're already hidden
if not view_props.hide:
set_errorbars_hidden(curve, view_props.hide_errors)
self.get_selected_ax().legend().draggable()
def close_tab(self):
"""Close the tab and set the view to None"""
self.view.close()
self.view = None
"""
Get selected axes object from name in combo box.
If not found return None.
"""
try:
return self.axes_names_dict[self.view.get_selected_ax_name()]
except KeyError:
return None
"""Get selected Line2D or ErrorbarContainer object"""
return self.curve_names_dict[self.view.get_selected_curve_name()]
def get_view_properties(self):
"""Get top level properties from view"""
return CurveProperties.from_view(self.view)
@staticmethod
def replot_curve(ax, curve, plot_kwargs):
"""Replot the given curve with new kwargs"""
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# Because of 'error every' option we must store an original copy of the
# curve so we do not lose data when plotting fewer errorbars. Store it
# on the current curve as this will be persistent after closing the
# options dialog
curve = getattr(curve, 'original_curve', curve)
setattr(curve, 'original_curve', curve)
if isinstance(curve, Line2D):
[plot_kwargs.pop(arg, None) for arg in
['capsize', 'capthick', 'ecolor', 'elinewidth', 'errorevery']]
new_curve = ax.plot(curve.get_xdata(), curve.get_ydata(),
**plot_kwargs)[0]
elif isinstance(curve, ErrorbarContainer):
x, y, xerr, yerr = get_data_from_errorbar_container(curve)
new_curve = ax.errorbar(x, y, xerr=xerr, yerr=yerr, **plot_kwargs)
else:
raise ValueError("Curve must have type 'Line2D' or "
"'ErrorbarContainer'. Found '{}'"
"".format(type(curve)))
setattr(new_curve, 'original_curve', curve)
return new_curve
def replot_selected_curve(self, plot_kwargs):
"""Replot the selected curve with the given plot kwargs"""
ax = self.get_selected_ax()
curve = self.get_selected_curve()
if isinstance(ax, MantidAxes):
try:
new_curve = ax.replot_artist(curve, errorbars=True,
**plot_kwargs)
except ValueError:
new_curve = self.replot_curve(ax, curve, plot_kwargs)
else:
new_curve = self.replot_curve(ax, curve, plot_kwargs)
setattr(new_curve, 'errorevery', plot_kwargs.get('errorevery', 1))
self.curve_names_dict[self.view.get_selected_curve_name()] = new_curve
self.get_selected_ax().legend().draggable()
def populate_curve_combo_box_and_update_view(self):
Populate curve combo box and update the view with the curve's
properties.
if self._populate_select_curve_combo_box():
def populate_select_axes_combo_box(self):
Add Axes names to 'select axes' combo box.
Names are generated similary to in AxesTabWidgetPresenter
"""
names = []
for name, ax in self.axes_names_dict.items():
if curve_in_ax(ax):
names.append(name)
names = sorted(names, key=lambda x: x[x.rfind("("):])
self.view.populate_select_axes_combo_box(names)
def remove_selected_curve(self):
"""
Remove selected curve from figure and combobox. If there are no
curves left on the axes remove that axes from the axes combo box
# Remove curve from ax and remove from curve names dictionary
remove_curve_from_ax(self.get_selected_curve())
self.curve_names_dict.pop(self.view.get_selected_curve_name())
# Update the legend and redraw
self.get_selected_ax().relim()
self.get_selected_ax().autoscale()
self.get_selected_ax().legend().draggable()
self.get_selected_ax().figure.canvas.draw()
# Remove the curve from the curve selection combo box
if self.remove_selected_curve_combo_box_entry():
return
self.update_view()
def remove_selected_curve_combo_box_entry(self):
"""
Remove selected entry in 'select_curve_combo_box'. If no curves remain
on the axes remove the axes entry from the 'select_axes_combo_box'. If
no axes with curves remain close the tab and return True
"""
with BlockQSignals(self.view.select_curve_combo_box):
self.view.remove_select_curve_combo_box_selected_item()
if self.view.select_curve_combo_box.count() == 0:
self.view.remove_select_axes_combo_box_selected_item()
if self.view.select_axes_combo_box.count() == 0:
def set_curve_label(self, curve, label):
"""Set label on curve and update its entry in the combo box"""
old_name = self.view.get_selected_curve_name()
curve_name = self._generate_curve_name(curve, label)
self.view.set_selected_curve_selector_text(curve_name)
self.curve_names_dict[curve_name] = self.curve_names_dict.pop(old_name)
def set_errorbars_tab_enabled(self):
"""Enable/disable the errorbar tab for selected curve"""
enable_errorbars = curve_has_errors(self.get_selected_curve())
self.view.set_errorbars_tab_enabled(enable_errorbars)
def update_view(self):
"""Update the view with the selected curve's properties"""
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
curve_props = CurveProperties.from_curve(self.get_selected_curve())
self.view.update_fields(curve_props)
self.set_errorbars_tab_enabled()
# Private methods
def _generate_curve_name(self, curve, label):
if label:
if label == '_nolegend_':
return None
else:
name = label
else:
name = '_nolabel_'
# Deal with case of curves sharing the same label
idx, base_name = 1, name
while name in self.curve_names_dict:
if self.curve_names_dict[name] == curve:
break
name = base_name + " ({})".format(idx)
idx += 1
return name
def _get_selected_ax_errorbars(self):
"""Get all errorbar containers in selected axes"""
ax = self.get_selected_ax()
return [cont for cont in ax.containers if isinstance(cont,
ErrorbarContainer)]
def _populate_select_curve_combo_box(self):
"""
Add curves on selected axes to the 'select curves' combo box.
Return False if there are no lines on the axes (this can occur
when a user uses the "Remove Curve" button), else return True.
"""
with BlockQSignals(self.view.select_curve_combo_box):
self.view.select_curve_combo_box.clear()
selected_ax = self.get_selected_ax()
if not selected_ax:
self.view.close()
return False
for errorbar_container in self._get_selected_ax_errorbars():
self._update_selected_curve_name(errorbar_container)
for line in selected_ax.get_lines():
self._update_selected_curve_name(line)
self.view.populate_select_curve_combo_box(self.curve_names_dict.keys())
return True
def _update_selected_curve_name(self, curve):
"""Update the selected curve's name in the curve_names_dict"""
name = self._generate_curve_name(curve, curve.get_label())
if name:
self.curve_names_dict[name] = curve