diff --git a/Orange/widgets/visualize/owmosaic.py b/Orange/widgets/visualize/owmosaic.py
index 8616d85c1e1..c6e176ac2c5 100644
--- a/Orange/widgets/visualize/owmosaic.py
+++ b/Orange/widgets/visualize/owmosaic.py
@@ -1,6 +1,6 @@
from collections import defaultdict
from functools import reduce
-from itertools import product, chain
+from itertools import product, chain, repeat
from math import sqrt, log
from operator import mul, attrgetter
@@ -9,7 +9,8 @@
from scipy.special import comb
from AnyQt.QtCore import Qt, QSize, pyqtSignal as Signal
from AnyQt.QtGui import QColor, QPainter, QPen, QStandardItem
-from AnyQt.QtWidgets import QGraphicsScene, QGraphicsLineItem
+from AnyQt.QtWidgets import (
+ QGraphicsScene, QGraphicsLineItem, QGraphicsItemGroup)
from Orange.data import Table, filter, Variable, Domain
from Orange.data.sql.table import SqlTable, LARGE_TABLE, DEFAULT_SAMPLE_TIME
@@ -28,6 +29,7 @@
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.visualize.utils import (
CanvasText, CanvasRectangle, ViewWithPress, VizRankDialog)
+from Orange.widgets.visualize.utils.plotutils import wrap_legend_items
from Orange.widgets.widget import OWWidget, Msg, Input, Output
@@ -813,42 +815,29 @@ def line(x1, y1, x2, y2):
"{}
Instances: {}
{}".format(
condition, n_actual, text[:-4]))
- def draw_legend(x0_x1, y0_y1):
- x0, x1 = x0_x1
- _, y1 = y0_y1
+ def create_legend():
if self.variable_color is None:
names = ["<-8", "-8:-4", "-4:-2", "-2:2", "2:4", "4:8", ">8",
"Residuals:"]
colors = self.RED_COLORS[::-1] + self.BLUE_COLORS[1:]
+ edges = repeat(Qt.black)
else:
- names = get_variable_values_sorted(class_var) + \
- [class_var.name + ":"]
- colors = [QColor(*col) for col in class_var.colors]
-
- names = [CanvasText(self.canvas, name, alignment=Qt.AlignVCenter)
- for name in names]
- totalwidth = sum(text.boundingRect().width() for text in names)
-
- # compute the x position of the center of the legend
- y = y1 + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35
- distance = 30
- startx = (x0 + x1) / 2 - (totalwidth + (len(names)) * distance) / 2
-
- names[-1].setPos(startx + 15, y)
- names[-1].show()
- xoffset = names[-1].boundingRect().width() + distance
+ names = get_variable_values_sorted(class_var)
+ edges = colors = [QColor(*col) for col in class_var.colors]
+ items = []
size = 8
- for i in range(len(names) - 1):
- if self.variable_color is None:
- edgecolor = Qt.black
- else:
- edgecolor = colors[i]
-
- CanvasRectangle(self.canvas, startx + xoffset, y - size / 2,
- size, size, edgecolor, colors[i])
- names[i].setPos(startx + xoffset + 10, y)
- xoffset += distance + names[i].boundingRect().width()
+ for name, color, edgecolor in zip(names, colors, edges):
+ item = QGraphicsItemGroup()
+ item.addToGroup(
+ CanvasRectangle(None, -size / 2, -size / 2, size, size,
+ edgecolor, color))
+ item.addToGroup(
+ CanvasText(None, name, size, 0, Qt.AlignVCenter))
+ items.append(item)
+ return wrap_legend_items(
+ items, hspacing=20, vspacing=16 + size,
+ max_width=self.canvas_view.width() - 2 * xoff)
self.canvas.clear()
self.areas = []
@@ -896,8 +885,10 @@ def get_max_label_width(attr):
maxw = max(int(t.boundingRect().width()), maxw)
return maxw
- # get the maximum width of rectangle
xoff = 20
+ legend = create_legend()
+
+ # get the maximum width of rectangle
width = 20
max_ylabel_w1 = max_ylabel_w2 = 0
if len(attr_list) > 1:
@@ -913,10 +904,11 @@ def get_max_label_width(attr):
self.ATTR_VAL_OFFSET + max_ylabel_w2 - 10
# get the maximum height of rectangle
- height = 100
yoff = 45
+ legendoff = yoff + self.ATTR_NAME_OFFSET + self.ATTR_VAL_OFFSET + 35
square_size = min(self.canvas_view.width() - width - 20,
- self.canvas_view.height() - height - 20)
+ self.canvas_view.height() - legendoff
+ - legend.boundingRect().height())
if square_size < 0:
return # canvas is too small to draw rectangles
@@ -937,7 +929,12 @@ def get_max_label_width(attr):
draw_data(
attr_list, (xoff, xoff + square_size), (yoff, yoff + square_size),
0, "", len(attr_list), [], [])
- draw_legend((xoff, xoff + square_size), (yoff, yoff + square_size))
+
+ self.canvas.addItem(legend)
+ legend.setPos(
+ xoff - legend.boundingRect().x()
+ + max(0, (square_size - legend.boundingRect().width()) / 2),
+ legendoff + square_size)
self.update_selection_rects()
@classmethod
diff --git a/Orange/widgets/visualize/tests/test_owmosaic.py b/Orange/widgets/visualize/tests/test_owmosaic.py
index cd7fab428d8..b9aaa08c50a 100644
--- a/Orange/widgets/visualize/tests/test_owmosaic.py
+++ b/Orange/widgets/visualize/tests/test_owmosaic.py
@@ -98,7 +98,8 @@ def assertCount(cb_color, cb_attr, areas):
assertCount(1, [0, 1, 1, 1], 0)
@patch('Orange.widgets.visualize.owmosaic.CanvasRectangle')
- def test_different_number_of_attributes(self, canvas_rectangle):
+ @patch('Orange.widgets.visualize.owmosaic.QGraphicsItemGroup.addToGroup')
+ def test_different_number_of_attributes(self, _, canvas_rectangle):
domain = Domain([DiscreteVariable(c, values="01") for c in "abcd"])
data = Table.from_list(
domain,
diff --git a/Orange/widgets/visualize/utils/plotutils.py b/Orange/widgets/visualize/utils/plotutils.py
index 106b9ccbf62..8b7384dfa9f 100644
--- a/Orange/widgets/visualize/utils/plotutils.py
+++ b/Orange/widgets/visualize/utils/plotutils.py
@@ -5,8 +5,8 @@
)
from AnyQt.QtGui import QTransform
from AnyQt.QtWidgets import (
- QGraphicsLineItem, QGraphicsSceneMouseEvent, QPinchGesture
-)
+ QGraphicsLineItem, QGraphicsSceneMouseEvent, QPinchGesture,
+ QGraphicsItemGroup)
import pyqtgraph as pg
@@ -348,3 +348,41 @@ def mouseDragEvent(self, ev, axis=None):
else:
self.moved.emit(self.item_id, pos.x(), pos.y())
self.graph.show_indicator(self.item_id)
+
+
+def wrap_legend_items(items, max_width, hspacing, vspacing):
+ def line_width(line):
+ return sum(item.boundingRect().width() for item in line) \
+ + hspacing * (len(line) - 1)
+
+ def create_line(line, yi, fixed_width=None):
+ x = 0
+ for item in line:
+ item.setPos(x, yi * vspacing)
+ paragraph.addToGroup(item)
+ if fixed_width:
+ x += fixed_width
+ else:
+ x += item.boundingRect().width() + hspacing
+
+ max_item = max(item.boundingRect().width() + hspacing for item in items)
+ in_line = int(max_width // max_item)
+ if line_width(items) < max_width: # single line
+ lines = [items]
+ fixed_width = None
+ elif in_line < 2:
+ lines = [[]]
+ for i, item in enumerate(items): # just a single column - free wrap
+ lines[-1].append(item)
+ if line_width(lines[-1]) > max_width and len(lines[-1]) > 1:
+ lines.append([lines[-1].pop()])
+ fixed_width = None
+ else: # arrange into grid
+ lines = [items[i:i + in_line]
+ for i in range(0, len(items) + in_line - 1, in_line)]
+ fixed_width = max_item
+
+ paragraph = QGraphicsItemGroup()
+ for yi, line in enumerate(lines):
+ create_line(line, yi, fixed_width=fixed_width)
+ return paragraph