def __init__(self,
                 parent=None,
                 orientation=Qt.Vertical,
                 value=0.0,
                 length=10.0,
                 **kwargs):
        self._orientation = orientation
        self._value = value
        self._length = length
        self._min = 0.0
        self._max = 1.0
        self._line = QLineF()  # type: Optional[QLineF]
        self._pen = QPen()
        super().__init__(parent, **kwargs)

        self.setAcceptedMouseButtons(Qt.LeftButton)
        self.setPen(
            make_pen(brush=QColor(50, 50, 50),
                     width=1,
                     cosmetic=False,
                     style=Qt.DashLine))

        if self._orientation == Qt.Vertical:
            self.setCursor(Qt.SizeVerCursor)
        else:
            self.setCursor(Qt.SizeHorCursor)
Esempio n. 2
0
    def test_arrowannotation(self):
        item = ArrowItem()
        self.scene.addItem(item)
        item.setLine(QLineF(100, 100, 100, 200))
        item.setLineWidth(5)

        item = ArrowItem()
        item.setLine(QLineF(150, 100, 150, 200))
        item.setLineWidth(10)
        item.setArrowStyle(ArrowItem.Concave)
        self.scene.addItem(item)

        item = ArrowAnnotation()
        item.setPos(10, 10)
        item.setLine(QLineF(10, 10, 200, 200))

        self.scene.addItem(item)
        item.setLineWidth(5)

        def advance():
            clock = time.clock() * 10
            item.setLineWidth(5 + math.sin(clock) * 5)
            item.setColor(QColor(Qt.red).lighter(100 + 30 * math.cos(clock)))
            self.singleShot(0, advance)

        advance()

        self.app.exec_()
    def test_arrowannotation(self):
        item = ArrowItem()
        self.scene.addItem(item)
        item.setLine(QLineF(100, 100, 100, 200))
        item.setLineWidth(5)

        item = ArrowItem()
        item.setLine(QLineF(150, 100, 150, 200))
        item.setLineWidth(10)
        item.setArrowStyle(ArrowItem.Concave)
        self.scene.addItem(item)

        item = ArrowAnnotation()
        item.setPos(10, 10)
        item.setLine(QLineF(10, 10, 200, 200))

        self.scene.addItem(item)
        item.setLineWidth(5)

        def advance():
            clock = time.clock() * 10
            item.setLineWidth(5 + math.sin(clock) * 5)
            item.setColor(QColor(Qt.red).lighter(100 + 30 * math.cos(clock)))

        timer = QTimer(item, interval=10)
        timer.timeout.connect(advance)
        timer.start()
        self.app.exec_()
Esempio n. 4
0
    def adjustGeometry(self):
        # type: () -> None
        """
        Adjust the widget geometry to exactly fit the arrow inside
        while preserving the arrow path scene geometry.

        """
        # local system coordinate
        geom = self.geometry().translated(-self.pos())
        line = self.__line

        arrow_rect = self.__arrowItem.shape().boundingRect()

        if geom.isNull() and not line.isNull():
            geom = QRectF(0, 0, 1, 1)

        if not (geom.contains(arrow_rect)):
            geom = geom.united(arrow_rect)

        geom = geom.intersected(arrow_rect)
        diff = geom.topLeft()
        line = QLineF(line.p1() - diff, line.p2() - diff)
        geom.translate(self.pos())
        self.setGeometry(geom)
        self.setLine(line)
Esempio n. 5
0
    def update_anchors(self):
        points, labels = self.master.get_anchors()
        if points is None:
            return
        r = self.scaled_radius * np.max(np.linalg.norm(points, axis=1))
        if self.anchor_items is None:
            self.anchor_items = []
            for point, label in zip(points, labels):
                anchor = AnchorItem(line=QLineF(0, 0, *point))
                anchor._label.setToolTip(f"<b>{label}</b>")
                label = label[:MAX_LABEL_LEN - 3] + "..." if len(
                    label) > MAX_LABEL_LEN else label
                anchor.setText(label)
                anchor.setFont(self.parameter_setter.anchor_font)

                visible = self.always_show_axes or np.linalg.norm(point) > r
                anchor.setVisible(visible)
                anchor.setPen(pg.mkPen((100, 100, 100)))
                self.plot_widget.addItem(anchor)
                self.anchor_items.append(anchor)
        else:
            for anchor, point, label in zip(self.anchor_items, points, labels):
                anchor.setLine(QLineF(0, 0, *point))
                visible = self.always_show_axes or np.linalg.norm(point) > r
                anchor.setVisible(visible)
                anchor.setFont(self.parameter_setter.anchor_font)
Esempio n. 6
0
    def setLine(self, line):
        """
        Set the arrow base line (a `QLineF` in object coordinates).
        """
        if self.__line != line:
            self.__line = QLineF(line)

            # local item coordinate system
            geom = self.geometry().translated(-self.pos())

            if geom.isNull() and not line.isNull():
                geom = QRectF(0, 0, 1, 1)

            arrow_shape = arrow_path_concave(line, self.lineWidth())
            arrow_rect = arrow_shape.boundingRect()

            if not (geom.contains(arrow_rect)):
                geom = geom.united(arrow_rect)

            if self.__autoAdjustGeometry:
                # Shrink the geometry if required.
                geom = geom.intersected(arrow_rect)

            # topLeft can move changing the local coordinates.
            diff = geom.topLeft()
            line = QLineF(line.p1() - diff, line.p2() - diff)
            self.__arrowItem.setLine(line)
            self.__arrowShadowBase.setLine(line)
            self.__line = line

            # parent item coordinate system
            geom.translate(self.pos())
            self.setGeometry(geom)
