Exemple #1
0
    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)
Exemple #2
0
    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 = {}
Exemple #3
0
    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))
Exemple #4
0
    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()
Exemple #5
0
    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)
Exemple #6
0
    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 = {}
Exemple #7
0
    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)
Exemple #8
0
    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)
Exemple #9
0
    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)
Exemple #10
0
    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)
Exemple #11
0
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
Exemple #12
0
 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
Exemple #13
0
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)
Exemple #14
0
 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)
Exemple #15
0
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
Exemple #16
0
 def make_central_widget(self):
     return MplWidget()
Exemple #17
0
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)