Skip to content

Commit d8f6cf6

Browse files
committed
OWMosaic: Wrap legend
1 parent 102c0de commit d8f6cf6

File tree

3 files changed

+74
-38
lines changed

3 files changed

+74
-38
lines changed

Orange/widgets/visualize/owmosaic.py

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from collections import defaultdict
22
from functools import reduce
3-
from itertools import product, chain
3+
from itertools import product, chain, repeat
44
from math import sqrt, log
55
from operator import mul, attrgetter
66

@@ -9,7 +9,8 @@
99
from scipy.special import comb
1010
from AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal
1111
from AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem
12-
from AnyQt.QtWidgets import QGraphicsScene, QGraphicsLineItem
12+
from AnyQt.QtWidgets import (
13+
QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)
1314

1415
from Orange.data import Table, filter, Variable, Domain
1516
from Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME
@@ -28,6 +29,7 @@
2829
from Orange.widgets.utils.widgetpreview import WidgetPreview
2930
from Orange.widgets.visualize.utils import (
3031
CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)
32+
from Orange.widgets.visualize.utils.plotutils import wrap_legend_items
3133
from Orange.widgets.widget import OWWidget, Msg, Input, Output
3234

3335

@@ -813,42 +815,29 @@ def line(x1, y1, x2, y2):
813815
"{}<hr>Instances: {}<br><br>{}".format(
814816
condition, n_actual, text[:-4]))
815817

816-
def draw_legend(x0_x1, y0_y1):
817-
x0, x1 = x0_x1
818-
_, y1 = y0_y1
818+
def create_legend():
819819
if self.variable_color is None:
820820
names = ["<-8", "-8:-4", "-4:-2", "-2:2", "2:4", "4:8", ">8",
821821
"Residuals:"]
822822
colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]
823+
edges = repeat(Qt.black)
823824
else:
824-
names = get_variable_values_sorted(class_var) + \
825-
[class_var.name + ":"]
826-
colors = [QColor(*col) for col in class_var.colors]
827-
828-
names = [CanvasText(self.canvas, name, alignment=Qt.AlignVCenter)
829-
for name in names]
830-
totalwidth = sum(text.boundingRect().width() for text in names)
831-
832-
# compute the x position of the center of the legend
833-
y = y1 + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35
834-
distance = 30
835-
startx = (x0 + x1) / 2 - (totalwidth + (len(names)) * distance) / 2
836-
837-
names[-1].setPos(startx + 15, y)
838-
names[-1].show()
839-
xoffset = names[-1].boundingRect().width() + distance
825+
names = get_variable_values_sorted(class_var)
826+
edges = colors = [QColor(*col) for col in class_var.colors]
840827

828+
items = []
841829
size = 8
842-
for i in range(len(names) - 1):
843-
if self.variable_color is None:
844-
edgecolor = Qt.black
845-
else:
846-
edgecolor = colors[i]
847-
848-
CanvasRectangle(self.canvas, startx + xoffset, y - size / 2,
849-
size, size, edgecolor, colors[i])
850-
names[i].setPos(startx + xoffset + 10, y)
851-
xoffset += distance + names[i].boundingRect().width()
830+
for name, color, edgecolor in zip(names, colors, edges):
831+
item = QGraphicsItemGroup()
832+
item.addToGroup(
833+
CanvasRectangle(None, -size / 2, -size / 2, size, size,
834+
edgecolor, color))
835+
item.addToGroup(
836+
CanvasText(None, name, size, 0, Qt.AlignVCenter))
837+
items.append(item)
838+
return wrap_legend_items(
839+
items, hspacing=20, vspacing=16 + size,
840+
max_width=self.canvas_view.width() - 2 * xoff)
852841

853842
self.canvas.clear()
854843
self.areas = []
@@ -896,8 +885,10 @@ def get_max_label_width(attr):
896885
maxw = max(int(t.boundingRect().width()), maxw)
897886
return maxw
898887