Esempio n. 7
0
    def setLine(self, line):
        """
        Set the arrow base line (a `QLineF` in object coordinates).
        """
        if self.__line != line:
            self.__line = QLineF(line)

            # local item coordinate system
            geom = self.geometry().translated(-self.pos())

            if geom.isNull() and not line.isNull():
                geom = QRectF(0, 0, 1, 1)

            arrow_shape = arrow_path_concave(line, self.lineWidth())
            arrow_rect = arrow_shape.boundingRect()

            if not (geom.contains(arrow_rect)):
                geom = geom.united(arrow_rect)

            if self.__autoAdjustGeometry:
                # Shrink the geometry if required.
                geom = geom.intersected(arrow_rect)

            # topLeft can move changing the local coordinates.
            diff = geom.topLeft()
            line = QLineF(line.p1() - diff, line.p2() - diff)
            self.__arrowItem.setLine(line)
            self.__arrowShadowBase.setLine(line)
            self.__line = line

            # parent item coordinate system
            geom.translate(self.pos())
            self.setGeometry(geom)
 def boundingRect(self) -> QRectF:
     if self._line is None:
         if self._orientation == Qt.Vertical:
             self._line = QLineF(0, self._value, self._length, self._value)
         else:
             self._line = QLineF(self._value, 0, self._value, self._length)
     r = QRectF(self._line.p1(), self._line.p2())
     penw = self.pen().width()
     return r.adjusted(-penw, -penw, penw, penw)
Esempio n. 9
0
    def setLine(self, line):
        # type: (QLineF) -> None
        if not isinstance(line, QLineF):
            raise TypeError()

        if line != self.__line:
            self.__line = QLineF(line)
            self.__pointsLayout()
            self.lineChanged.emit(line)
Esempio n. 10
0
    def __activeControlMoved(self, pos):
        line = QLineF(self.__line)
        control = self.__activeControl
        if control.anchor() == ControlPoint.TopLeft:
            line.setP1(pos)
        elif control.anchor() == ControlPoint.BottomRight:
            line.setP2(pos)

        if self.__line != line:
            self.blockSignals(True)
            self.setLine(line)
            self.blockSignals(False)
            self.lineEdited.emit(line)
Esempio n. 11
0
def qpainterpath_simple_split(path, t):
    """
    Split a QPainterPath defined simple curve.

    The path must be either empty or composed of a single LineToElement or
    CurveToElement.

    Parameters
    ----------
    path : QPainterPath

    t : float
        Point where to split specified as a percentage along the path

    Returns
    -------
    splitpath: Tuple[QPainterPath, QPainterPath]
        A pair of QPainterPaths
    """
    assert path.elementCount() > 0
    el0 = path.elementAt(0)
    assert el0.type == QPainterPath.MoveToElement
    if path.elementCount() == 1:
        p1 = QPainterPath()
        p1.moveTo(el0.x, el0.y)
        return p1, QPainterPath(p1)

    el1 = path.elementAt(1)
    if el1.type == QPainterPath.LineToElement:
        pointat = path.pointAtPercent(t)
        l1 = QLineF(el0.x, el0.y, pointat.x(), pointat.y())
        l2 = QLineF(pointat.x(), pointat.y(), el1.x, el1.y)
        p1 = QPainterPath()
        p2 = QPainterPath()
        p1.addLine(l1)
        p2.addLine(l2)
        return p1, p2
    elif el1.type == QPainterPath.CurveToElement:
        c0, c1, c2, c3 = el0, el1, path.elementAt(2), path.elementAt(3)
        assert all(el.type == QPainterPath.CurveToDataElement
                   for el in [c2, c3])
        cp = [QPointF(el.x, el.y) for el in [c0, c1, c2, c3]]
        first, second = bezier_subdivide(cp, t)
        p1, p2 = QPainterPath(), QPainterPath()
        p1.moveTo(first[0])
        p1.cubicTo(*first[1:])
        p2.moveTo(second[0])
        p2.cubicTo(*second[1:])
        return p1, p2
    else:
        assert False
Esempio n. 12
0
def shape_line_intersection(shape, shape_pos, line):
    """ Return point of intersection between shape and line that is
        closest to line.p1().
    """
    intersections, point = [], QPointF()
    p1 = shape.pointAtPercent(0) + shape_pos
    r = shape.boundingRect()
    for t in np.linspace(0, 1.01, 50) % 1:
        p2 = shape.pointAtPercent(t) + shape_pos
        if QLineF(p1, p2).intersect(line, point) == QLineF.BoundedIntersection:
            intersections.append(QPointF(point))
        p1 = p2
    return min(intersections,
               key=lambda point: QLineF(line.p1(), point).length())
Esempio n. 13
0
    def _updateTextAnchors(self):
        n = len(self._items)

        items = self._items
        dist = 15

        shape = reduce(QPainterPath.united, [item.path() for item in items])
        brect = shape.boundingRect()
        bradius = max(brect.width() / 2, brect.height() / 2)

        center = self.boundingRect().center()

        anchors = _category_anchors(items)
        self._textanchors = []
        for angle, anchor_h, anchor_v in anchors:
            line = QLineF.fromPolar(bradius, angle)
            ext = QLineF.fromPolar(dist, angle)
            line = QLineF(line.p1(), line.p2() + ext.p2())
            line = line.translated(center)

            anchor_pos = line.p2()
            self._textanchors.append((anchor_pos, anchor_h, anchor_v))

        for i in range(n):
            self._updateTextItemPos(i)
