@@ -695,10 +695,12 @@ def __deactivate_selection(self):
695695
696696 self .varmodel_other .extend (variables )
697697
698- def _get_data (self , var ):
699- """Return the column data for variable `var`."""
698+ def _get_data (self , var , dtype ):
699+ """
700+ Return the column data and mask for variable `var`
701+ """
700702 X , _ = self .data .get_column_view (var )
701- return X . ravel ( )
703+ return column_data ( self . data , var , dtype )
702704
703705 def _setup_plot (self , reset_view = True ):
704706 self .__replot_requested = False
@@ -708,7 +710,7 @@ def _setup_plot(self, reset_view=True):
708710 if not variables :
709711 return
710712
711- coords = [self ._get_data (var ) for var in variables ]
713+ coords = [self ._get_data (var , dtype = float )[ 0 ] for var in variables ]
712714 coords = numpy .vstack (coords )
713715 p , N = coords .shape
714716 assert N == len (self .data ), p == len (variables )
@@ -721,8 +723,9 @@ def _setup_plot(self, reset_view=True):
721723 coords = coords [:, mask ]
722724
723725 X , Y = numpy .dot (axes , coords )
724- X = plotutils .normalized (X )
725- Y = plotutils .normalized (Y )
726+ if X .size and Y .size :
727+ X = plotutils .normalized (X )
728+ Y = plotutils .normalized (Y )
726729
727730 pen_data , brush_data = self ._color_data (mask )
728731 size_data = self ._size_data (mask )
@@ -773,7 +776,7 @@ def _setup_plot(self, reset_view=True):
773776 def _color_data (self , mask = None ):
774777 color_var = self .color_var ()
775778 if color_var is not None :
776- color_data = self ._get_data (color_var )
779+ color_data , _ = self ._get_data (color_var , dtype = float )
777780 if color_var .is_continuous :
778781 color_data = plotutils .continuous_colors (
779782 color_data , None , * color_var .colors )
@@ -866,14 +869,12 @@ def _shape_data(self, mask):
866869 shape_data = numpy .array (["o" ] * len (self .data ))
867870 else :
868871 assert shape_var .is_discrete
869- max_symbol = len (ScatterPlotItem .Symbols ) - 1
870- shape = self ._get_data (shape_var )
871- shape_mask = numpy .isnan (shape )
872- shape %= max_symbol - 1
873- shape [shape_mask ] = max_symbol
874-
875872 symbols = numpy .array (list (ScatterPlotItem .Symbols ))
876- shape_data = symbols [numpy .asarray (shape , dtype = int )]
873+ max_symbol = symbols .size - 1
874+ shapeidx , shape_mask = column_data (self .data , shape_var , dtype = int )
875+ shapeidx [shape_mask ] = max_symbol
876+ shapeidx [~ shape_mask ] %= max_symbol - 1
877+ shape_data = symbols [shapeidx ]
877878 if mask is None :
878879 return shape_data
879880 else :
@@ -892,12 +893,20 @@ def _size_data(self, mask=None):
892893 size_data = numpy .full ((len (self .data ),), self .point_size ,
893894 dtype = float )
894895 else :
895- size_data = plotutils .normalized (self ._get_data (size_var ))
896- size_data -= numpy .nanmin (size_data )
897- size_mask = numpy .isnan (size_data )
896+ nan_size = OWLinearProjection .MinPointSize - 2
897+ size_data , size_mask = self ._get_data (size_var , dtype = float )
898+ size_data_valid = size_data [~ size_mask ]
899+ if size_data_valid .size :
900+ smin , smax = numpy .min (size_data_valid ), numpy .max (size_data_valid )
901+ sspan = smax - smin
902+ else :
903+ sspan = smax = smin = 0
904+ size_data [~ size_mask ] -= smin
905+ if sspan > 0 :
906+ size_data [~ size_mask ] /= sspan
898907 size_data = \
899908 size_data * self .point_size + OWLinearProjection .MinPointSize
900- size_data [size_mask ] = OWLinearProjection . MinPointSize - 2
909+ size_data [size_mask ] = nan_size
901910 if mask is None :
902911 return size_data
903912 else :
@@ -1541,8 +1550,25 @@ def gestureEvent(self, event):
15411550 return False
15421551
15431552
1553+ def column_data (table , var , dtype ):
1554+ dtype = numpy .dtype (dtype )
1555+ col , copy = table .get_column_view (var )
1556+ if var .is_primitive () and not isinstance (col .dtype .type , numpy .inexact ):
1557+ # from mixes metas domain
1558+ col = col .astype (float )
1559+ copy = True
1560+ mask = numpy .isnan (col )
1561+ if dtype != col .dtype :
1562+ col = col .astype (dtype )
1563+ copy = True
1564+
1565+ if not copy :
1566+ col = col .copy ()
1567+ return col , mask
1568+
1569+
15441570class plotutils :
1545- @ staticmethod
1571+ @staticmethod
15461572 def continuous_colors (data , palette = None ,
15471573 low = (220 , 220 , 220 ), high = (0 ,0 ,0 ),
15481574 through_black = False ):
@@ -1552,14 +1578,7 @@ def continuous_colors(data, palette=None,
15521578 amin , amax = numpy .nanmin (data ), numpy .nanmax (data )
15531579 span = amax - amin
15541580 data = (data - amin ) / (span or 1 )
1555-
1556- mask = numpy .isnan (data )
1557- # Unknown values as gray
1558- # TODO: This should already be a part of palette
1559- colors = numpy .empty ((len (data ), 3 ))
1560- colors [mask ] = (128 , 128 , 128 )
1561- colors [~ mask ] = [palette .getRGB (v ) for v in data [~ mask ]]
1562- return colors
1581+ return palette .getRGB (data )
15631582
15641583 @staticmethod
15651584 def discrete_colors (data , nvalues , palette = None , color_index = None ):
@@ -1577,7 +1596,11 @@ def discrete_colors(data, nvalues, palette=None, color_index=None):
15771596
15781597 @staticmethod
15791598 def normalized (a ):
1599+ if not a .size :
1600+ return a .copy ()
15801601 amin , amax = numpy .nanmin (a ), numpy .nanmax (a )
1602+ if numpy .isnan (amin ):
1603+ return a .copy ()
15811604 span = amax - amin
15821605 mean = numpy .nanmean (a )
15831606 return (a - mean ) / (span or 1 )
0 commit comments