11"""
22Widget for assigning colors to variables
33"""
4+ from itertools import chain
45
56import numpy as np
67from AnyQt .QtCore import Qt , QSize , QAbstractTableModel
1819ColorRole = next (gui .OrangeUserRole )
1920
2021
22+ class AttrDesc :
23+ def __init__ (self , var , name = None , colors = None , values = None ):
24+ self .var = var
25+ self .name = name
26+ self .colors = colors
27+ self .values = values
28+
29+ def get_name (self ):
30+ return self .name or self .var .name
31+
32+ def get_colors (self ):
33+ return self .colors or self .var .colors
34+
35+ def get_values (self ):
36+ return self .values or self .var .values
37+
38+
2139# noinspection PyMethodOverriding
2240class ColorTableModel (QAbstractTableModel ):
2341 """Base color model for discrete and continuous attributes. The model
@@ -55,7 +73,7 @@ def data(self, index, role=Qt.DisplayRole):
5573 # Only valid for the first column
5674 row = index .row ()
5775 if role in (Qt .DisplayRole , Qt .EditRole ):
58- return self .variables [row ].name
76+ return self .variables [row ].get_name ()
5977 if role == Qt .FontRole :
6078 font = QFont ()
6179 font .setBold (True )
@@ -83,39 +101,41 @@ class DiscColorTableModel(ColorTableModel):
83101 # pylint: disable=missing-docstring
84102 def n_columns (self ):
85103 return bool (self .variables ) and \
86- 1 + max (len (var .values ) for var in self .variables )
104+ 1 + max (len (row . var .values ) for row in self .variables )
87105
88106 def data (self , index , role = Qt .DisplayRole ):
89107 # pylint: disable=too-many-return-statements
90108 row , col = index .row (), index .column ()
91109 if col == 0 :
92110 return ColorTableModel .data (self , index , role )
93- var = self .variables [row ]
94- if col > len (var .values ):
111+ desc = self .variables [row ]
112+ if col > len (desc . var .values ):
95113 return None
96114 if role in (Qt .DisplayRole , Qt .EditRole ):
97- return var .values [col - 1 ]
98- try :
99- color = var .colors [col - 1 ]
100- except (AttributeError , IndexError ):
101- return None
115+ return desc .get_values ()[col - 1 ]
116+ color = desc .get_colors ()[col - 1 ]
102117 if role == Qt .DecorationRole :
103118 return QColor (* color )
104119 if role == Qt .ToolTipRole :
105120 return self ._encode_color (color )
106121 if role == ColorRole :
107- return var . colors [ col - 1 ]
122+ return color
108123 return None
109124
110125 # noinspection PyMethodOverriding
111126 def setData (self , index , value , role ):
112127 row , col = index .row (), index .column ()
113128 if col == 0 :
114129 return ColorTableModel .setData (self , index , value , role )
130+ desc = self .variables [row ]
115131 if role == ColorRole :
116- self .variables [row ].set_color (col - 1 , value [:3 ])
132+ if not desc .colors :
133+ desc .colors = desc .var .colors .tolist ()
134+ desc .colors [col - 1 ] = value [:3 ]
117135 elif role == Qt .EditRole :
118- self .variables [row ].values [col - 1 ] = value
136+ if not desc .values :
137+ desc .values = list (desc .var .values )
138+ desc .values [col - 1 ] = value
119139 else :
120140 return False
121141 self .dataChanged .emit (index , index )
@@ -139,7 +159,8 @@ def _column0():
139159
140160 def _column1 ():
141161 if role == Qt .DecorationRole :
142- continuous_palette = ContinuousPaletteGenerator (* var .colors )
162+ continuous_palette = \
163+ ContinuousPaletteGenerator (* desc .get_colors ())
143164 line = continuous_palette .getRGB (np .arange (0 , 1 , 1 / 256 ))
144165 data = np .arange (0 , 256 , dtype = np .int8 ). \
145166 reshape ((1 , 256 )). \
@@ -150,10 +171,11 @@ def _column1():
150171 img .data = data
151172 return img
152173 if role == Qt .ToolTipRole :
153- return "{} - {}" .format (self ._encode_color (var .colors [0 ]),
154- self ._encode_color (var .colors [1 ]))
174+ colors = desc .get_colors ()
175+ return f"{ self ._encode_color (colors [0 ])} " \
176+ f"- { self ._encode_color (colors [1 ])} "
155177 if role == ColorRole :
156- return var . colors
178+ return desc . get_colors ()
157179 return None
158180
159181 def _column2 ():
@@ -166,7 +188,7 @@ def _column2():
166188 return None
167189
168190 row , col = index .row (), index .column ()
169- var = self .variables [row ]
191+ desc = self .variables [row ]
170192 if 0 <= col <= 2 :
171193 return [_column0 , _column1 , _column2 ][col ]()
172194
@@ -183,9 +205,9 @@ def setData(self, index, value, role):
183205 return True
184206
185207 def copy_to_all (self , index ):
186- colors = self .variables [index .row ()].colors
187- for row in range ( self .n_rows ()) :
188- self . variables [ row ] .colors = colors
208+ colors = self .variables [index .row ()].get_colors ()
209+ for desc in self .variables :
210+ desc .colors = colors
189211 self .dataChanged .emit (self .index (0 , 1 ), self .index (self .n_rows (), 1 ))
190212
191213
@@ -296,8 +318,8 @@ class Outputs:
296318
297319 settingsHandler = settings .PerfectDomainContextHandler (
298320 match_values = settings .PerfectDomainContextHandler .MATCH_VALUES_ALL )
299- disc_data = settings .ContextSetting ([])
300- cont_data = settings .ContextSetting ([])
321+ disc_colors = settings .ContextSetting ([])
322+ cont_colors = settings .ContextSetting ([])
301323 color_settings = settings .Setting (None )
302324 selected_schema_index = settings .Setting (0 )
303325 auto_apply = settings .Setting (True )
@@ -308,8 +330,8 @@ def __init__(self):
308330 super ().__init__ ()
309331 self .data = None
310332 self .orig_domain = self .domain = None
311- self .disc_colors = []
312- self .cont_colors = []
333+ self .disc_dict = {}
334+ self .cont_dict = {}
313335
314336 box = gui .hBox (self .controlArea , "Discrete Variables" )
315337 self .disc_model = DiscColorTableModel ()
@@ -334,19 +356,6 @@ def __init__(self):
334356 def sizeHint ():
335357 return QSize (500 , 570 )
336358
337- def _create_proxies (self , variables ):
338- part_vars = []
339- for var in variables :
340- if var .is_discrete or var .is_continuous :
341- var = var .make_proxy ()
342- if var .is_discrete :
343- var .values = var .values [:]
344- self .disc_colors .append (var )
345- else :
346- self .cont_colors .append (var )
347- part_vars .append (var )
348- return part_vars
349-
350359 @Inputs .data
351360 def set_data (self , data ):
352361 """Handle data input signal"""
@@ -356,53 +365,54 @@ def set_data(self, data):
356365 if data is None :
357366 self .data = self .domain = None
358367 else :
359- domain = self .orig_domain = data .domain
360- domain = Orange .data .Domain (self ._create_proxies (domain .attributes ),
361- self ._create_proxies (domain .class_vars ),
362- self ._create_proxies (domain .metas ))
363- self .openContext (data )
364- self .data = data .transform (domain )
368+ self .data = data
369+ for var in chain (data .domain .variables , data .domain .metas ):
370+ if var .is_discrete :
371+ self .disc_colors .append (AttrDesc (var ))
372+ elif var .is_continuous :
373+ self .cont_colors .append (AttrDesc (var ))
374+
365375 self .disc_model .set_data (self .disc_colors )
366376 self .cont_model .set_data (self .cont_colors )
367377 self .disc_view .resizeColumnsToContents ()
368378 self .cont_view .resizeColumnsToContents ()
379+ self .openContext (data )
380+ self .disc_dict = {k .var .name : k for k in self .disc_colors }
381+ self .cont_dict = {k .var .name : k for k in self .cont_colors }
369382 self .unconditional_commit ()
370383
371- def storeSpecificSettings (self ):
372- # Store the colors that were changed -- but not others
373- self .current_context .disc_data = \
374- [(var .name , var .values , "colors" in var .attributes and var .colors )
375- for var in self .disc_colors ]
376- self .current_context .cont_data = \
377- [(var .name , "colors" in var .attributes and var .colors )
378- for var in self .cont_colors ]
379-
380- def retrieveSpecificSettings (self ):
381- disc_data = getattr (self .current_context , "disc_data" , ())
382- for var , (name , values , colors ) in zip (self .disc_colors , disc_data ):
383- var .name = name
384- var .values = values [:]
385- if colors is not False :
386- var .colors = colors
387- cont_data = getattr (self .current_context , "cont_data" , ())
388- for var , (name , colors ) in zip (self .cont_colors , cont_data ):
389- var .name = name
390- if colors is not False :
391- var .colors = colors
392-
393384 def _on_data_changed (self , * args ):
394385 self .commit ()
395386
396387 def commit (self ):
397- self .Outputs .data .send (self .data )
388+ def make (vars ):
389+ new_vars = []
390+ for var in vars :
391+ source = self .disc_dict if var .is_discrete else self .cont_dict
392+ desc = source .get (var .name )
393+ if desc :
394+ name = desc .get_name ()
395+ if var .is_discrete :
396+ var = var .copy (name = name , values = desc .get_values ())
397+ else :
398+ var = var .copy (name = name )
399+ var .colors = desc .colors
400+ new_vars .append (var )
401+ return new_vars
402+
403+ dom = self .data .domain
404+ new_domain = Orange .data .Domain (
405+ make (dom .attributes ), make (dom .class_vars ), make (dom .metas ))
406+ new_data = self .data .transform (new_domain )
407+ self .Outputs .data .send (new_data )
398408
399409 def send_report (self ):
400410 """Send report"""
401- def _report_variables (variables , orig_variables ):
411+ def _report_variables (variables ):
402412 from Orange .widgets .report import colored_square as square
403413
404414 def was (n , o ):
405- return n if n == o else "{ } (was: {})". format ( n , o )
415+ return n if n == o else f" { n } (was: { o } )"
406416
407417 # definition of td element for continuous gradient
408418 # with support for pre-standard css (needed at least for Qt 4.8)
@@ -420,36 +430,36 @@ def was(n, o):
420430 '"></span></td>'
421431
422432 rows = ""
423- for var , ovar in zip ( variables , orig_variables ) :
433+ for var in variables :
424434 if var .is_discrete :
435+ desc = self .disc_dict [var .name ]
425436 values = " \n " .join (
426437 "<td>{} {}</td>" .
427- format (square (* var . colors [ i ] ), was (value , ovalue ))
428- for i , ( value , ovalue ) in
429- enumerate ( zip (var . values , ovar . values ) ))
438+ format (square (* color ), was (value , old_value ))
439+ for color , value , old_value in
440+ zip (desc . get_colors (), desc . get_values (), var . values ))
430441 elif var .is_continuous :
431- col = var .colors
442+ desc = self .cont_dict [var .name ]
443+ col = desc .get_colors ()
432444 colors = col [0 ][:3 ] + ("black, " * col [2 ], ) + col [1 ][:3 ]
433445 values = cont_tpl .format (* colors * len (defs ))
434446 else :
435447 continue
436- name = was (var . name , ovar .name )
448+ names = was (desc . get_name (), desc . var .name )
437449 rows += '<tr style="height: 2em">\n ' \
438450 ' <th style="text-align: right">{}</th>{}\n </tr>\n ' . \
439- format (name , values )
451+ format (names , values )
440452 return rows
441453
442454 if not self .data :
443455 return
444- domain = self .data .domain
445- orig_domain = self .orig_domain
456+ dom = self .data .domain
446457 sections = (
447- (name , _report_variables (vars , ovars ))
448- for name , vars , ovars in (
449- ("Features" , domain .attributes , orig_domain .attributes ),
450- ("Outcome" + "s" * (len (domain .class_vars ) > 1 ),
451- domain .class_vars , orig_domain .class_vars ),
452- ("Meta attributes" , domain .metas , orig_domain .metas )))
458+ (name , _report_variables (vars ))
459+ for name , vars in (
460+ ("Features" , dom .attributes ),
461+ ("Outcome" + "s" * (len (dom .class_vars ) > 1 ), dom .class_vars ),
462+ ("Meta attributes" , dom .metas )))
453463 table = "" .join ("<tr><th>{}</th></tr>{}" .format (name , rows )
454464 for name , rows in sections if rows )
455465 if table :
0 commit comments