Esempio n. 14
0
    def _anchor_circle(self):
        # minimum visible anchor radius (radius)
        minradius = self.radius / 100 + 1e-5
        for item in chain(self.plotdata.anchoritem, self.plotdata.items):
            self.viewbox.removeItem(item)
        self.plotdata.anchoritem = []
        self.plotdata.items = []
        for anchor, var in zip(self.plotdata.anchors,
                               self.data.domain.attributes):
            if True or np.linalg.norm(anchor) > minradius:
                axitem = AnchorItem(
                    line=QLineF(0, 0, *anchor),
                    text=var.name,
                )
                axitem.setVisible(np.linalg.norm(anchor) > minradius)
                axitem.setPen(pg.mkPen((100, 100, 100)))
                axitem.setArrowVisible(True)
                self.plotdata.anchoritem.append(axitem)
                self.viewbox.addItem(axitem)

        hidecircle = QGraphicsEllipseItem()
        hidecircle.setRect(
            QRectF(-minradius, -minradius, 2 * minradius, 2 * minradius))

        _pen = QPen(Qt.lightGray, 1)
        _pen.setCosmetic(True)
        hidecircle.setPen(_pen)
        self.viewbox.addItem(hidecircle)
        self.plotdata.items.append(hidecircle)
        self.plotdata.hidecircle = hidecircle
Esempio n. 15
0
    def __init__(self, parent=None, line=None, **kwargs):
        Annotation.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)

        self.setFocusPolicy(Qt.ClickFocus)

        if line is None:
            line = QLineF(0, 0, 20, 0)

        self.__line = line
        self.__color = QColor(Qt.red)
        self.__arrowItem = ArrowItem(self)
        self.__arrowItem.setLine(line)
        self.__arrowItem.setBrush(self.__color)
        self.__arrowItem.setPen(QPen(Qt.NoPen))
        self.__arrowItem.setArrowStyle(ArrowItem.Concave)
        self.__arrowItem.setLineWidth(5)

        self.__shadow = QGraphicsDropShadowEffect(
            blurRadius=5,
            offset=QPointF(1.0, 2.0),
        )

        self.__arrowItem.setGraphicsEffect(self.__shadow)
        self.__shadow.setEnabled(True)

        self.__autoAdjustGeometry = True
Esempio n. 16
0
    def add_annotation(self, scheme_annot):
        """
        Create a new item for :class:`SchemeAnnotation` and add it
        to the scene. If the `scheme_annot` is already in the scene do
        nothing and just return its item.

        """
        if scheme_annot in self.__item_for_annotation:
            # Already added
            return self.__item_for_annotation[scheme_annot]

        if isinstance(scheme_annot, scheme.SchemeTextAnnotation):
            item = items.TextAnnotation()
            x, y, w, h = scheme_annot.rect
            item.setPos(x, y)
            item.resize(w, h)
            item.setTextInteractionFlags(Qt.TextEditorInteraction)

            font = font_from_dict(scheme_annot.font, item.font())
            item.setFont(font)
            item.setContent(scheme_annot.content, scheme_annot.content_type)
            scheme_annot.content_changed.connect(item.setContent)
        elif isinstance(scheme_annot, scheme.SchemeArrowAnnotation):
            item = items.ArrowAnnotation()
            start, end = scheme_annot.start_pos, scheme_annot.end_pos
            item.setLine(QLineF(QPointF(*start), QPointF(*end)))
            item.setColor(QColor(scheme_annot.color))

        scheme_annot.geometry_changed.connect(
            self.__on_scheme_annot_geometry_change)

        self.add_annotation_item(item)
        self.__item_for_annotation[scheme_annot] = item

        return item
Esempio n. 17
0
    def _anchor_circle(self, variables):
        # minimum visible anchor radius (radius)
        min_radius = self._get_min_radius()
        axisitems = []
        for anchor, var in zip(self.plotdata.axes, variables[:]):
            axitem = AnchorItem(
                line=QLineF(0, 0, *anchor),
                text=var.name,
            )
            axitem.setVisible(np.linalg.norm(anchor) > min_radius)
            axitem.setPen(pg.mkPen((100, 100, 100)))
            axitem.setArrowVisible(True)
            self.viewbox.addItem(axitem)
            axisitems.append(axitem)

        self.plotdata.axisitems = axisitems
        if self.placement == self.Placement.Circular:
            return

        hidecircle = QGraphicsEllipseItem()
        hidecircle.setRect(
            QRectF(-min_radius, -min_radius, 2 * min_radius, 2 * min_radius))

        _pen = QPen(Qt.lightGray, 1)
        _pen.setCosmetic(True)
        hidecircle.setPen(_pen)

        self.viewbox.addItem(hidecircle)
        self.plotdata.hidecircle = hidecircle
Esempio n. 18
0
 def setLine(self, line):
     # type: (QLineF) -> None
     """Set the baseline of the arrow (:class:`QLineF`).
     """
     if self.__line != line:
         self.__line = QLineF(line)
         self.__updateArrowPath()
