Skip to content

Commit d672ab7

Browse files
authored
Merge pull request #3809 from robertcv/fix/radviz_labels
[FIX] OWRadviz: legible axis labels
2 parents 23703a6 + cd1d87c commit d672ab7

File tree

2 files changed

+82
-26
lines changed

2 files changed

+82
-26
lines changed

Orange/widgets/utils/plot/owplotgui.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,20 @@ class VariableSelectionModel(VariableListModel):
5858
SortRole = next(OrangeUserRole)
5959
selection_changed = pyqtSignal()
6060

61-
def __init__(self, selected_vars):
61+
def __init__(self, selected_vars, max_vars=None):
6262
super().__init__(enable_dnd=True)
6363
self.selected_vars = selected_vars
64+
self.max_vars = max_vars
6465

6566
def is_selected(self, index):
6667
return self[index.row()] in self.selected_vars
6768

69+
def is_full(self):
70+
if self.max_vars is None:
71+
return False
72+
else:
73+
return len(self.selected_vars) >= self.max_vars
74+
6875
def data(self, index, role):
6976
if role == self.IsSelected:
7077
return self.is_selected(index)
@@ -84,7 +91,7 @@ def toggle_item(self, index):
8491
var = self[index.row()]
8592
if var in self.selected_vars:
8693
self.selected_vars.remove(var)
87-
else:
94+
elif not self.is_full():
8895
self.selected_vars.append(var)
8996
self.selection_changed.emit()
9097

@@ -131,17 +138,19 @@ def paint(self, painter, option, index):
131138
rect = QRect(option.rect)
132139

133140
is_selected = index.data(VariableSelectionModel.IsSelected)
141+
full_selection = index.model().sourceModel().is_full()
134142
if option.state & QStyle.State_MouseOver:
135-
txt = [" Add ", " Remove "][is_selected]
136-
txtw = painter.fontMetrics().width(txt)
137-
painter.save()
138-
painter.setPen(Qt.NoPen)
139-
painter.setBrush(QColor("#ccc"))
140-
brect = QRect(rect.x() + rect.width() - 8 - txtw, rect.y(),
141-
txtw, rect.height())
142-
painter.drawRoundedRect(brect, 4, 4)
143-
painter.restore()
144-
painter.drawText(brect, Qt.AlignCenter, txt)
143+
if not full_selection or (full_selection and is_selected):
144+
txt = [" Add ", " Remove "][is_selected]
145+
txtw = painter.fontMetrics().width(txt)
146+
painter.save()
147+
painter.setPen(Qt.NoPen)
148+
painter.setBrush(QColor("#ccc"))
149+
brect = QRect(rect.x() + rect.width() - 8 - txtw, rect.y(),
150+
txtw, rect.height())
151+
painter.drawRoundedRect(brect, 4, 4)
152+
painter.restore()
153+
painter.drawText(brect, Qt.AlignCenter, txt)
145154

146155
painter.save()
147156
double_pen = painter.pen()

Orange/widgets/visualize/owradviz.py

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,17 +205,21 @@ def stopped(self):
205205
self.n_attrs_spin.setDisabled(False)
206206

207207

208+
MAX_LABEL_LEN = 16
209+
210+
208211
class OWRadvizGraph(OWGraphWithAnchors):
209212
def __init__(self, scatter_widget, parent):
210213
super().__init__(scatter_widget, parent)
211214
self.anchors_scatter_item = None
215+
self.padding = 0.025
212216

213217
def clear(self):
214218
super().clear()
215219
self.anchors_scatter_item = None
216220

217221
def set_view_box_range(self):
218-
self.view_box.setRange(QRectF(-1.2, -1.05, 2.4, 2.1), padding=0.025)
222+
self.view_box.setRange(QRectF(-1, -1, 2, 2), padding=self.padding)
219223

