Skip to content

Commit 8405d7f

Browse files
committed
OWColor: Refactor to avoid setting Variable.name
1 parent 2127143 commit 8405d7f

2 files changed

Lines changed: 96 additions & 84 deletions

File tree

Orange/data/variable.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,13 +692,15 @@ def mapper(value, col_idx=None):
692692
def colors(self):
693693
if self._colors is not None:
694694
colors = np.array(self._colors)
695+
elif not self.values:
696+
colors = np.zeros((0, 3)) # to match additional colors in vstacks
695697
else:
696698
from Orange.widgets.utils.colorpalette import ColorPaletteGenerator
697699
default = tuple(ColorPaletteGenerator.palette(self))
698700
colors = self.attributes.get('colors', ())
699701
colors = tuple(hex_to_color(color) for color in colors) \
700702
+ default[len(colors):]
701-
colors = np.array(colors)
703+
colors = np.array(colors)
702704
colors.flags.writeable = False
703705
return colors
704706

Orange/widgets/data/owcolor.py

Lines changed: 93 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Widget for assigning colors to variables
33
"""
4+
from itertools import chain
45

56
import numpy as np
67
from AnyQt.QtCore import Qt, QSize, QAbstractTableModel
@@ -18,6 +19,23 @@
1819
ColorRole = 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
2240
class 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

Comments
 (0)