Esempio n. 19
0
def line_extended(line, distance):
    """
    Return an QLineF extended by `distance` units in the positive direction.
    """
    angle = line.angle() / 360 * 2 * math.pi
    dx, dy = unit_point(angle, r=distance)
    return QLineF(line.p1(), line.p2() + QPointF(dx, dy))
Esempio n. 20
0
 def __on_lineGeometryChanged(self):
     # Possible geometry change from out of our control, for instance
     # item move as a part of a selection group.
     if not self.control.isControlActive():
         line = self.item.line()
         p1, p2 = map(self.item.mapToScene, (line.p1(), line.p2()))
         self.control.setLine(QLineF(p1, p2))
Esempio n. 21
0
 def _draw_border(point_1, point_2, border_width, parent):
     pen = QPen(QColor(self.border_color))
     pen.setCosmetic(True)
     pen.setWidth(border_width)
     line = QGraphicsLineItem(QLineF(point_1, point_2), parent)
     line.setPen(pen)
     return line
Esempio n. 22
0
    def paint(self, painter, option, index):
        curr_class_dist = np.array(index.data(Qt.DisplayRole), dtype=float)
        curr_class_dist /= sum(curr_class_dist)
        painter.save()
        self.drawBackground(painter, option, index)
        rect = option.rect

        if sum(curr_class_dist) > 0:
            pw = 3
            hmargin = 5
            x = rect.left() + hmargin
            width = rect.width() - 2 * hmargin
            vmargin = 1
            textoffset = pw + vmargin * 2
            painter.save()
            baseline = rect.bottom() - textoffset / 2

            text = str(index.data(Qt.DisplayRole))
            option.displayAlignment = Qt.AlignCenter
            text_rect = rect.adjusted(0, 0, 0, -textoffset * 0)
            self.drawDisplay(painter, option, text_rect, text)

            painter.setRenderHint(QPainter.Antialiasing)
            for prop, color in zip(curr_class_dist, self.color_schema):
                if prop == 0:
                    continue
                painter.setPen(QPen(QBrush(color), pw))
                to_x = x + prop * width
                line = QLineF(x, baseline, to_x, baseline)
                painter.drawLine(line)
                x = to_x
            painter.restore()

        painter.restore()
Esempio n. 23
0
 def draw_axes(self):
     self.remove_all_axes()
     for i in range(len(self.attributes)):
         axis_id = UserAxis + i
         a = self.add_axis(axis_id,
                           line=QLineF(i, 0, i, 1),
                           arrows=AxisStart | AxisEnd,
                           zoomable=True)
         a.always_horizontal_text = True
         a.max_text_width = 100
         a.title_margin = -10
         a.text_margin = 0
         a.setZValue(5)
         self.set_axis_title(axis_id, self.domain[self.attributes[i]].name)
         self.set_show_axis_title(axis_id, self.show_attr_values)
         if self.show_attr_values:
             attr = self.domain[self.attributes[i]]
             if attr.is_continuous:
                 self.set_axis_scale(axis_id, self.attr_values[attr][0],
                                     self.attr_values[attr][1])
             elif attr.is_discrete:
                 attribute_values = get_variable_values_sorted(
                     self.domain[self.attributes[i]])
                 attr_len = len(attribute_values)
                 values = [
                     float(1.0 + 2.0 * j) / float(2 * attr_len)
                     for j in range(len(attribute_values))
                 ]
                 a.set_bounds((0, 1))
                 self.set_axis_labels(axis_id,
                                      labels=attribute_values,
                                      values=values)
Esempio n. 24
0
def arrow_path_concave(line, width):
    """
    Return a :class:`QPainterPath` of a pretty looking arrow.
    """
    path = QPainterPath()
    p1, p2 = line.p1(), line.p2()

    if p1 == p2:
        return path

    baseline = QLineF(line)
    # Require some minimum length.
    baseline.setLength(max(line.length() - width * 3, width * 3))

    start, end = baseline.p1(), baseline.p2()
    mid = (start + end) / 2.0
    normal = QLineF.fromPolar(1.0, baseline.angle() + 90).p2()

    path.moveTo(start)
    path.lineTo(start + (normal * width / 4.0))

    path.quadTo(mid + (normal * width / 4.0),
                end + (normal * width / 1.5))

    path.lineTo(end - (normal * width / 1.5))
    path.quadTo(mid - (normal * width / 4.0),
                start - (normal * width / 4.0))
    path.closeSubpath()

    arrow_head_len = width * 4
    arrow_head_angle = 50
    line_angle = line.angle() - 180

    angle_1 = line_angle - arrow_head_angle / 2.0
    angle_2 = line_angle + arrow_head_angle / 2.0

    points = [p2,
              p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(),
              baseline.p2(),
              p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(),
              p2]

    poly = QPolygonF(points)
    path_head = QPainterPath()
    path_head.addPolygon(poly)
    path = path.united(path_head)
    return path
Esempio n. 25
0
 def _add_point_items(self):
     r = self.radius / 100 + 1e-5
     for point, var in zip(self._points, self._attributes):
         axitem = AnchorItem(line=QLineF(0, 0, *point), text=var.name)
         axitem.setVisible(np.linalg.norm(point) > r)
         axitem.setPen(pg.mkPen((100, 100, 100)))
         self.plot_widget.addItem(axitem)
         self._point_items.append(axitem)
Esempio n. 26
0
 def update_ends(self):
     try:
         self.prepareGeometryChange()
         self.setLine(QLineF(self.node1.edge_out_point(self),
                             self.node2.edge_in_point(self)))
     except RuntimeError:  # this gets called through QTimer.singleShot
         # and might already be deleted by Qt
         pass
