Source code for glue.viewers.histogram.qt.viewer_widget
from __future__ import absolute_import, division, print_function
import os
from functools import partial
from glue.external.qt.QtCore import Qt
from glue.external.qt import QtGui
from glue.core import message as msg
from glue.viewers.histogram.client import HistogramClient
from glue.viewers.common.qt.toolbar import GlueToolbar
from glue.viewers.common.qt.mouse_mode import HRangeMode
from glue.utils.qt import load_ui
from glue.utils.qt.widget_properties import (connect_int_spin, ButtonProperty,
FloatLineProperty, connect_float_edit,
ValueProperty, connect_bool_button)
from glue.viewers.common.qt.data_viewer import DataViewer
from glue.viewers.common.qt.mpl_widget import MplWidget, defer_draw
from glue.viewers.histogram.qt.layer_style_widget import HistogramLayerStyleWidget
from glue.utils.array import pretty_number
__all__ = ['HistogramWidget']
WARN_SLOW = 10000000
def _hash(x):
return str(id(x))
[docs]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 = HistogramLayerStyleWidget
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)
[docs] 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
[docs] 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 _remove_data(self, data):
""" Remove data item from the combo box """
pass
[docs] 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())
[docs] 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"