def __init__(self, session, parent=None): super(ScatterWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtWidgets.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = ScatterClient( self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._connect() self.unique_fields = set() tb = self.make_toolbar() cache_axes(self.client.axes, tb) self.statusBar().setSizeGripEnabled(False) self.setFocusPolicy(Qt.StrongFocus)
def __init__(self, session, parent=None): super(HistogramWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtGui.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = HistogramClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._init_limits() self.make_toolbar() self._connect() # maps _hash(componentID) -> componentID self._component_hashes = {}
def __init__(self, session, parent=None): super(MatplotlibDataViewer, self).__init__(session, parent) # Use MplWidget to set up a Matplotlib canvas inside the Qt window self.mpl_widget = MplWidget() self.setCentralWidget(self.mpl_widget) # TODO: shouldn't have to do this self.central_widget = self.mpl_widget self.figure, self._axes = init_mpl(self.mpl_widget.canvas.fig) # Set up the state which will contain everything needed to represent # the current state of the viewer self.state = self._state_cls() self.state.data_collection = session.data_collection # Set up the options widget, which will include options that control the # viewer state self.options = self._options_cls(viewer_state=self.state, session=session) add_callback(self.state, 'x_min', nonpartial(self.limits_to_mpl)) add_callback(self.state, 'x_max', nonpartial(self.limits_to_mpl)) add_callback(self.state, 'y_min', nonpartial(self.limits_to_mpl)) add_callback(self.state, 'y_max', nonpartial(self.limits_to_mpl)) self.axes.callbacks.connect('xlim_changed', nonpartial(self.limits_from_mpl)) self.axes.callbacks.connect('ylim_changed', nonpartial(self.limits_from_mpl)) self.state.add_callback('x_log', nonpartial(self.update_x_log)) self.state.add_callback('y_log', nonpartial(self.update_y_log)) self.axes.set_autoscale_on(False) # TODO: in future could move the following to a more basic data viewer class # When layer artists are removed from the layer artist container, we need # to make sure we remove matching layer states in the viewer state # layers attribute. self._layer_artist_container.on_changed( nonpartial(self._sync_state_layers)) # And vice-versa when layer states are removed from the viewer state, we # need to keep the layer_artist_container in sync self.state.add_callback('layers', nonpartial(self._sync_layer_artist_container))
def __init__(self, session, parent=None): super(CustomWidgetBase, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self._build_coordinator() self.option_widget = self._build_ui() self.client = CustomClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container, coordinator=self._coordinator) self.make_toolbar() self.statusBar().setSizeGripEnabled(False) self._update_artists = [] self.settings_changed()
def _build_main_widget(self): self.widget = SpectrumMainWindow() self.widget.window_closed.connect(self.reset) w = QtGui.QWidget() l = QtGui.QHBoxLayout() l.setSpacing(2) l.setContentsMargins(2, 2, 2, 2) w.setLayout(l) mpl = MplWidget() self.canvas = mpl.canvas l.addWidget(mpl) l.setStretchFactor(mpl, 5) self.widget.setCentralWidget(w)
def __init__(self, session, parent=None): super(HistogramWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtWidgets.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = HistogramClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._init_limits() self._connect() # maps _hash(componentID) -> componentID self._component_hashes = {}
def __init__(self, session, parent=None): super(DendroWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.option_widget = QtWidgets.QWidget() self.setCentralWidget(self.central_widget) self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self.client = DendroClient( self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._connect() self.statusBar().setSizeGripEnabled(False)
def __init__(self, image=None, wcs=None, parent=None, **kwargs): """ :param image: Image to display (2D numpy array) :param parent: Parent widget (optional) :param kwargs: Extra keywords to pass to imshow """ super(StandaloneImageWidget, self).__init__(parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self._setup_axes() self._im = None self._norm = DS9Normalize() self.make_toolbar() if image is not None: self.set_image(image=image, wcs=wcs, **kwargs)
def __init__(self, session, parent=None): super(ScatterWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtWidgets.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = ScatterClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._connect() self.unique_fields = set() self.statusBar().setSizeGripEnabled(False) self.setFocusPolicy(Qt.StrongFocus)
def __init__(self, session, parent=None): super(ScatterViewer, self).__init__(session, parent) # Use MplWidget to set up a Matplotlib canvas inside the Qt window self.mpl_widget = MplWidget() self._axes = self.mpl_widget.canvas.figure.add_subplot(1, 1, 1) self._axes.set_xlim(-10, 10) self._axes.set_ylim(-10, 10) self.setCentralWidget(self.mpl_widget) # Set up the state which will contain everything needed to represent # the current state of the viewer self.viewer_state = ScatterViewerState() # Set up the options widget, which will include options that control the # viewer state self.options = ScatterOptionsWidget(viewer_state=self.viewer_state, session=session) self.viewer_state.connect_all(self.update)
class HistogramWidget(DataViewer): LABEL = "Histogram" _property_set = DataViewer._property_set + \ 'component xlog ylog normed cumulative autoscale xmin xmax nbins'.split( ) xmin = FloatLineProperty('ui.xmin', 'Minimum value') xmax = FloatLineProperty('ui.xmax', 'Maximum value') normed = ButtonProperty('ui.normalized_box', 'Normalized?') autoscale = ButtonProperty('ui.autoscale_box', 'Autoscale view to histogram?') cumulative = ButtonProperty('ui.cumulative_box', 'Cumulative?') nbins = ValueProperty('ui.binSpinBox', 'Number of bins') xlog = ButtonProperty('ui.xlog_box', 'Log-scale the x axis?') ylog = ButtonProperty('ui.ylog_box', 'Log-scale the y axis?') _layer_style_widget_cls = {HistogramLayerArtist: HistogramLayerStyleWidget} _toolbar_cls = MatplotlibViewerToolbar tools = ['select:xrange'] def __init__(self, session, parent=None): super(HistogramWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtWidgets.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = HistogramClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._init_limits() self._connect() # maps _hash(componentID) -> componentID self._component_hashes = {} def _init_limits(self): validator = QtGui.QDoubleValidator(None) validator.setDecimals(7) self.ui.xmin.setValidator(validator) self.ui.xmax.setValidator(validator) lo, hi = self.client.xlimits self.ui.xmin.setText(str(lo)) self.ui.xmax.setText(str(hi)) def _tweak_geometry(self): self.central_widget.resize(600, 400) self.resize(self.central_widget.size()) def _connect(self): ui = self.ui cl = self.client ui.attributeCombo.currentIndexChanged.connect(self._set_attribute_from_combo) ui.normalized_box.toggled.connect(partial(setattr, cl, 'normed')) ui.autoscale_box.toggled.connect(partial(setattr, cl, 'autoscale')) ui.cumulative_box.toggled.connect(partial(setattr, cl, 'cumulative')) connect_int_spin(cl, 'nbins', ui.binSpinBox) connect_float_edit(cl, 'xmin', ui.xmin) connect_float_edit(cl, 'xmax', ui.xmax) connect_bool_button(cl, 'xlog', ui.xlog_box) connect_bool_button(cl, 'ylog', ui.ylog_box) @defer_draw def _update_attributes(self): """Repopulate the combo box that selects the quantity to plot""" combo = self.ui.attributeCombo component = self.component new = self.client.component or component combo.blockSignals(True) combo.clear() # implementation note: # PySide doesn't robustly store python objects with setData # use _hash(x) instead model = QtGui.QStandardItemModel() data_ids = set(_hash(d) for d in self._data) self._component_hashes = dict((_hash(c), c) for d in self._data for c in d.components) found = False for d in self._data: if d not in self._layer_artist_container: continue item = QtGui.QStandardItem(d.label) item.setData(_hash(d), role=Qt.UserRole) assert item.data(Qt.UserRole) == _hash(d) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) model.appendRow(item) for c in d.visible_components: if (not d.get_component(c).categorical and not d.get_component(c).numeric): continue if c is new: found = True item = QtGui.QStandardItem(c.label) item.setData(_hash(c), role=Qt.UserRole) model.appendRow(item) combo.setModel(model) # separators below data items for i in range(combo.count()): if combo.itemData(i) in data_ids: combo.insertSeparator(i + 1) combo.blockSignals(False) if found: self.component = new else: combo.setCurrentIndex(2) # skip first data + separator self._set_attribute_from_combo() @property def component(self): combo = self.ui.attributeCombo index = combo.currentIndex() return self._component_hashes.get(combo.itemData(index), None) @component.setter def component(self, component): combo = self.ui.attributeCombo if combo.count() == 0: # cold start problem, when restoring self._update_attributes() # combo.findData doesn't seem to work robustly for i in range(combo.count()): data = combo.itemData(i) if data == _hash(component): combo.setCurrentIndex(i) return raise IndexError("Component not present: %s" % component) @defer_draw def _set_attribute_from_combo(self, *args): if self.component is not None: for d in self._data: try: component = d.get_component(self.component) except: continue else: break if component.categorical: if self.ui.xlog_box.isEnabled(): self.ui.xlog_box.setEnabled(False) self.xlog = False else: if not self.ui.xlog_box.isEnabled(): self.ui.xlog_box.setEnabled(True) self.client.set_component(self.component) self.update_window_title() @defer_draw def add_data(self, data): """ Add data item to combo box. If first addition, also update attributes """ if self.data_present(data): return True if data.size > WARN_SLOW and not self._confirm_large_data(data): return False self.client.add_layer(data) self._update_attributes() return True def add_subset(self, subset): pass def _remove_data(self, data): """ Remove data item from the combo box """ pass def data_present(self, data): return data in self._layer_artist_container def register_to_hub(self, hub): super(HistogramWidget, self).register_to_hub(hub) self.client.register_to_hub(hub) hub.subscribe(self, msg.DataCollectionDeleteMessage, handler=lambda x: self._remove_data(x.data)) hub.subscribe(self, msg.DataUpdateMessage, handler=lambda *args: self._update_labels()) hub.subscribe(self, msg.ComponentsChangedMessage, handler=lambda x: self._update_attributes()) def unregister(self, hub): super(HistogramWidget, self).unregister(hub) self.client.unregister(hub) hub.unsubscribe_all(self) @property def window_title(self): c = self.client.component if c is not None: label = str(c.label) else: label = 'Histogram' return label def _update_labels(self): self.update_window_title() self._update_attributes() def __str__(self): return "Histogram Widget" def options_widget(self): return self.option_widget
def _make_plot_widget(self, parent): widget = MplWidget(parent) ax = widget.canvas.fig.add_subplot(111) p = ax.plot([1, 2, 3])[0] return widget, ax
class ScatterWidget(DataViewer): """ An interactive scatter plot. """ LABEL = "Scatter Plot" _property_set = DataViewer._property_set + \ 'xlog ylog xflip yflip hidden xatt yatt xmin xmax ymin ymax'.split() xlog = ButtonProperty('ui.xLogCheckBox', 'log scaling on x axis?') ylog = ButtonProperty('ui.yLogCheckBox', 'log scaling on y axis?') xflip = ButtonProperty('ui.xFlipCheckBox', 'invert the x axis?') yflip = ButtonProperty('ui.yFlipCheckBox', 'invert the y axis?') xmin = FloatLineProperty('ui.xmin', 'Lower x limit of plot') xmax = FloatLineProperty('ui.xmax', 'Upper x limit of plot') ymin = FloatLineProperty('ui.ymin', 'Lower y limit of plot') ymax = FloatLineProperty('ui.ymax', 'Upper y limit of plot') hidden = ButtonProperty('ui.hidden_attributes', 'Show hidden attributes') xatt = CurrentComboProperty('ui.xAxisComboBox', 'Attribute to plot on x axis') yatt = CurrentComboProperty('ui.yAxisComboBox', 'Attribute to plot on y axis') _layer_style_widget_cls = {ScatterLayerArtist: ScatterLayerStyleWidget} _toolbar_cls = MatplotlibViewerToolbar tools = ['select:rectangle', 'select:xrange', 'select:yrange', 'select:circle', 'select:polygon'] def __init__(self, session, parent=None): super(ScatterWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtWidgets.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = ScatterClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._connect() self.unique_fields = set() self.statusBar().setSizeGripEnabled(False) self.setFocusPolicy(Qt.StrongFocus) def initialize_toolbar(self): super(ScatterWidget, self).initialize_toolbar() cache_axes(self.client.axes, self.toolbar) def _tweak_geometry(self): self.central_widget.resize(600, 400) self.resize(self.central_widget.size()) def _connect(self): ui = self.ui cl = self.client connect_bool_button(cl, 'xlog', ui.xLogCheckBox) connect_bool_button(cl, 'ylog', ui.yLogCheckBox) connect_bool_button(cl, 'xflip', ui.xFlipCheckBox) connect_bool_button(cl, 'yflip', ui.yFlipCheckBox) ui.xAxisComboBox.currentIndexChanged.connect(self.update_xatt) ui.yAxisComboBox.currentIndexChanged.connect(self.update_yatt) ui.hidden_attributes.toggled.connect(lambda x: self._update_combos()) ui.swapAxes.clicked.connect(nonpartial(self.swap_axes)) ui.snapLimits.clicked.connect(cl.snap) connect_float_edit(cl, 'xmin', ui.xmin) connect_float_edit(cl, 'xmax', ui.xmax) connect_float_edit(cl, 'ymin', ui.ymin) connect_float_edit(cl, 'ymax', ui.ymax) @defer_draw def _update_combos(self): """ Update contents of combo boxes """ # have to be careful here, since client and/or widget # are potentially out of sync layer_ids = [] # show hidden attributes if needed if ((self.client.xatt and self.client.xatt.hidden) or (self.client.yatt and self.client.yatt.hidden)): self.hidden = True # determine which components to put in combos for l in self.client.data: if not self.client.is_layer_present(l): continue for lid in self.client.plottable_attributes( l, show_hidden=self.hidden): if lid not in layer_ids: layer_ids.append(lid) oldx = self.xatt oldy = self.yatt newx = self.client.xatt or oldx newy = self.client.yatt or oldy for combo, target in zip([self.ui.xAxisComboBox, self.ui.yAxisComboBox], [newx, newy]): combo.blockSignals(True) combo.clear() if not layer_ids: # empty component list continue # populate for lid in layer_ids: combo.addItem(lid.label, userData=lid) idx = layer_ids.index(target) if target in layer_ids else 0 combo.setCurrentIndex(idx) combo.blockSignals(False) # ensure client and widget synced self.client.xatt = self.xatt self.client.lyatt = self.yatt @defer_draw def add_data(self, data): """Add a new data set to the widget :returns: True if the addition was expected, False otherwise """ if self.client.is_layer_present(data): return if data.size > WARN_SLOW and not self._confirm_large_data(data): return False first_layer = self.client.layer_count == 0 self.client.add_data(data) self._update_combos() if first_layer: # forces both x and y axes to be rescaled self.update_xatt(None) self.update_yatt(None) self.ui.xAxisComboBox.setCurrentIndex(0) if len(data.visible_components) > 1: self.ui.yAxisComboBox.setCurrentIndex(1) else: self.ui.yAxisComboBox.setCurrentIndex(0) self.update_window_title() return True @defer_draw def add_subset(self, subset): """Add a subset to the widget :returns: True if the addition was accepted, False otherwise """ if self.client.is_layer_present(subset): return data = subset.data if data.size > WARN_SLOW and not self._confirm_large_data(data): return False first_layer = self.client.layer_count == 0 self.client.add_layer(subset) self._update_combos() if first_layer: # forces both x and y axes to be rescaled self.update_xatt(None) self.update_yatt(None) self.ui.xAxisComboBox.setCurrentIndex(0) if len(data.visible_components) > 1: self.ui.yAxisComboBox.setCurrentIndex(1) else: self.ui.yAxisComboBox.setCurrentIndex(0) self.update_window_title() return True def register_to_hub(self, hub): super(ScatterWidget, self).register_to_hub(hub) self.client.register_to_hub(hub) hub.subscribe(self, core.message.DataUpdateMessage, nonpartial(self._sync_labels)) hub.subscribe(self, core.message.ComponentsChangedMessage, nonpartial(self._update_combos)) hub.subscribe(self, core.message.ComponentReplacedMessage, self._on_component_replace) def _on_component_replace(self, msg): # let client update its state first self.client._on_component_replace(msg) self._update_combos() def unregister(self, hub): super(ScatterWidget, self).unregister(hub) hub.unsubscribe_all(self.client) hub.unsubscribe_all(self) @defer_draw def swap_axes(self): xid = self.ui.xAxisComboBox.currentIndex() yid = self.ui.yAxisComboBox.currentIndex() xlog = self.ui.xLogCheckBox.isChecked() ylog = self.ui.yLogCheckBox.isChecked() xflip = self.ui.xFlipCheckBox.isChecked() yflip = self.ui.yFlipCheckBox.isChecked() self.ui.xAxisComboBox.setCurrentIndex(yid) self.ui.yAxisComboBox.setCurrentIndex(xid) self.ui.xLogCheckBox.setChecked(ylog) self.ui.yLogCheckBox.setChecked(xlog) self.ui.xFlipCheckBox.setChecked(yflip) self.ui.yFlipCheckBox.setChecked(xflip) @defer_draw def update_xatt(self, index): component_id = self.xatt self.client.xatt = component_id @defer_draw def update_yatt(self, index): component_id = self.yatt self.client.yatt = component_id @property def window_title(self): data = self.client.data label = ', '.join([d.label for d in data if self.client.is_visible(d)]) return label def _sync_labels(self): self.update_window_title() def options_widget(self): return self.option_widget @defer_draw def restore_layers(self, rec, context): self.client.restore_layers(rec, context) self._update_combos() # manually force client attributes to sync self.update_xatt(None) self.update_yatt(None)
def __init__(self, session, parent=None): super(ExampleViewer, self).__init__(session, parent=parent) self.central_widget = MplWidget(parent) self._axes = self.central_widget.canvas.fig.add_subplot(111) self._axes.plot([1, 2, 3])[0] self.setCentralWidget(self.central_widget)
class HistogramWidget(DataViewer): LABEL = "Histogram" _property_set = DataViewer._property_set + \ 'component xlog ylog normed cumulative autoscale xmin xmax nbins'.split( ) xmin = FloatLineProperty('ui.xmin', 'Minimum value') xmax = FloatLineProperty('ui.xmax', 'Maximum value') normed = ButtonProperty('ui.normalized_box', 'Normalized?') autoscale = ButtonProperty('ui.autoscale_box', 'Autoscale view to histogram?') cumulative = ButtonProperty('ui.cumulative_box', 'Cumulative?') nbins = ValueProperty('ui.binSpinBox', 'Number of bins') xlog = ButtonProperty('ui.xlog_box', 'Log-scale the x axis?') ylog = ButtonProperty('ui.ylog_box', 'Log-scale the y axis?') def __init__(self, session, parent=None): super(HistogramWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtGui.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = HistogramClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._init_limits() self.make_toolbar() self._connect() # maps _hash(componentID) -> componentID self._component_hashes = {} @staticmethod def _get_default_tools(): return [] def _init_limits(self): validator = QtGui.QDoubleValidator(None) validator.setDecimals(7) self.ui.xmin.setValidator(validator) self.ui.xmax.setValidator(validator) lo, hi = self.client.xlimits self.ui.xmin.setText(str(lo)) self.ui.xmax.setText(str(hi)) def _tweak_geometry(self): self.central_widget.resize(600, 400) self.resize(self.central_widget.size()) def _connect(self): ui = self.ui cl = self.client ui.attributeCombo.currentIndexChanged.connect(self._set_attribute_from_combo) ui.normalized_box.toggled.connect(partial(setattr, cl, 'normed')) ui.autoscale_box.toggled.connect(partial(setattr, cl, 'autoscale')) ui.cumulative_box.toggled.connect(partial(setattr, cl, 'cumulative')) connect_int_spin(cl, 'nbins', ui.binSpinBox) connect_float_edit(cl, 'xmin', ui.xmin) connect_float_edit(cl, 'xmax', ui.xmax) connect_bool_button(cl, 'xlog', ui.xlog_box) connect_bool_button(cl, 'ylog', ui.ylog_box) def make_toolbar(self): result = GlueToolbar(self.central_widget.canvas, self, name='Histogram') for mode in self._mouse_modes(): result.add_mode(mode) self.addToolBar(result) return result def _mouse_modes(self): axes = self.client.axes def apply_mode(mode): return self.apply_roi(mode.roi()) rect = HRangeMode(axes, roi_callback=apply_mode) return [rect] @defer_draw def _update_attributes(self): """Repopulate the combo box that selects the quantity to plot""" combo = self.ui.attributeCombo component = self.component new = self.client.component or component combo.blockSignals(True) combo.clear() # implementation note: # PySide doesn't robustly store python objects with setData # use _hash(x) instead model = QtGui.QStandardItemModel() data_ids = set(_hash(d) for d in self._data) self._component_hashes = dict((_hash(c), c) for d in self._data for c in d.components) found = False for d in self._data: if d not in self._layer_artist_container: continue item = QtGui.QStandardItem(d.label) item.setData(_hash(d), role=Qt.UserRole) assert item.data(Qt.UserRole) == _hash(d) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) model.appendRow(item) for c in d.visible_components: if (not d.get_component(c).categorical and not d.get_component(c).numeric): continue if c is new: found = True item = QtGui.QStandardItem(c.label) item.setData(_hash(c), role=Qt.UserRole) model.appendRow(item) combo.setModel(model) # separators below data items for i in range(combo.count()): if combo.itemData(i) in data_ids: combo.insertSeparator(i + 1) combo.blockSignals(False) if found: self.component = new else: combo.setCurrentIndex(2) # skip first data + separator self._set_attribute_from_combo() @property def component(self): combo = self.ui.attributeCombo index = combo.currentIndex() return self._component_hashes.get(combo.itemData(index), None) @component.setter def component(self, component): combo = self.ui.attributeCombo if combo.count() == 0: # cold start problem, when restoring self._update_attributes() # combo.findData doesn't seem to work robustly for i in range(combo.count()): data = combo.itemData(i) if data == _hash(component): combo.setCurrentIndex(i) return raise IndexError("Component not present: %s" % component) @defer_draw def _set_attribute_from_combo(self, *args): if self.component is not None: for d in self._data: try: component = d.get_component(self.component) except: continue else: break if component.categorical: if self.ui.xlog_box.isEnabled(): self.ui.xlog_box.setEnabled(False) self.xlog = False else: if not self.ui.xlog_box.isEnabled(): self.ui.xlog_box.setEnabled(True) self.client.set_component(self.component) self.update_window_title() @defer_draw def add_data(self, data): """ Add data item to combo box. If first addition, also update attributes """ if self.data_present(data): return True if data.size > WARN_SLOW and not self._confirm_large_data(data): return False self.client.add_layer(data) self._update_attributes() return True def add_subset(self, subset): pass def _remove_data(self, data): """ Remove data item from the combo box """ pass def data_present(self, data): return data in self._layer_artist_container def register_to_hub(self, hub): super(HistogramWidget, self).register_to_hub(hub) self.client.register_to_hub(hub) hub.subscribe(self, msg.DataCollectionDeleteMessage, handler=lambda x: self._remove_data(x.data)) hub.subscribe(self, msg.DataUpdateMessage, handler=lambda *args: self._update_labels()) hub.subscribe(self, msg.ComponentsChangedMessage, handler=lambda x: self._update_attributes()) def unregister(self, hub): super(HistogramWidget, self).unregister(hub) self.client.unregister(hub) hub.unsubscribe_all(self) @property def window_title(self): c = self.client.component if c is not None: label = str(c.label) else: label = 'Histogram' return label def _update_labels(self): self.update_window_title() self._update_attributes() def __str__(self): return "Histogram Widget" def options_widget(self): return self.option_widget
def make_central_widget(self): return MplWidget()
class ScatterWidget(DataViewer): """ An interactive scatter plot. """ LABEL = "Scatter Plot" _property_set = DataViewer._property_set + \ 'xlog ylog xflip yflip hidden xatt yatt xmin xmax ymin ymax'.split() xlog = ButtonProperty('ui.xLogCheckBox', 'log scaling on x axis?') ylog = ButtonProperty('ui.yLogCheckBox', 'log scaling on y axis?') xflip = ButtonProperty('ui.xFlipCheckBox', 'invert the x axis?') yflip = ButtonProperty('ui.yFlipCheckBox', 'invert the y axis?') xmin = FloatLineProperty('ui.xmin', 'Lower x limit of plot') xmax = FloatLineProperty('ui.xmax', 'Upper x limit of plot') ymin = FloatLineProperty('ui.ymin', 'Lower y limit of plot') ymax = FloatLineProperty('ui.ymax', 'Upper y limit of plot') hidden = ButtonProperty('ui.hidden_attributes', 'Show hidden attributes') xatt = CurrentComboProperty('ui.xAxisComboBox', 'Attribute to plot on x axis') yatt = CurrentComboProperty('ui.yAxisComboBox', 'Attribute to plot on y axis') def __init__(self, session, parent=None): super(ScatterWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.option_widget = QtGui.QWidget() self.setCentralWidget(self.central_widget) self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = ScatterClient( self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._connect() self.unique_fields = set() tb = self.make_toolbar() cache_axes(self.client.axes, tb) self.statusBar().setSizeGripEnabled(False) self.setFocusPolicy(Qt.StrongFocus) @staticmethod def _get_default_tools(): return [] def _tweak_geometry(self): self.central_widget.resize(600, 400) self.resize(self.central_widget.size()) def _connect(self): ui = self.ui cl = self.client connect_bool_button(cl, 'xlog', ui.xLogCheckBox) connect_bool_button(cl, 'ylog', ui.yLogCheckBox) connect_bool_button(cl, 'xflip', ui.xFlipCheckBox) connect_bool_button(cl, 'yflip', ui.yFlipCheckBox) ui.xAxisComboBox.currentIndexChanged.connect(self.update_xatt) ui.yAxisComboBox.currentIndexChanged.connect(self.update_yatt) ui.hidden_attributes.toggled.connect(lambda x: self._update_combos()) ui.swapAxes.clicked.connect(nonpartial(self.swap_axes)) ui.snapLimits.clicked.connect(cl.snap) connect_float_edit(cl, 'xmin', ui.xmin) connect_float_edit(cl, 'xmax', ui.xmax) connect_float_edit(cl, 'ymin', ui.ymin) connect_float_edit(cl, 'ymax', ui.ymax) def make_toolbar(self): result = GlueToolbar(self.central_widget.canvas, self, name='Scatter Plot') for mode in self._mouse_modes(): result.add_mode(mode) self.addToolBar(result) return result def _mouse_modes(self): axes = self.client.axes def apply_mode(mode): return self.apply_roi(mode.roi()) rect = RectangleMode(axes, roi_callback=apply_mode) xra = HRangeMode(axes, roi_callback=apply_mode) yra = VRangeMode(axes, roi_callback=apply_mode) circ = CircleMode(axes, roi_callback=apply_mode) poly = PolyMode(axes, roi_callback=apply_mode) return [rect, xra, yra, circ, poly] @defer_draw def _update_combos(self): """ Update contents of combo boxes """ # have to be careful here, since client and/or widget # are potentially out of sync layer_ids = [] # show hidden attributes if needed if ((self.client.xatt and self.client.xatt.hidden) or (self.client.yatt and self.client.yatt.hidden)): self.hidden = True # determine which components to put in combos for l in self.client.data: if not self.client.is_layer_present(l): continue for lid in self.client.plottable_attributes( l, show_hidden=self.hidden): if lid not in layer_ids: layer_ids.append(lid) oldx = self.xatt oldy = self.yatt newx = self.client.xatt or oldx newy = self.client.yatt or oldy for combo, target in zip( [self.ui.xAxisComboBox, self.ui.yAxisComboBox], [newx, newy]): combo.blockSignals(True) combo.clear() if not layer_ids: # empty component list continue # populate for lid in layer_ids: combo.addItem(lid.label, userData=lid) idx = layer_ids.index(target) if target in layer_ids else 0 combo.setCurrentIndex(idx) combo.blockSignals(False) # ensure client and widget synced self.client.xatt = self.xatt self.client.lyatt = self.yatt @defer_draw def add_data(self, data): """Add a new data set to the widget :returns: True if the addition was expected, False otherwise """ if self.client.is_layer_present(data): return if data.size > WARN_SLOW and not self._confirm_large_data(data): return False first_layer = self.client.layer_count == 0 self.client.add_data(data) self._update_combos() if first_layer: # forces both x and y axes to be rescaled self.update_xatt(None) self.update_yatt(None) self.ui.xAxisComboBox.setCurrentIndex(0) if len(data.visible_components) > 1: self.ui.yAxisComboBox.setCurrentIndex(1) else: self.ui.yAxisComboBox.setCurrentIndex(0) self.update_window_title() return True @defer_draw def add_subset(self, subset): """Add a subset to the widget :returns: True if the addition was accepted, False otherwise """ if self.client.is_layer_present(subset): return data = subset.data if data.size > WARN_SLOW and not self._confirm_large_data(data): return False first_layer = self.client.layer_count == 0 self.client.add_layer(subset) self._update_combos() if first_layer: # forces both x and y axes to be rescaled self.update_xatt(None) self.update_yatt(None) self.ui.xAxisComboBox.setCurrentIndex(0) if len(data.visible_components) > 1: self.ui.yAxisComboBox.setCurrentIndex(1) else: self.ui.yAxisComboBox.setCurrentIndex(0) self.update_window_title() return True def register_to_hub(self, hub): super(ScatterWidget, self).register_to_hub(hub) self.client.register_to_hub(hub) hub.subscribe(self, core.message.DataUpdateMessage, nonpartial(self._sync_labels)) hub.subscribe(self, core.message.ComponentsChangedMessage, nonpartial(self._update_combos)) hub.subscribe(self, core.message.ComponentReplacedMessage, self._on_component_replace) def _on_component_replace(self, msg): # let client update its state first self.client._on_component_replace(msg) self._update_combos() def unregister(self, hub): super(ScatterWidget, self).unregister(hub) hub.unsubscribe_all(self.client) hub.unsubscribe_all(self) @defer_draw def swap_axes(self): xid = self.ui.xAxisComboBox.currentIndex() yid = self.ui.yAxisComboBox.currentIndex() xlog = self.ui.xLogCheckBox.isChecked() ylog = self.ui.yLogCheckBox.isChecked() xflip = self.ui.xFlipCheckBox.isChecked() yflip = self.ui.yFlipCheckBox.isChecked() self.ui.xAxisComboBox.setCurrentIndex(yid) self.ui.yAxisComboBox.setCurrentIndex(xid) self.ui.xLogCheckBox.setChecked(ylog) self.ui.yLogCheckBox.setChecked(xlog) self.ui.xFlipCheckBox.setChecked(yflip) self.ui.yFlipCheckBox.setChecked(xflip) @defer_draw def update_xatt(self, index): component_id = self.xatt self.client.xatt = component_id @defer_draw def update_yatt(self, index): component_id = self.yatt self.client.yatt = component_id @property def window_title(self): data = self.client.data label = ', '.join([d.label for d in data if self.client.is_visible(d)]) return label def _sync_labels(self): self.update_window_title() def options_widget(self): return self.option_widget @defer_draw def restore_layers(self, rec, context): self.client.restore_layers(rec, context) self._update_combos() # manually force client attributes to sync self.update_xatt(None) self.update_yatt(None)