Esempio n. 27
0
def angle(point1, point2):
    """Return the angle between the two points in range from -180 to 180.
    """
    angle = QLineF(point1, point2).angle()
    if angle > 180:
        return angle - 360
    else:
        return angle
Esempio n. 28
0
def path_link_disabled(basepath):
    # type: (QPainterPath) -> QPainterPath
    """
    Return a QPainterPath 'styled' to indicate a 'disabled' link.

    A disabled link is displayed with a single disconnection symbol in the
    middle (--||--)

    Parameters
    ----------
    basepath : QPainterPath
        The base path (a simple curve spine).

    Returns
    -------
    path : QPainterPath
        A 'styled' link path
    """
    segmentlen = basepath.length()
    px = 5

    if segmentlen < 10:
        return QPainterPath(basepath)

    t = (px / 2) / segmentlen
    p1, _ = qpainterpath_simple_split(basepath, 0.50 - t)
    _, p2 = qpainterpath_simple_split(basepath, 0.50 + t)

    angle = -basepath.angleAtPercent(0.5) + 90
    angler = math.radians(angle)
    normal = QPointF(math.cos(angler), math.sin(angler))

    end1 = p1.currentPosition()
    start2 = QPointF(p2.elementAt(0).x, p2.elementAt(0).y)
    p1.moveTo(start2.x(), start2.y())
    p1.addPath(p2)

    def QPainterPath_addLine(path, line):
        # type: (QPainterPath, QLineF) -> None
        path.moveTo(line.p1())
        path.lineTo(line.p2())

    QPainterPath_addLine(p1, QLineF(end1 - normal * 3, end1 + normal * 3))
    QPainterPath_addLine(p1, QLineF(start2 - normal * 3, start2 + normal * 3))
    return p1
Esempio n. 29
0
    def _updateTextAnchors(self):
        n = len(self._items)

        items = self._items
        dist = 15

        shape = reduce(QPainterPath.united, [item.path() for item in items])
        brect = shape.boundingRect()
        bradius = max(brect.width() / 2, brect.height() / 2)

        center = self.boundingRect().center()

        anchors = _category_anchors(items)
        self._textanchors = []
        for angle, anchor_h, anchor_v in anchors:
            line = QLineF.fromPolar(bradius, angle)
            ext = QLineF.fromPolar(dist, angle)
            line = QLineF(line.p1(), line.p2() + ext.p2())
            line = line.translated(center)

            anchor_pos = line.p2()
            self._textanchors.append((anchor_pos, anchor_h, anchor_v))

        for i in range(n):
            self._updateTextItemPos(i)
Esempio n. 30
0
 def update_anchors(self):
     points, labels = self.master.get_anchors()
     if points is None:
         return
     r = self.scaled_radius
     if self.anchor_items is None:
         self.anchor_items = []
         for point, label in zip(points, labels):
             anchor = AnchorItem(line=QLineF(0, 0, *point), text=label)
             anchor.setVisible(np.linalg.norm(point) > r)
             anchor.setPen(pg.mkPen((100, 100, 100)))
             self.plot_widget.addItem(anchor)
             self.anchor_items.append(anchor)
     else:
         for anchor, point, label in zip(self.anchor_items, points, labels):
             anchor.setLine(QLineF(0, 0, *point))
             anchor.setText(label)
             anchor.setVisible(np.linalg.norm(point) > r)
Esempio n. 31
0
 def __init__(self, x, y, parent=None, line=QLineF(), scene_size=1, text="", **kwargs):
     super().__init__(parent, **kwargs)
     self.arrows = [
         pg.ArrowItem(pos=(x - scene_size * 0.07 * np.cos(np.radians(angle)),
                           y + scene_size * 0.07 * np.sin(np.radians(angle))),
                      parent=self, angle=angle,
                      headLen=13, tipAngle=45,
                      brush=pg.mkColor(128, 128, 128))
         for angle in (0, 90, 180, 270)]
Esempio n. 32
0
 def paint(self, p, opt, widget):
     if self.xData is None or len(self.xData) == 0:
         return
     p.setRenderHint(p.Antialiasing, True)
     p.setCompositionMode(p.CompositionMode_SourceOver)
     if self.widths is None:
         p.setPen(self.pen)
         for x0, y0, x1, y1 in zip(self.xData[::2], self.yData[::2],
                                   self.xData[1::2], self.yData[1::2]):
             p.drawLine(QLineF(x0, y0, x1, y1))
     else:
         pen = QPen(self.pen)
         for x0, y0, x1, y1, w in zip(self.xData[::2], self.yData[::2],
                                      self.xData[1::2], self.yData[1::2],
                                      self.widths):
             pen.setWidth(w)
             p.setPen(pen)
             p.drawLine(QLineF(x0, y0, x1, y1))
Esempio n. 33
0
def angle(point1, point2):
    # type: (QPointF, QPointF) -> float
    """
    Return the angle between the two points in range from -180 to 180.
    """
    angle = QLineF(point1, point2).angle()
    if angle > 180:
        return angle - 360
    else:
        return angle
Esempio n. 34
0
    def __init__(self, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__line = QLineF()
        self.__points = [
            ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start
            ControlPoint(self, ControlPoint.BottomRight),  # line end
        ]

        self.__activeControl = None

        if self.scene():
            self.__installFilter()

        for p in self.__points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)
