Example #1
0
def scaled(size: QSizeF, constraint: QSizeF, mode=Qt.KeepAspectRatio) -> QSizeF:
    """
    Return size scaled to fit in the constrains using the aspect mode `mode`.

    If  width or height of constraint are negative they are ignored,
    ie. the result is not constrained in that dimension.
    """
    size, constraint = QSizeF(size), QSizeF(constraint)
    if constraint.width() < 0 and constraint.height() < 0:
        return size
    if mode == Qt.IgnoreAspectRatio:
        if constraint.width() >= 0:
            size.setWidth(constraint.width())
        if constraint.height() >= 0:
            size.setHeight(constraint.height())
    elif mode == Qt.KeepAspectRatio:
        if constraint.width() < 0:
            constraint.setWidth(QWIDGETSIZE_MAX)
        if constraint.height() < 0:
            constraint.setHeight(QWIDGETSIZE_MAX)
        size.scale(constraint, mode)
    elif mode == Qt.KeepAspectRatioByExpanding:
        if constraint.width() < 0:
            constraint.setWidth(0)
        if constraint.height() < 0:
            constraint.setHeight(0)
        size.scale(constraint, mode)
    return size
Example #2
0
    def _rescale(self):
        if self._root is None:
            return

        scale = self._height_scale_factor()
        base = scale * self._root.value.height
        crect = self.contentsRect()
        leaf_count = len(list(leaves(self._root)))
        if self.orientation in [Left, Right]:
            drect = QSizeF(base, leaf_count)
        else:
            drect = QSizeF(leaf_count, base)

        eps = np.finfo(np.float64).eps

        if abs(drect.width()) < eps:
            sx = 1.0
        else:
            sx = crect.width() / drect.width()

        if abs(drect.height()) < eps:
            sy = 1.0
        else:
            sy = crect.height() / drect.height()

        transform = QTransform().scale(sx, sy)
        self._transform = transform
        self._itemgroup.setPos(crect.topLeft())
        self._itemgroup.setGeometry(crect)
        for node_geom in postorder(self._layout):
            node, _ = node_geom.value
            item = self._items[node]
            item.setGeometryData(transform.map(item.sourcePath),
                                 transform.map(item.sourceAreaShape))
        self._selection_items = None
        self._update_selection_items()
 def sizeHint(self, which: Qt.SizeHint, constraint=QSizeF(-1, -1)) -> QSizeF:
     if which == Qt.PreferredSize:
         brect = self.item.boundingRect()
         brect = self.item.mapRectToParent(brect)
         scale = self.__transform
         size = brect.size()
         if scale is not None:
             # undo the scaling
             sx, sy = self.__scale
             size = QSizeF(float(Fraction(size.width()) / sx),
                           float(Fraction(size.height()) / sy))
         if constraint != QSizeF(-1, -1):
             size = scaled(size, constraint, self.__aspectMode)
         return size
     else:
         return QSizeF()
    def pixmapTransform(self) -> QTransform:
        if self.__pixmap.isNull():
            return QTransform()

        pxsize = QSizeF(self.__pixmap.size())
        crect = self.contentsRect()
        transform = QTransform()
        transform = transform.translate(crect.left(), crect.top())

        if self.__scaleContents:
            csize = scaled(pxsize, crect.size(), self.__aspectMode)
        else:
            csize = pxsize

        xscale = csize.width() / pxsize.width()
        yscale = csize.height() / pxsize.height()

        return transform.scale(xscale, yscale)
    def sizeHint(self, which: Qt.SizeHint, constraint=QSizeF(-1, -1)) -> QSizeF:
        left, top, right, bottom = self.getContentsMargins()
        extra_margins = QSizeF(left + right, top + bottom)
        if constraint.width() >= 0:
            constraint.setWidth(
                max(constraint.width() - extra_margins.width(), 0.0))

        if which == Qt.PreferredSize:
            if constraint.width() >= 0:
                rect = QRectF(0, 0, constraint.width(), FLT_MAX)
            else:
                rect = QRectF(0, 0, FLT_MAX, FLT_MAX)
            res = self.__doLayout(rect)
            sh = reduce(QRectF.united, res, QRectF()).size()
            return sh + extra_margins
        if which == Qt.MinimumSize:
            return reduce(QSizeF.expandedTo,
                          (item.minimumSize() for item in self.__items),
                          QSizeF()) + extra_margins
        return QSizeF()
Example #6
0
    def paint(self, painter, option, widget=0):
        if self._pixmap.isNull():
            return

        rect = self.contentsRect()
        pixsize = QSizeF(self._pixmap.size())
        aspectmode = (Qt.KeepAspectRatio
                      if self._keepAspect else Qt.IgnoreAspectRatio)

        if self._crop:
            height, width = pixsize.height(), pixsize.width()

            diff = abs(height - width)
            if height > width:
                y, x = diff / 2, 0
                h, w = height - diff, width
            else:
                x, y = diff / 2, 0
                w, h = width - diff, height

            source = QRectF(x, y, w, h)
            pixrect = QRectF(QPointF(0, 0), rect.size())

        else:
            source = QRectF(QPointF(0, 0), pixsize)
            pixsize.scale(rect.size(), aspectmode)
            pixrect = QRectF(QPointF(0, 0), pixsize)

        if self._subset:
            painter.setOpacity(1.0)
        else:
            painter.setOpacity(0.35)

        pixrect.moveCenter(rect.center())
        painter.save()
        painter.setPen(QPen(QColor(0, 0, 0, 50), 3))
        painter.drawRoundedRect(pixrect, 2, 2)
        painter.setRenderHint(QPainter.SmoothPixmapTransform)

        painter.drawPixmap(pixrect, self._pixmap, source)
        painter.restore()
    def paint(self, painter, option, widget=0):
        if self._pixmap.isNull():
            return

        rect = self.contentsRect()
        pixsize = QSizeF(self._pixmap.size())
        aspectmode = (Qt.KeepAspectRatio if self._keepAspect else Qt.IgnoreAspectRatio)

        if self._crop:
            height, width = pixsize.height(), pixsize.width()

            diff = abs(height - width)
            if height > width:
                y, x = diff / 2, 0
                h, w = height - diff, width
            else:
                x, y = diff / 2, 0
                w, h = width - diff, height

            source = QRectF(x, y, w, h)
            pixrect = QRectF(QPointF(0, 0), rect.size())

        else:
            source = QRectF(QPointF(0, 0), pixsize)
            pixsize.scale(rect.size(), aspectmode)
            pixrect = QRectF(QPointF(0, 0), pixsize)

        if self._subset:
            painter.setOpacity(1.0)
        else:
            painter.setOpacity(0.35)

        pixrect.moveCenter(rect.center())
        painter.save()
        painter.setPen(QPen(QColor(0, 0, 0, 50), 3))
        painter.drawRoundedRect(pixrect, 2, 2)
        painter.setRenderHint(QPainter.SmoothPixmapTransform)

        painter.drawPixmap(pixrect, self._pixmap, source)
        painter.restore()
Example #8
0
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)
Example #9
0
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)