Skip to content

Commit

Permalink
Group-By functionality for Scatter Widget seperate from date-scatter.
Browse files Browse the repository at this point in the history
  • Loading branch information
aak65 committed Mar 11, 2015
1 parent 94136e1 commit 7ac7d54
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 13 deletions.
23 changes: 18 additions & 5 deletions glue/clients/layer_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
from abc import ABCMeta, abstractproperty, abstractmethod

import numpy as np
from pandas import DataFrame, groupby
from matplotlib.cm import gray
from ..external import six
from ..core.exceptions import IncompatibleAttribute
from ..core.util import PropertySetMixin, Pointer
from ..core.subset import Subset
from .util import small_view, small_view_array
from .util import small_view, small_view_array, get_colors
from ..utils import view_cascade, get_extent, color2rgb
from .ds9norm import DS9Normalize


import time
__all__ = ['LayerArtistBase', 'LayerArtist', 'DendroLayerArtist',
'HistogramLayerArtist', 'ScatterLayerArtist',
'LayerArtistContainer', 'RGBImageLayerArtist', 'ImageLayerArtist']
Expand Down Expand Up @@ -216,6 +217,8 @@ class ScatterLayerBase(object):
# which ComponentID to assign to Y axis
yatt = abstractproperty()

gatt = abstractproperty()