Esempio n. 35
0
    def paint(self, painter, option, widget=None):
        color, _ = Edge.Color.SELECTED if self.selected else Edge.Color.DEFAULT
        pen = self.pen()
        pen.setColor(color)
        pen.setBrush(QBrush(color))
        pen.setWidth(np.clip(2 * self.weight(), .5, 4))
        painter.setPen(pen)
        self.setPen(pen)

        if self.source == self.dest:
            return self.paintArc(painter, option, widget)
        if self.source.collidesWithItem(self.dest):
            return

        have_two_edges = len([edge for edge in self.source.edges
                              if self.source in edge and self.dest in edge and edge is not self])

        source_pos = self.source.pos()
        dest_pos = self.dest.pos()

        color = self.pen().color()
        painter.setBrush(color)

        point = shape_line_intersection(self.dest.shape(), dest_pos,
                                        QLineF(source_pos, dest_pos))
        line = QLineF(source_pos, point)
        if have_two_edges:
            normal = line.normalVector()
            normal.setLength(15)
            line = QLineF(normal.p2(), point)
            self.label.setPos(line.pointAt(.5))
            self.squares.placeBelow(self.label)

        self.setLine(line)
        painter.drawLine(line)

        # Draw arrow head
        self.arrowHead.clear()
        for point in self._arrowhead_points(line):
            self.arrowHead.append(point)
        painter.drawPolygon(self.arrowHead)
Esempio n. 36
0
    def adjustGeometry(self):
        """
        Adjust the widget geometry to exactly fit the arrow inside
        while preserving the arrow path scene geometry.

        """
        # local system coordinate
        geom = self.geometry().translated(-self.pos())
        line = self.__line

        arrow_rect = self.__arrowItem.shape().boundingRect()

        if geom.isNull() and not line.isNull():
            geom = QRectF(0, 0, 1, 1)

        if not (geom.contains(arrow_rect)):
            geom = geom.united(arrow_rect)

        geom = geom.intersected(arrow_rect)
        diff = geom.topLeft()
        line = QLineF(line.p1() - diff, line.p2() - diff)
        geom.translate(self.pos())
        self.setGeometry(geom)
        self.setLine(line)
Esempio n. 37
0
def qpainterpath_simple_split(path, t):
    """
    Split a QPainterPath defined simple curve.

    The path must be either empty or composed of a single LineToElement or
    CurveToElement.

    Parameters
    ----------
    path : QPainterPath

    t : float
        Point where to split specified as a percentage along the path

    Returns
    -------
    splitpath: Tuple[QPainterPath, QPainterPath]
        A pair of QPainterPaths
    """
    assert path.elementCount() > 0
    el0 = path.elementAt(0)
    assert el0.type == QPainterPath.MoveToElement
    if path.elementCount() == 1:
        p1 = QPainterPath()
        p1.moveTo(el0.x, el0.y)
        return p1, QPainterPath(p1)

    el1 = path.elementAt(1)
    if el1.type == QPainterPath.LineToElement:
        pointat = path.pointAtPercent(t)
        l1 = QLineF(el0.x, el0.y, pointat.x(), pointat.y())
        l2 = QLineF(pointat.x(), pointat.y(), el1.x, el1.y)
        p1 = QPainterPath()
        p2 = QPainterPath()
        p1.moveTo(l1.p1())
        p1.lineTo(l1.p2())
        p2.moveTo(l2.p1())
        p2.lineTo(l2.p2())
        return p1, p2
    elif el1.type == QPainterPath.CurveToElement:
        c0, c1, c2, c3 = el0, el1, path.elementAt(2), path.elementAt(3)
        assert all(el.type == QPainterPath.CurveToDataElement
                   for el in [c2, c3])
        cp = [QPointF(el.x, el.y) for el in [c0, c1, c2, c3]]
        first, second = bezier_subdivide(cp, t)
        p1, p2 = QPainterPath(), QPainterPath()
        p1.moveTo(first[0])
        p1.cubicTo(*first[1:])
        p2.moveTo(second[0])
        p2.cubicTo(*second[1:])
        return p1, p2
    else:
        assert False
Esempio n. 38
0
    def __init__(self, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__line = QLineF()
        self.__points = \
            [ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start
             ControlPoint(self, ControlPoint.BottomRight)  # line end
             ]

        self.__activeControl = None

        if self.scene():
            self.__installFilter()

        for p in self.__points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)
Esempio n. 39
0
def arrow_path_plain(line, width):
    """
    Return an :class:`QPainterPath` of a plain looking arrow.
    """
    path = QPainterPath()
    p1, p2 = line.p1(), line.p2()

    if p1 == p2:
        return path

    baseline = QLineF(line)
    # Require some minimum length.
    baseline.setLength(max(line.length() - width * 3, width * 3))
    path.moveTo(baseline.p1())
    path.lineTo(baseline.p2())

    stroker = QPainterPathStroker()
    stroker.setWidth(width)
    path = stroker.createStroke(path)

    arrow_head_len = width * 4
    arrow_head_angle = 50
    line_angle = line.angle() - 180

    angle_1 = line_angle - arrow_head_angle / 2.0
    angle_2 = line_angle + arrow_head_angle / 2.0

    points = [
        p2,
        p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(),
        p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(),
        p2,
    ]

    poly = QPolygonF(points)
    path_head = QPainterPath()
    path_head.addPolygon(poly)
    path = path.united(path_head)
    return path