899-
# get the maximum width of rectangle
900888
xoff = 20
889+
legend = create_legend()
890+
891+
# get the maximum width of rectangle
901892
width = 20
902893
max_ylabel_w1 = max_ylabel_w2 = 0
903894
if len(attr_list) > 1:
@@ -913,10 +904,11 @@ def get_max_label_width(attr):
913904
self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10
914905

915906
# get the maximum height of rectangle
916-
height = 100
917907
yoff = 45
908+
legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35
918909
square_size = min(self.canvas_view.width() - width - 20,
919-
self.canvas_view.height() - height - 20)
910+
self.canvas_view.height() - legendoff
911+
- legend.boundingRect().height())
920912

921913
if square_size < 0:
922914
return # canvas is too small to draw rectangles
@@ -937,7 +929,12 @@ def get_max_label_width(attr):
937929
draw_data(
938930
attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),
939931
0, "", len(attr_list), [], [])
940-
draw_legend((xoff, xoff + square_size), (yoff, yoff + square_size))
932+
933+
self.canvas.addItem(legend)
934+
legend.setPos(
935+
xoff - legend.boundingRect().x()
936+
+ max(0, (square_size - legend.boundingRect().width()) / 2),
937+
legendoff + square_size)
941938
self.update_selection_rects()
942939

943940
@classmethod

Orange/widgets/visualize/tests/test_owmosaic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ def assertCount(cb_color, cb_attr, areas):
9898
assertCount(1, [0, 1, 1, 1], 0)
9999

100100
@patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')
101-
def test_different_number_of_attributes(self, canvas_rectangle):
101+
@patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')
102+
def test_different_number_of_attributes(self, _, canvas_rectangle):
102103
domain = Domain([DiscreteVariable(c, values="01") for c in "abcd"])
103104
data = Table.from_list(
104105
domain,

Orange/widgets/visualize/utils/plotutils.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
)
66
from AnyQt.QtGui import QTransform
77
from AnyQt.QtWidgets import (
8-
QGraphicsLineItem, QGraphicsSceneMouseEvent, QPinchGesture
9-
)
8+
QGraphicsLineItem, QGraphicsSceneMouseEvent, QPinchGesture,
9+
QGraphicsItemGroup)
1010

1111
import pyqtgraph as pg
1212

@@ -348,3 +348,41 @@ def mouseDragEvent(self, ev, axis=None):
348348
else:
349349
self.moved.emit(self.item_id, pos.x(), pos.y())
350350
self.graph.show_indicator(self.item_id)
351+
352+
353+
def wrap_legend_items(items, max_width, hspacing, vspacing):
354+
def line_width(line):
355+
return sum(item.boundingRect().width() for item in line) \
356+
+ hspacing * (len(line) - 1)
357+
358+
def create_line(line, yi, fixed_width=None):
359+
x = 0
360+
for item in line:
361+
item.setPos(x, yi * vspacing)
362+
paragraph.addToGroup(item)
363+
if fixed_width:
364+
x += fixed_width
365+
else:
366+
x += item.boundingRect().width() + hspacing
367+
368+
max_item = max(item.boundingRect().width() + hspacing for item in items)
369+
in_line = int(max_width // max_item)
370+
if line_width(items) < max_width: # single line
371+
lines = [items]
372+
fixed_width = None
373+
elif in_line < 2:
374+
lines = [[]]
375+
for i, item in enumerate(items): # just a single column - free wrap
376+
lines[-1].append(item)
377+
if line_width(lines[-1]) > max_width and len(lines[-1]) > 1:
378+
lines.append([lines[-1].pop()])
379+
fixed_width = None
380+
else: # arrange into grid
381+
lines = [items[i:i + in_line]
382+
for i in range(0, len(items) + in_line - 1, in_line)]
383+
fixed_width = max_item
384+
385+
paragraph = QGraphicsItemGroup()
386+
for yi, line in enumerate(lines):
387+
create_line(line, yi, fixed_width=fixed_width)
388+
return paragraph

0 commit comments

Comments
 (0)