220224
def closest_draggable_item(self, pos):
221225
points, _ = self.master.get_anchors()
@@ -231,18 +235,55 @@ def update_anchors(self):
231235
points, labels = self.master.get_anchors()
232236
if points is None:
233237
return
234-
if self.anchor_items is None:
235-
self.anchor_items = []
236-
for point, label in zip(points, labels):
237-
anchor = TextItem()
238-
anchor.setText(label)
239-
anchor.setColor(QColor(0, 0, 0))
240-
anchor.setPos(*point)
241-
self.plot_widget.addItem(anchor)
242-
self.anchor_items.append(anchor)
243-
else:
244-
for anchor, point in zip(self.anchor_items, points):
245-
anchor.setPos(*point)
238+
if self.anchor_items is not None:
239+
for anchor in self.anchor_items:
240+
self.plot_widget.removeItem(anchor)
241+
242+
self.anchor_items = []
243+
label_len = 1
244+
for point, label in zip(points, labels):
245+
anchor = TextItem()
246+
anchor.textItem.setToolTip(f"<b>{label}</b>")
247+
248+
if len(label) > MAX_LABEL_LEN:
249+
i = label.rfind(" ", 0, MAX_LABEL_LEN)
250+
if i != -1:
251+
first_row = label[:i] + "\n"
252+
second_row = label[i + 1:]
253+
if len(second_row) > MAX_LABEL_LEN:
254+
j = second_row.rfind(" ", 0, MAX_LABEL_LEN)
255+
if j != -1:
256+
second_row = second_row[:j + 1] + "..."
257+
else:
258+
second_row = second_row[:MAX_LABEL_LEN - 3] + "..."
259+
label = first_row + second_row
260+
else:
261+
label = label[:MAX_LABEL_LEN - 3] + "..."
262+
263+
anchor.setText(label)
264+
label_len = min(MAX_LABEL_LEN, len(label))
265+
anchor.setColor(QColor(0, 0, 0))
266+
267+
x, y = point
268+
angle = np.rad2deg(np.arctan2(y, x))
269+
anchor.setPos(x * 1.025, y * 1.025)
270+
271+
if abs(angle) < 90:
272+
anchor.setAngle(angle)
273+
anchor.setAnchor((0, 0.5))
274+
else:
275+
anchor.setAngle(angle + 180)
276+
anchor.setAnchor((1, 0.5))
277+
278+
anchor.textItem.setTextWidth(anchor.textItem.boundingRect().width())
279+
option = anchor.textItem.document().defaultTextOption()
280+
option.setAlignment(Qt.AlignRight)
281+
anchor.textItem.document().setDefaultTextOption(option)
282+
283+
self.plot_widget.addItem(anchor)
284+
self.anchor_items.append(anchor)
285+
286+
self.padding = label_len * 0.0175
246287
self._update_anchors_scatter_item(points)
247288

248289
def _update_anchors_scatter_item(self, points):
@@ -282,9 +323,11 @@ class Warning(OWAnchorProjectionWidget.Warning):
282323
invalid_embedding = widget.Msg("No projection for selected features")
283324
removed_vars = widget.Msg("Categorical variables with more than"
284325
" two values are not shown.")
326+
max_vars_selected = widget.Msg("Maximum number of selected variables reached.")
285327

286328
def _add_controls(self):
287-
self.model_selected = VariableSelectionModel(self.selected_vars)
329+
self.model_selected = VariableSelectionModel(self.selected_vars,
330+
max_vars=20)
288331
variables_selection(self.controlArea, self, self.model_selected)
289332
self.model_selected.selection_changed.connect(
290333
self.__model_selected_changed)
@@ -316,6 +359,10 @@ def vizrank_set_attrs(self, *attrs):
316359
self.model_selected.selection_changed.emit()
317360

318361
def __model_selected_changed(self):
362+
if self.model_selected.is_full():
363+
self.Warning.max_vars_selected()
364+
else:
365+
self.Warning.max_vars_selected.clear()
319366
self.init_projection()
320367
self.setup_plot()
321368
self.commit()

0 commit comments

Comments
 (0)