Esempio n. 40
0
    def update(self, zoom_only=False):
        self.update_ticks()
        line_color = self.plot.color(OWPalette.Axis)
        text_color = self.plot.color(OWPalette.Text)
        if not self.graph_line or not self.scene():
            return
        self.line_item.setLine(self.graph_line)
        self.line_item.setPen(line_color)
        if self.title:
            self.title_item.setHtml('<b>' + self.title + '</b>')
            self.title_item.setDefaultTextColor(text_color)
        if self.title_location == AxisMiddle:
            title_p = 0.5
        elif self.title_location == AxisEnd:
            title_p = 0.95
        else:
            title_p = 0.05
        title_pos = self.graph_line.pointAt(title_p)
        v = self.graph_line.normalVector().unitVector()

        dense_text = False
        if hasattr(self, 'title_margin'):
            offset = self.title_margin
        elif self._ticks:
            if self.should_be_expanded():
                offset = 55
                dense_text = True
            else:
                offset = 35
        else:
            offset = 10

        if self.title_above:
            title_pos += (v.p2() - v.p1()) * (offset + QFontMetrics(self.title_item.font()).height())
        else:
            title_pos -= (v.p2() - v.p1()) * offset
            ## TODO: Move it according to self.label_pos
        self.title_item.setVisible(self.show_title)
        self.title_item.setRotation(-self.graph_line.angle())
        c = self.title_item.mapToParent(self.title_item.boundingRect().center())
        tl = self.title_item.mapToParent(self.title_item.boundingRect().topLeft())
        self.title_item.setPos(title_pos - c + tl)

        ## Arrows
        if not zoom_only:
            if self.start_arrow_item:
                self.scene().removeItem(self.start_arrow_item)
                self.start_arrow_item = None
            if self.end_arrow_item:
                self.scene().removeItem(self.end_arrow_item)
                self.end_arrow_item = None

        if self.arrows & AxisStart:
            if not zoom_only or not self.start_arrow_item:
                self.start_arrow_item = QGraphicsPathItem(self.arrow_path, self)
            self.start_arrow_item.setPos(self.graph_line.p1())
            self.start_arrow_item.setRotation(-self.graph_line.angle() + 180)
            self.start_arrow_item.setBrush(line_color)
            self.start_arrow_item.setPen(line_color)
        if self.arrows & AxisEnd:
            if not zoom_only or not self.end_arrow_item:
                self.end_arrow_item = QGraphicsPathItem(self.arrow_path, self)
            self.end_arrow_item.setPos(self.graph_line.p2())
            self.end_arrow_item.setRotation(-self.graph_line.angle())
            self.end_arrow_item.setBrush(line_color)
            self.end_arrow_item.setPen(line_color)

        ## Labels

        n = len(self._ticks)
        resize_plot_item_list(self.label_items, n, QGraphicsTextItem, self)
        resize_plot_item_list(self.label_bg_items, n, QGraphicsRectItem, self)
        resize_plot_item_list(self.tick_items, n, QGraphicsLineItem, self)

        test_rect = QRectF(self.graph_line.p1(), self.graph_line.p2()).normalized()
        test_rect.adjust(-1, -1, 1, 1)

        n_v = self.graph_line.normalVector().unitVector()
        if self.title_above:
            n_p = n_v.p2() - n_v.p1()
        else:
            n_p = n_v.p1() - n_v.p2()
        l_v = self.graph_line.unitVector()
        l_p = l_v.p2() - l_v.p1()
        for i in range(n):
            pos, text, size, step = self._ticks[i]
            hs = 0.5 * step
            tick_pos = self.map_to_graph(pos)
            if not test_rect.contains(tick_pos):
                self.tick_items[i].setVisible(False)
                self.label_items[i].setVisible(False)
                continue
            item = self.label_items[i]
            item.setVisible(True)
            if not zoom_only:
                if self.id in XAxes or getattr(self, 'is_horizontal', False):
                    item.setHtml('<center>' + Qt.escape(text.strip()) + '</center>')
                else:
                    item.setHtml(Qt.escape(text.strip()))

            item.setTextWidth(-1)
            text_angle = 0
            if dense_text:
                w = min(item.boundingRect().width(), self.max_text_width)
                item.setTextWidth(w)
                if self.title_above:
                    label_pos = tick_pos + n_p * (w + self.text_margin) + l_p * item.boundingRect().height() / 2
                else:
                    label_pos = tick_pos + n_p * self.text_margin + l_p * item.boundingRect().height() / 2
                text_angle = -90 if self.title_above else 90
            else:
                w = min(item.boundingRect().width(),
                        QLineF(self.map_to_graph(pos - hs), self.map_to_graph(pos + hs)).length())
                label_pos = tick_pos + n_p * self.text_margin + l_p * item.boundingRect().height() / 2
                item.setTextWidth(w)

            if not self.always_horizontal_text:
                if self.title_above:
                    item.setRotation(-self.graph_line.angle() - text_angle)
                else:
                    item.setRotation(self.graph_line.angle() - text_angle)

            item.setPos(label_pos)
            item.setDefaultTextColor(text_color)

            self.label_bg_items[i].setRect(item.boundingRect())
            self.label_bg_items[i].setPen(QPen(Qt.NoPen))
            self.label_bg_items[i].setBrush(self.plot.color(OWPalette.Canvas))

            item = self.tick_items[i]
            item.setVisible(True)
            tick_line = QLineF(v)
            tick_line.translate(-tick_line.p1())
            tick_line.setLength(size)
            if self.title_above:
                tick_line.setAngle(tick_line.angle() + 180)
            item.setLine(tick_line)
            item.setPen(line_color)
            item.setPos(self.map_to_graph(pos))
