def path_link_disabled(basepath): """ 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): 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
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)
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), text=label) 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)
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))) anchor.setFont(self.parameter_setter.anchor_font) 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) anchor.setFont(self.parameter_setter.anchor_font)
def __on_scheme_annot_geometry_change(self): annot = self.sender() item = self.__item_for_annotation[annot] if isinstance(annot, scheme.SchemeTextAnnotation): item.setGeometry(QRectF(*annot.rect)) elif isinstance(annot, scheme.SchemeArrowAnnotation): p1 = item.mapFromScene(QPointF(*annot.start_pos)) p2 = item.mapFromScene(QPointF(*annot.end_pos)) item.setLine(QLineF(p1, p2)) else: pass
def paint(self, painter, option, widget=None): brect = self.boundingRect() c = brect.center() line = QLineF(brect.left(), c.y(), brect.right(), c.y()) t = painter.transform() line = t.map(line) painter.save() painter.resetTransform() painter.setPen(self.currentPen) painter.drawLine(line) painter.restore()
def test_controlpointline(self): control = ControlPointLine() line = QGraphicsLineItem(10, 10, 200, 200) self.scene.addItem(line) self.scene.addItem(control) control.setLine(line.line()) control.setFocus() control.lineChanged.connect(line.setLine) control.setLine(QLineF(30, 30, 180, 180)) self.assertEqual(control.line(), line.line()) self.assertEqual(line.line(), QLineF(30, 30, 180, 180)) control.lineEdited.connect(line.setLine) self.view.show() self.app.exec_() self.assertEqual(control.line(), line.line())
def paint(self, p, *args): # From orange3-bioinformatics:OWFeatureSelection.py, thanks to @ales-erjavec brect = self.boundingRect() c = brect.center() line = QLineF(brect.left(), c.y(), brect.right(), c.y()) t = p.transform() line = t.map(line) p.save() p.resetTransform() p.setPen(self.currentPen) p.drawLine(line) p.restore()
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 __init__(self, parent=None, line=None, **kwargs): # type: (Optional[QGraphicsItem], Optional[QLineF], Any) -> None super().__init__(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 = QLineF(line) self.__color = QColor(Qt.red) # An item with the same shape as this arrow, stacked behind this # item as a source for QGraphicsDropShadowEffect. Cannot attach # the effect to this item directly as QGraphicsEffect makes the item # non devicePixelRatio aware. self.__arrowShadowBase = ArrowItem(self, line=line) self.__arrowShadowBase.setPen(Qt.NoPen) # no pen -> slightly thinner self.__arrowShadowBase.setBrush(QBrush(self.__color)) self.__arrowShadowBase.setArrowStyle(ArrowItem.Concave) self.__arrowShadowBase.setLineWidth(5) self.__shadow = QGraphicsDropShadowEffect( blurRadius=5, offset=QPointF(1.0, 2.0), ) self.__arrowShadowBase.setGraphicsEffect(self.__shadow) self.__shadow.setEnabled(True) # The 'real' shape item self.__arrowItem = ArrowItem(self, line=line) self.__arrowItem.setBrush(self.__color) self.__arrowItem.setPen(QPen(self.__color)) self.__arrowItem.setArrowStyle(ArrowItem.Concave) self.__arrowItem.setLineWidth(5) self.__autoAdjustGeometry = True
def __init__(self, parent=None, line=None, lineWidth=4, **kwargs): GraphicsPathObject.__init__(self, parent, **kwargs) if line is None: line = QLineF(0, 0, 10, 0) self.__line = line self.__lineWidth = lineWidth self.__arrowStyle = ArrowItem.Plain self.__updateArrowPath()
def __init__( self, parent: Optional[QObject] = None, color=QColor(), penWidth=5, barFillRatioRole=Qt.UserRole + 1, barColorRole=Qt.UserRole + 2, **kwargs ): super().__init__(parent, **kwargs) self.color = color self.penWidth = penWidth self.barFillRatioRole = barFillRatioRole self.barColorRole = barColorRole # Line and pen instances reused self.__line = QLineF() self.__pen = QPen(color, penWidth, Qt.SolidLine, Qt.RoundCap)
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) 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)
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
def __init__(self, parent=None, line=None, lineWidth=4., **kwargs): # type: (Optional[QGraphicsItem], Optional[QLineF], float, Any) -> None super().__init__(parent, **kwargs) if line is None: line = QLineF(0, 0, 10, 0) self.__line = line self.__lineWidth = lineWidth self.__arrowStyle = ArrowItem.Plain self.__updateArrowPath()
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)
def arrow_path_concave(line, width): # type: (QLineF, float) -> QPainterPath """ 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
def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: if self.arrow_item is not None: p1, p2 = self.down_pos, event.scenePos() # Commit the annotation to the scheme self.annotation.set_line(point_to_tuple(p1), point_to_tuple(p2)) self.document.addAnnotation(self.annotation) p1, p2 = map(self.arrow_item.mapFromScene, (p1, p2)) self.arrow_item.setLine(QLineF(p1, p2)) self.end() return True
def __init__(self, parent=None, line=QLineF(), text="", **kwargs): super().__init__(None, **kwargs) self._text = text self.setFlag(pg.GraphicsObject.ItemHasNoContents) self._spine = QGraphicsLineItem(line, self) angle = line.angle() self._arrow = pg.ArrowItem(parent=self, angle=0) self._arrow.setPos(self._spine.line().p2()) self._arrow.setRotation(angle) self._label = TextItem(text=text, color=(10, 10, 10)) self._label.setParentItem(self) self._label.setPos(self._spine.line().p2()) if parent is not None: self.setParentItem(parent)
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) ]
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)
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: Optional[QLineF] = QLineF() self._pen: Optional[QPen] = None super().__init__(parent, **kwargs) self.setAcceptedMouseButtons(Qt.LeftButton) if self._orientation == Qt.Vertical: self.setCursor(Qt.SizeVerCursor) else: self.setCursor(Qt.SizeHorCursor)
def editItem(self, item): annotation = self.scene.annotation_for_item(item) control = controlpoints.ControlPointLine() self.scene.addItem(control) line = item.line() self.savedLine = line p1, p2 = map(item.mapToScene, (line.p1(), line.p2())) control.setLine(QLineF(p1, p2)) control.setFocusProxy(item) control.lineEdited.connect(self.__on_lineEdited) item.geometryChanged.connect(self.__on_lineGeometryChanged) self.item = item self.annotation = annotation self.control = control
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 __init__(self, parent=None, **kwargs): # type: (Optional[QGraphicsItem], Any) -> None super().__init__(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 # type: Optional[ControlPoint] if self.scene(): self.__installFilter() for p in self.__points: p.setFlag(QGraphicsItem.ItemIsFocusable) p.setFocusProxy(self)
def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: if self.arrow_item is None and \ (self.down_pos - event.scenePos()).manhattanLength() > \ QApplication.instance().startDragDistance(): annot = scheme.SchemeArrowAnnotation( point_to_tuple(self.down_pos), point_to_tuple(event.scenePos())) annot.set_color(self.color) item = self.scene.add_annotation(annot) self.arrow_item = item self.annotation = annot if self.arrow_item is not None: p1, p2 = map(self.arrow_item.mapFromScene, (self.down_pos, event.scenePos())) self.arrow_item.setLine(QLineF(p1, p2)) event.accept() return True
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
def add_annotation(self, scheme_annot): # type: (BaseSchemeAnnotation) -> Annotation """ 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 # type: ignore item.setPos(x, y) item.resize(w, h) item.setTextInteractionFlags(Qt.TextEditorInteraction) font = font_from_dict(scheme_annot.font, item.font()) # type: ignore 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))) # type: ignore 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
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)
def line(self): # type: () -> QLineF """ Return the arrow base line (`QLineF` in object coordinates). """ return QLineF(self.__line)