def _rescale(self): if self._root is None: return crect = self.contentsRect() leaf_count = len(list(leaves(self._root))) if self.orientation in [Left, Right]: drect = QSizeF(self._root.value.height, leaf_count - 1) else: drect = QSizeF(self._root.value.last - 1, self._root.value.height) transform = QTransform().scale(crect.width() / drect.width(), crect.height() / drect.height()) self._itemgroup.setPos(crect.topLeft()) self._itemgroup.setTransform(transform) self._selection_items = None self._update_selection_items()
def _rescale(self): if self._root is None: return crect = self.contentsRect() leaf_count = len(list(leaves(self._root))) if self.orientation in [Left, Right]: drect = QSizeF(self._root.value.height, leaf_count - 1) else: drect = QSizeF(self._root.value.last - 1, self._root.value.height) transform = QTransform().scale( crect.width() / drect.width(), crect.height() / drect.height() ) self._itemgroup.setPos(crect.topLeft()) self._itemgroup.setTransform(transform) self._selection_items = None self._update_selection_items()
class OWLegend(QGraphicsObject): """ A legend for :obj:`.OWPlot`. Its items are arranged into a hierarchy by `category`. This is useful when points differ in more than one attribute. In such a case, there can be one category for point color and one for point shape. Usually the category name will be the name of the attribute, while the item's title will be the value. Arbitrary categories can be created, for an example see :meth:`.OWPlot.update_axes`, which creates a special category for unused axes. decimals .. image:: files/legend-categories.png In the image above, `type` and `milk` are categories with 7 and 2 possible values, respectively. """ def __init__(self, graph, scene): QGraphicsObject.__init__(self) if scene: scene.addItem(self) self.graph = graph self.curves = [] self.items = {} self.attributes = [] self.point_attrs = {} self.point_vals = {} self.default_values = { PointColor : Qt.black, PointSize : 8, PointSymbol : OWPoint.Ellipse } self.box_rect = QRectF() self.setFiltersChildEvents(True) self.setFlag(self.ItemHasNoContents, True) self.mouse_down = False self._orientation = Qt.Vertical self.max_size = QSizeF() self._floating = True self._floating_animation = None self._mouse_down_pos = QPointF() def clear(self): """ Removes all items from the legend """ for lst in self.items.values(): for i in lst: i.setParentItem(None) if self.scene(): self.scene().removeItem(i) self.items = {} self.update_items() def add_curve(self, curve): """ Adds a legend item with the same point symbol and name as ``curve``. If the curve's name contains the equal sign (=), it is split at that sign. The first part of the curve is a used as the category, and the second part as the value. """ i = curve.name.find('=') if i == -1: cat = '' name = curve.name else: cat = curve.name[:i] name = curve.name[i+1:] self.add_item(cat, name, curve.point_item(0, 0, 0)) def add_item(self, category, value, point): """ Adds an item with title ``value`` and point symbol ``point`` to the specified ``category``. """ if category not in self.items: self.items[category] = [OWLegendTitle(category, self)] self.items[category].append(OWLegendItem(str(value), point, self)) self.update_items() def add_color_gradient(self, title, values): if len(values) < 2: # No point in showing a gradient with less that two values return if title in self.items: self.remove_category(title) item = OWLegendGradient(self.graph.contPalette, [str(v) for v in values], self) self.items[title] = [OWLegendTitle(title, self), item] self.update_items() def remove_category(self, category): """ Removes ``category`` and all items that belong to it. """ if category not in self.items: return if self.scene(): for item in self.items[category]: self.scene().removeItem(item) del self.items[category] def update_items(self): """ Updates the legend, repositioning the items according to the legend's orientation. """ self.box_rect = QRectF() x = y = 0 for lst in self.items.values(): for item in lst: if hasattr(item, 'text_item'): item.text_item.setDefaultTextColor(self.graph.color(OWPalette.Text)) if hasattr(item, 'rect_item'): item.rect_item.setBrush(self.graph.color(OWPalette.Canvas)) if hasattr(item, 'set_orientation'): item.set_orientation(self._orientation) if self._orientation == Qt.Vertical: for lst in self.items.values(): for item in lst: if self.max_size.height() and y and y + item.boundingRect().height() > self.max_size.height(): y = 0 x = x + item.boundingRect().width() self.box_rect = self.box_rect | item.boundingRect().translated(x, y) move_item_xy(item, x, y, self.graph.animate_plot) y = y + item.boundingRect().height() elif self._orientation == Qt.Horizontal: for lst in self.items.values(): max_h = max(item.boundingRect().height() for item in lst) for item in lst: if self.max_size.width() and x and x + item.boundingRect().width() > self.max_size.width(): x = 0 y = y + max_h self.box_rect = self.box_rect | item.boundingRect().translated(x, y) move_item_xy(item, x, y, self.graph.animate_plot) x = x + item.boundingRect().width() if lst: x = 0 y = y + max_h def mouseMoveEvent(self, event): self.graph.notify_legend_moved(event.scenePos()) if self._floating: p = event.scenePos() - self._mouse_down_pos if self._floating_animation and self._floating_animation.state() == QPropertyAnimation.Running: self.set_pos_animated(p) else: self.setPos(p) event.accept() def mousePressEvent(self, event): self.setCursor(Qt.ClosedHandCursor) self.mouse_down = True self._mouse_down_pos = event.scenePos() - self.pos() event.accept() def mouseReleaseEvent(self, event): self.unsetCursor() self.mouse_down = False self._mouse_down_pos = QPointF() event.accept() def boundingRect(self): return self.box_rect def paint(self, painter, option, widget=None): pass def set_orientation(self, orientation): """ Sets the legend's orientation to ``orientation``. """ self._orientation = orientation self.update_items() def orientation(self): return self._orientation def set_pos_animated(self, pos): if (self.pos() - pos).manhattanLength() < 6 or not self.graph.animate_plot: self.setPos(pos) else: t = 250 if self._floating_animation and self._floating_animation.state() == QPropertyAnimation.Running: t = t - self._floating_animation.currentTime() self._floating_animation.stop() self._floating_animation = QPropertyAnimation(self, 'pos') self._floating_animation.setEndValue(pos) self._floating_animation.setDuration(t) self._floating_animation.start(QPropertyAnimation.KeepWhenStopped) def set_floating(self, floating, pos=None): """ If floating is ``True``, the legend can be dragged with the mouse. Otherwise, it's fixed in its position. If ``pos`` is specified, the legend is moved there. """ if floating == self._floating: return self._floating = floating if pos: if floating: self.set_pos_animated(pos - self._mouse_down_pos) else: self.set_pos_animated(pos)
class OWLegend(QGraphicsObject): """ A legend for :obj:`.OWPlot`. Its items are arranged into a hierarchy by `category`. This is useful when points differ in more than one attribute. In such a case, there can be one category for point color and one for point shape. Usually the category name will be the name of the attribute, while the item's title will be the value. Arbitrary categories can be created, for an example see :meth:`.OWPlot.update_axes`, which creates a special category for unused axes. decimals .. image:: files/legend-categories.png In the image above, `type` and `milk` are categories with 7 and 2 possible values, respectively. """ def __init__(self, graph, scene): QGraphicsObject.__init__(self) if scene: scene.addItem(self) self.graph = graph self.curves = [] self.items = {} self.attributes = [] self.point_attrs = {} self.point_vals = {} self.default_values = { PointColor: Qt.black, PointSize: 8, PointSymbol: OWPoint.Ellipse } self.box_rect = QRectF() self.setFiltersChildEvents(True) self.setFlag(self.ItemHasNoContents, True) self.mouse_down = False self._orientation = Qt.Vertical self.max_size = QSizeF() self._floating = True self._floating_animation = None self._mouse_down_pos = QPointF() def clear(self): """ Removes all items from the legend """ for lst in self.items.itervalues(): for i in lst: i.setParentItem(None) if self.scene(): self.scene().removeItem(i) self.items = {} self.update_items() def add_curve(self, curve): """ Adds a legend item with the same point symbol and name as ``curve``. If the curve's name contains the equal sign (=), it is split at that sign. The first part of the curve is a used as the category, and the second part as the value. """ i = curve.name.find('=') if i == -1: cat = '' name = curve.name else: cat = curve.name[:i] name = curve.name[i + 1:] self.add_item(cat, name, curve.point_item(0, 0, 0)) def add_item(self, category, value, point): """ Adds an item with title ``value`` and point symbol ``point`` to the specified ``category``. """ if category not in self.items: self.items[category] = [OWLegendTitle(category, self)] self.items[category].append(OWLegendItem(str(value), point, self)) self.update_items() def add_color_gradient(self, title, values): if len(values) < 2: # No point in showing a gradient with less that two values return if title in self.items: self.remove_category(title) item = OWLegendGradient(self.graph.contPalette, [str(v) for v in values], self) self.items[title] = [OWLegendTitle(title, self), item] self.update_items() def remove_category(self, category): """ Removes ``category`` and all items that belong to it. """ if category not in self.items: return if self.scene(): for item in self.items[category]: self.scene().removeItem(item) del self.items[category] def update_items(self): """ Updates the legend, repositioning the items according to the legend's orientation. """ self.box_rect = QRectF() x = y = 0 for lst in self.items.itervalues(): for item in lst: if hasattr(item, 'text_item'): item.text_item.setDefaultTextColor( self.graph.color(OWPalette.Text)) if hasattr(item, 'rect_item'): item.rect_item.setBrush(self.graph.color(OWPalette.Canvas)) if hasattr(item, 'set_orientation'): item.set_orientation(self._orientation) if self._orientation == Qt.Vertical: for lst in self.items.itervalues(): for item in lst: if self.max_size.height() and y and y + item.boundingRect( ).height() > self.max_size.height(): y = 0 x = x + item.boundingRect().width() self.box_rect = self.box_rect | item.boundingRect( ).translated(x, y) move_item_xy(item, x, y, self.graph.animate_plot) y = y + item.boundingRect().height() elif self._orientation == Qt.Horizontal: for lst in self.items.itervalues(): max_h = max(item.boundingRect().height() for item in lst) for item in lst: if self.max_size.width() and x and x + item.boundingRect( ).width() > self.max_size.width(): x = 0 y = y + max_h self.box_rect = self.box_rect | item.boundingRect( ).translated(x, y) move_item_xy(item, x, y, self.graph.animate_plot) x = x + item.boundingRect().width() if lst: x = 0 y = y + max_h def mouseMoveEvent(self, event): self.graph.notify_legend_moved(event.scenePos()) if self._floating: p = event.scenePos() - self._mouse_down_pos if self._floating_animation and self._floating_animation.state( ) == QPropertyAnimation.Running: self.set_pos_animated(p) else: self.setPos(p) event.accept() def mousePressEvent(self, event): self.setCursor(Qt.ClosedHandCursor) self.mouse_down = True self._mouse_down_pos = event.scenePos() - self.pos() event.accept() def mouseReleaseEvent(self, event): self.unsetCursor() self.mouse_down = False self._mouse_down_pos = QPointF() event.accept() def boundingRect(self): return self.box_rect def paint(self, painter, option, widget=None): pass def set_orientation(self, orientation): """ Sets the legend's orientation to ``orientation``. """ self._orientation = orientation self.update_items() def orientation(self): return self._orientation def set_pos_animated(self, pos): if (self.pos() - pos).manhattanLength() < 6 or not self.graph.animate_plot: self.setPos(pos) else: t = 250 if self._floating_animation and self._floating_animation.state( ) == QPropertyAnimation.Running: t = t - self._floating_animation.currentTime() self._floating_animation.stop() self._floating_animation = QPropertyAnimation(self, 'pos') self._floating_animation.setEndValue(pos) self._floating_animation.setDuration(t) self._floating_animation.start(QPropertyAnimation.KeepWhenStopped) def set_floating(self, floating, pos=None): """ If floating is ``True``, the legend can be dragged with the mouse. Otherwise, it's fixed in its position. If ``pos`` is specified, the legend is moved there. """ if floating == self._floating: return self._floating = floating if pos: if floating: self.set_pos_animated(pos - self._mouse_down_pos) else: self.set_pos_animated(pos)