@abstractmethod
def get_data(self):
"""
Expand Down Expand Up @@ -352,7 +355,7 @@ def _sync_style(self):
artist.set_markerfacecolor(style.color)
artist.set_marker(style.marker)
artist.set_markersize(style.markersize)
artist.set_linestyle('None')
#artist.set_linestyle('None')
artist.set_alpha(style.alpha)
artist.set_zorder(self.zorder)
artist.set_visible(self.visible and self.enabled)
Expand Down Expand Up @@ -685,7 +688,8 @@ def _sync_style(self):
class ScatterLayerArtist(LayerArtist, ScatterLayerBase):
xatt = ChangedTrigger()
yatt = ChangedTrigger()
_property_set = LayerArtist._property_set + ['xatt', 'yatt']
gatt = ChangedTrigger()
_property_set = LayerArtist._property_set + ['xatt', 'yatt', 'gatt']

def __init__(self, layer, ax):
super(ScatterLayerArtist, self).__init__(layer, ax)
Expand All @@ -698,11 +702,20 @@ def _recalc(self):
try:
x = self.layer[self.xatt].ravel()
y = self.layer[self.yatt].ravel()
g = self.layer[self.gatt].ravel()
except IncompatibleAttribute as exc:
self.disable_invalid_attributes(*exc.args)
return False

self.artists = self._axes.plot(x, y)
self.artists = self._axes.plot(x, y, 'k.')

df = DataFrame({'g': g, 'x': x, 'y': y})
groups = df.groupby('g')
if int(len(groups)) < int(len(x)):
colors = get_colors(len(groups))
for grp, c in zip(groups, colors):
art = self._axes.plot(grp[1]['x'], grp[1]['y'], '.-', color=c)
self.artists.extend(art)
return True

def update(self, view=None, transpose=False):
Expand Down
36 changes: 32 additions & 4 deletions glue/clients/scatter_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import numpy as np

from ..core.client import Client
from ..core.data import Data, IncompatibleAttribute, ComponentID, CategoricalComponent
from ..core.data import Data, IncompatibleAttribute, ComponentID, CategoricalComponent, Component
from ..core.subset import RoiSubsetState, RangeSubsetState
from ..core.roi import PolygonalROI, RangeROI
from ..core.util import relim
from ..core.edit_subset_mode import EditSubsetMode
from ..core.message import ComponentReplacedMessage
from ..utils import lookup_class
from ..utils import lookup_class, coerce_numeric
from .viz_client import init_mpl
from .layer_artist import ScatterLayerArtist, LayerArtistContainer
from .util import update_ticks, visible_limits
Expand All @@ -34,6 +34,7 @@ class ScatterClient(Client):
xflip = CallbackProperty(False)
xatt = CallbackProperty()
yatt = CallbackProperty()
gatt = CallbackProperty()
jitter = CallbackProperty()

def __init__(self, data=None, figure=None, axes=None,
Expand All @@ -59,6 +60,7 @@ def __init__(self, data=None, figure=None, axes=None,
self._layer_updated = False # debugging
self._xset = False
self._yset = False
self._gset = False
self.axes = axes

self._connect()
Expand Down Expand Up @@ -93,6 +95,7 @@ def _connect(self):
add_callback(self, 'ymax', self._set_limits)
add_callback(self, 'xatt', partial(self._set_xydata, 'x'))
add_callback(self, 'yatt', partial(self._set_xydata, 'y'))
add_callback(self, 'gatt', partial(self._set_xydata, 'g'))
add_callback(self, 'jitter', self._jitter)
self.axes.figure.canvas.mpl_connect('draw_event',
lambda x: self._pull_properties())
Expand All @@ -119,6 +122,25 @@ def plottable_attributes(self, layer, show_hidden=False):
return [c for c in comp if
data.get_component(c).numeric]

def groupable_attributes(self, layer, show_hidden=False):
data = layer.data
l = data._shape[0]
if not data.find_component_id('None'):
none_comp = Component(np.array(range(0, l)), units='None')
data.add_component(none_comp, 'None', hidden=False)
else:
none_comp = data.find_component_id('None')
to_comp = coerce_numeric(np.array(range(0, l)))
to_comp.setflags(write=False)
none_comp._data = to_comp

comp = data.components if show_hidden else data.visible_components
groups = [comp[-1]]
for c in comp:
if data.get_component(c).group:
groups.append(c)
return groups

def add_layer(self, layer):
""" Adds a new visual layer to a client, to display either a dataset
or a subset. Updates both the client data structure and the
Expand Down Expand Up @@ -220,8 +242,8 @@ def _set_xydata(self, coord, attribute, snap=True):
:type snap: bool
"""

if coord not in ('x', 'y'):
raise TypeError("coord must be one of x,y")
if coord not in ('x', 'y', 'g'):
raise TypeError("coord must be one of x, y, g")
if not isinstance(attribute, ComponentID):
raise TypeError("attribute must be a ComponentID")

Expand All @@ -234,6 +256,9 @@ def _set_xydata(self, coord, attribute, snap=True):
new_add = not self._yset
self.yatt = attribute
self._yset = self.yatt is not None
elif coord == 'g':
self.gatt = attribute
self._gset = self.gatt is not None

# update plots
list(map(self._update_layer, self.artists.layers))
Expand Down Expand Up @@ -433,6 +458,7 @@ def _update_layer(self, layer, force=False):
for art in self.artists[layer]:
art.xatt = self.xatt
art.yatt = self.yatt
art.gatt = self.gatt
art.force_update() if force else art.update()
self._redraw()

Expand Down Expand Up @@ -465,6 +491,8 @@ def _on_component_replace(self, msg):
self.xatt = new
if self.yatt is old:
self.yatt = new
if self.gatt is old:
self.gatt = new

def register_to_hub(self, hub):
super(ScatterClient, self).register_to_hub(hub)
Expand Down
19 changes: 18 additions & 1 deletion glue/clients/util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import absolute_import, division, print_function

from functools import partial

from colorsys import hls_to_rgb
import numpy as np
from matplotlib.ticker import AutoLocator, MaxNLocator, LogLocator
from matplotlib.ticker import (LogFormatterMathtext, ScalarFormatter,
Expand Down Expand Up @@ -113,3 +113,20 @@ def update_ticks(axes, coord, components, is_log):
else:
axis.set_major_locator(AutoLocator())
axis.set_major_formatter(ScalarFormatter())


def get_colors(num_colors):
"""
Taken from: http://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors
Creates a list of distinct colors to plot with
:param num_colors: number of colors to generate
:return: list of colors
"""
colors = []
if num_colors:
for i in np.arange(0., 360., 360. / num_colors):
hue = i / 360.
lightness = (50 + np.random.rand() * 10) / 100.
saturation = (90 + np.random.rand() * 10) / 100.
colors.append(hls_to_rgb(hue, lightness, saturation))
return colors
9 changes: 9 additions & 0 deletions glue/core/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ def numeric(self):
"""
return np.can_cast(self.data[0], np.complex)

@property
def group(self):
"""
Whether or not the datatype can be considered a grouping identifier
"""
elems = len(self.data[:])
groups = len(np.unique(self.data[:]))
return (elems / groups) > 2 and groups < 1001

def __str__(self):
return "Component with shape %s" % shape_to_string(self.shape)

Expand Down
16 changes: 16 additions & 0 deletions glue/qt/ui/scatterwidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,22 @@
</item>
</layout>
</item>
<item>
<widget class="QComboBox" name="groupComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select a field to group time series information by. (ex. Patient ID)</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
Expand Down
Loading

0 comments on commit 7ac7d54

Please sign in to comment.