Source code for glue.viewers.scatter.qt.viewer_widget
from __future__ import absolute_import, division, print_function
import os
from glue.external.qt.QtCore import Qt
from glue.external.qt import QtGui
from glue import core
from glue.viewers.scatter.client import ScatterClient
from glue.viewers.common.qt.toolbar import GlueToolbar
from glue.viewers.common.qt.mouse_mode import (RectangleMode, CircleMode,
PolyMode, HRangeMode, VRangeMode)
from glue.utils.qt import load_ui
from glue.viewers.common.qt.data_viewer import DataViewer
from glue.viewers.common.qt.mpl_widget import MplWidget, defer_draw
from glue.viewers.scatter.qt.layer_style_widget import ScatterLayerStyleWidget
from glue.utils import nonpartial, cache_axes
from glue.utils.qt.widget_properties import (ButtonProperty, FloatLineProperty,
CurrentComboProperty,
connect_bool_button, connect_float_edit)
__all__ = ['ScatterWidget']
WARN_SLOW = 1000000 # max number of points which render quickly
[docs]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 = ScatterLayerStyleWidget
def __init__(self, session, parent=None):
super(ScatterWidget, 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 = 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)
[docs] 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
[docs] 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
[docs] 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
[docs] 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()
[docs] def unregister(self, hub):
super(ScatterWidget, self).unregister(hub)
hub.unsubscribe_all(self.client)
hub.unsubscribe_all(self)
@defer_draw
[docs] 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
@defer_draw
@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()
@defer_draw
[docs] 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)