Esempio n. 41
0
class ControlPointLine(QGraphicsObject):

    lineChanged = Signal(QLineF)
    lineEdited = Signal(QLineF)

    def __init__(self, parent=None, **kwargs):
        QGraphicsObject.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemHasNoContents)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        self.__line = QLineF()
        self.__points = \
            [ControlPoint(self, ControlPoint.TopLeft),  # TopLeft is line start
             ControlPoint(self, ControlPoint.BottomRight)  # line end
             ]

        self.__activeControl = None

        if self.scene():
            self.__installFilter()

        for p in self.__points:
            p.setFlag(QGraphicsItem.ItemIsFocusable)
            p.setFocusProxy(self)

    def setLine(self, line):
        if not isinstance(line, QLineF):
            raise TypeError()

        if line != self.__line:
            self.__line = line
            self.__pointsLayout()
            self.lineChanged.emit(line)

    def line(self):
        return self.__line

    def isControlActive(self):
        """Return the state of the control. True if the control is
        active (user is dragging one of the points) False otherwise.

        """
        return self.__activeControl is not None

    def __installFilter(self):
        for p in self.__points:
            p.installSceneEventFilter(self)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSceneHasChanged:
            if self.scene():
                self.__installFilter()
        return QGraphicsObject.itemChange(self, change, value)

    def sceneEventFilter(self, obj, event):
        try:
            obj = toGraphicsObjectIfPossible(obj)
            if isinstance(obj, ControlPoint):
                etype = event.type()
                if etype == QEvent.GraphicsSceneMousePress:
                    self.__setActiveControl(obj)
                elif etype == QEvent.GraphicsSceneMouseRelease:
                    self.__setActiveControl(None)

            return QGraphicsObject.sceneEventFilter(self, obj, event)
        except Exception:
            log.error("", exc_info=True)

    def __pointsLayout(self):
        self.__points[0].setPos(self.__line.p1())
        self.__points[1].setPos(self.__line.p2())

    def __setActiveControl(self, control):
        if self.__activeControl != control:
            if self.__activeControl is not None:
                self.__activeControl.positionChanged[QPointF].disconnect(
                    self.__activeControlMoved
                )

            self.__activeControl = control

            if control is not None:
                control.positionChanged[QPointF].connect(
                    self.__activeControlMoved
                )

    def __activeControlMoved(self, pos):
        line = QLineF(self.__line)
        control = self.__activeControl
        if control.anchor() == ControlPoint.TopLeft:
            line.setP1(pos)
        elif control.anchor() == ControlPoint.BottomRight:
            line.setP2(pos)

        if self.__line != line:
            self.blockSignals(True)
            self.setLine(line)
            self.blockSignals(False)
            self.lineEdited.emit(line)

    def boundingRect(self):
        return QRectF()
Esempio n. 42
0
    def paintArc(self, painter, option, widget):
        assert self.source is self.dest
        node = self.source
        def best_angle():
            """...is the one furthest away from all other angles"""
            angles = [QLineF(node.pos(), other.pos()).angle()
                      for other in chain((edge.source for edge in node.edges
                                          if edge.dest == node and edge.source != node),
                                         (edge.dest for edge in node.edges
                                          if edge.dest != node and edge.source == node))]
            angles.sort()
            if not angles:  # If this self-constraint is the only edge
                return 225
            deltas = np.array(angles[1:] + [360 + angles[0]]) - angles
            return (angles[deltas.argmax()] + deltas.max()/2) % 360

        angle = best_angle()
        inf = QPointF(-1e20, -1e20)  # Doesn't work with real -np.inf!
        line0 = QLineF(node.pos(), inf)
        line1 = QLineF(node.pos(), inf)
        line2 = QLineF(node.pos(), inf)
        line0.setAngle(angle)
        line1.setAngle(angle - 13)
        line2.setAngle(angle + 13)

        p0 = shape_line_intersection(node.shape(), node.pos(), line0)
        p1 = shape_line_intersection(node.shape(), node.pos(), line1)
        p2 = shape_line_intersection(node.shape(), node.pos(), line2)
        path = QPainterPath()
        path.moveTo(p1)
        line = QLineF(node.pos(), p0)
        line.setLength(3*line.length())
        pt = line.p2()
        path.quadTo(pt, p2)

        line = QLineF(node.pos(), pt)
        self.setLine(line)  # This invalidates DeviceCoordinateCache
        painter.drawPath(path)

        # Draw arrow head
        line = QLineF(pt, p2)
        self.arrowHead.clear()
        for point in self._arrowhead_points(line):
            self.arrowHead.append(point)
        painter.setBrush(self.pen().color())
        painter.drawPolygon(self.arrowHead)

        # Update label position
        self.label.setPos(path.pointAtPercent(.5))
        if 90 < angle < 270:  # Right-align the label
            pos = self.label.pos()
            x, y = pos.x(), pos.y()
            self.label.setPos(x - self.label.boundingRect().width(), y)
        self.squares.placeBelow(self.label)
Esempio n. 43
0
 def adjust(self):
     line = QLineF(self.source.pos(), self.dest.pos())
     self.setLine(line)
     self.label.setPos(line.pointAt(.5) - self.label.boundingRect().center())
     self.squares.placeBelow(self.label)