def __updateCurve(self): self.prepareGeometryChange() self.__boundingRect = None if self.sourceAnchor and self.sinkAnchor: source_pos = self.sourceAnchor.anchorScenePos() sink_pos = self.sinkAnchor.anchorScenePos() source_pos = self.curveItem.mapFromScene(source_pos) sink_pos = self.curveItem.mapFromScene(sink_pos) # Adaptive offset for the curve control points to avoid a # cusp when the two points have the same y coordinate # and are close together delta = source_pos - sink_pos dist = math.sqrt(delta.x() ** 2 + delta.y() ** 2) cp_offset = min(dist / 2.0, 60.0) # TODO: make the curve tangent orthogonal to the anchors path. path = QPainterPath() path.moveTo(source_pos) path.cubicTo(source_pos + QPointF(cp_offset, 0), sink_pos - QPointF(cp_offset, 0), sink_pos) self.curveItem.setCurvePath(path) self.sourceIndicator.setPos(source_pos) self.sinkIndicator.setPos(sink_pos) self.__updateText() else: self.setHoverState(False) self.curveItem.setPath(QPainterPath())
def __init__(self, parent, **kwargs): # type: (Optional[QGraphicsItem], Any) -> None super().__init__(parent, **kwargs) self.__parentNodeItem = None # type: Optional[NodeItem] self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.connectedBrush = QBrush(QColor("#9CACB4")) self.setBrush(self.normalBrush) self.shadow = QGraphicsDropShadowEffect( blurRadius=10, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0) ) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] # type: List[AnchorPoint] self.__pointPositions = [] # type: List[float] self.__fullStroke = QPainterPath() self.__dottedStroke = QPainterPath() self.__shape = None # type: Optional[QPainterPath]
def text_outline_path(doc: QTextDocument) -> QPainterPath: # return a path outlining all the text lines. margin = doc.documentMargin() path = QPainterPath() offset = min(margin, 2) for line in iter_lines(doc): rect = line.naturalTextRect() rect.translate(margin, margin) rect = rect.adjusted(-offset, -offset, offset, offset) p = QPainterPath() p.addRoundedRect(rect, 3, 3) path = path.united(p) return path
def __init__(self, parent, **kwargs): # type: (Optional[QGraphicsItem], Any) -> None super().__init__(parent, **kwargs) self.__parentNodeItem = None # type: Optional[NodeItem] self.setAcceptHoverEvents(True) self.setPen(QPen(Qt.NoPen)) self.normalBrush = QBrush(QColor("#CDD5D9")) self.normalHoverBrush = QBrush(QColor("#9CACB4")) self.connectedBrush = self.normalHoverBrush self.connectedHoverBrush = QBrush(QColor("#959595")) self.setBrush(self.normalBrush) self.__animationEnabled = False self.__hover = False # Does this item have any anchored links. self.anchored = False if isinstance(parent, NodeItem): self.__parentNodeItem = parent else: self.__parentNodeItem = None self.__anchorPath = QPainterPath() self.__points = [] # type: List[AnchorPoint] self.__pointPositions = [] # type: List[float] self.__fullStroke = QPainterPath() self.__dottedStroke = QPainterPath() self.__shape = None # type: Optional[QPainterPath] self.shadow = QGraphicsDropShadowEffect( blurRadius=0, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0), ) # self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) shadowitem = GraphicsPathObject(self, objectName="shadow-shape-item") shadowitem.setPen(Qt.NoPen) shadowitem.setBrush(QBrush(QColor(SHADOW_COLOR))) shadowitem.setGraphicsEffect(self.shadow) shadowitem.setFlag(QGraphicsItem.ItemStacksBehindParent) self.__shadow = shadowitem self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius", self) self.__blurAnimation.setDuration(50) self.__blurAnimation.finished.connect(self.__on_finished)
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 setupGraphics(self): """ Set up the graphics. """ shape_rect = QRectF(-24, -24, 48, 48) self.shapeItem = NodeBodyItem(self) self.shapeItem.setShapeRect(shape_rect) self.shapeItem.setAnimationEnabled(self.__animationEnabled) # Rect for widget's 'ears'. anchor_rect = QRectF(-31, -31, 62, 62) self.inputAnchorItem = SinkAnchorItem(self) input_path = QPainterPath() start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2 input_path.arcMoveTo(anchor_rect, start_angle) input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE) self.inputAnchorItem.setAnchorPath(input_path) self.outputAnchorItem = SourceAnchorItem(self) output_path = QPainterPath() start_angle = self.ANCHOR_SPAN_ANGLE / 2 output_path.arcMoveTo(anchor_rect, start_angle) output_path.arcTo(anchor_rect, start_angle, -self.ANCHOR_SPAN_ANGLE) self.outputAnchorItem.setAnchorPath(output_path) self.inputAnchorItem.hide() self.outputAnchorItem.hide() # Title caption item self.captionTextItem = NameTextItem(self) self.captionTextItem.setPlainText("") self.captionTextItem.setPos(0, 33) def iconItem(standard_pixmap): item = GraphicsIconItem(self, icon=standard_icon(standard_pixmap), iconSize=QSize(16, 16)) item.hide() return item self.errorItem = iconItem(QStyle.SP_MessageBoxCritical) self.warningItem = iconItem(QStyle.SP_MessageBoxWarning) self.infoItem = iconItem(QStyle.SP_MessageBoxInformation) self.prepareGeometryChange() self.__boundingRect = None
def anchorPath(self): # type: () -> QPainterPath """ Return the anchor path (:class:`QPainterPath`). This is a curve on which the anchor points lie. """ return QPainterPath(self.__anchorPath)
def setAnchorPath(self, path): # type: (QPainterPath) -> None """ Set the anchor's curve path as a :class:`QPainterPath`. """ self.__anchorPath = QPainterPath(path) # Create a stroke of the path. stroke_path = QPainterPathStroker() stroke_path.setCapStyle(Qt.RoundCap) # Shape is wider (bigger mouse hit area - should be settable) stroke_path.setWidth(25) self.prepareGeometryChange() self.__shape = stroke_path.createStroke(path) # The full stroke stroke_path.setWidth(3) self.__fullStroke = stroke_path.createStroke(path) # The dotted stroke (when not connected to anything) stroke_path.setDashPattern(Qt.DotLine) self.__dottedStroke = stroke_path.createStroke(path) if self.anchored: assert self.__fullStroke is not None self.setPath(self.__fullStroke) self.__shadow.setPath(self.__fullStroke) brush = self.connectedHoverBrush if self.__hover else self.connectedBrush self.setBrush(brush) else: assert self.__dottedStroke is not None self.setPath(self.__dottedStroke) self.__shadow.setPath(self.__dottedStroke) brush = self.normalHoverBrush if self.__hover else self.normalBrush self.setBrush(brush)
def __init__(self, parent): # type: (QGraphicsItem) -> None super().__init__(parent) self.setAcceptedMouseButtons(Qt.NoButton) self.setAcceptHoverEvents(True) self.__animationEnabled = False self.__hover = False self.__enabled = True self.__selected = False self.__shape = None # type: Optional[QPainterPath] self.__curvepath = QPainterPath() self.__curvepath_disabled = None # type: Optional[QPainterPath] self.__pen = self.pen() self.setPen(QPen(QBrush(QColor("#9CACB4")), 2.0)) self.shadow = QGraphicsDropShadowEffect(blurRadius=5, color=QColor(SHADOW_COLOR), offset=QPointF(0, 0)) self.setGraphicsEffect(self.shadow) self.shadow.setEnabled(False) self.__blurAnimation = QPropertyAnimation(self.shadow, b"blurRadius") self.__blurAnimation.setDuration(50) self.__blurAnimation.finished.connect(self.__on_finished)
def __updateFrame(self): # type: () -> None rect = self.geometry() rect.moveTo(0, 0) path = QPainterPath() path.addRect(rect) self.__framePathItem.setPath(path)
def setCurvePath(self, path): if path != self.__curvepath: self.prepareGeometryChange() self.__curvepath = QPainterPath(path) self.__curvepath_disabled = None self.__shape = None self.__update()
def _relayout(self): if self._root is None: return scale = self._height_scale_factor() base = scale * self._root.value.height self._layout = dendrogram_path(self._root, self.orientation, scaleh=scale) for node_geom in postorder(self._layout): node, geom = node_geom.value item = self._items[node] item.element = geom # the untransformed source path item.sourcePath = path_toQtPath(geom) r = item.sourcePath.boundingRect() if self.orientation == Left: r.setRight(base) elif self.orientation == Right: r.setLeft(0) elif self.orientation == Top: r.setBottom(base) else: r.setTop(0) hitarea = QPainterPath() hitarea.addRect(r) item.sourceAreaShape = hitarea item.setGeometryData(item.sourcePath, item.sourceAreaShape) item.setZValue(-node.value.height)
def _create_path(item, path): ppath = QPainterPath() if item.node.is_leaf: ppath.addRect(path.boundingRect().adjusted(-8, -4, 0, 4)) else: ppath.addPolygon(path) ppath = path_outline(ppath, width=-8) return ppath
def violin_shape(x, p): # type: (Sequence[float], Sequence[float]) -> QPainterPath points = [QPointF(pi, xi) for xi, pi in zip(x, p)] points += [QPointF(-pi, xi) for xi, pi in reversed(list(zip(x, p)))] poly = QPolygonF(points) path = QPainterPath() path.addPolygon(poly) return path
def test_shapeFromPath(self): path = QPainterPath() path.addRect(10, 10, 20, 20) pen = QPen(QColor("#FFF"), 2.0) path = shapeFromPath(path, pen) self.assertGreaterEqual(area(path.controlPointRect()), (20 + 2.0)**2)
def __init__(self, parent=None, **kwargs): QGraphicsObject.__init__(self, parent, **kwargs) self.setFlag(QGraphicsObject.ItemSendsGeometryChanges) self.__path = QPainterPath() self.__brush = QBrush(Qt.NoBrush) self.__pen = QPen() self.__boundingRect = None
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 redraw_path(self): self.path = QPainterPath() for segment in self.segment(self.data()): if self.fitted: self.draw_cubic_path(segment) else: self.draw_normal_path(segment) self._item.setPath(self.graph_transform().map(self.path)) self._item.setPen(self.pen())
def move_label(label, frm, to): label.setX(to) to += t_box.width() / 2 path = QPainterPath() path.lineTo(0, 4) path.lineTo(to - frm, 4) path.lineTo(to - frm, 8) p = QGraphicsPathItem(path) p.setPos(frm, 12) labels.addToGroup(p)
def setShapeRect(self, rect): """ Set the item's shape `rect`. The item should be confined within this rect. """ path = QPainterPath() path.addEllipse(rect) self.setPath(path) self.__shapeRect = rect
def _draw_hexagon(): path = QPainterPath() s = 0.5 / (np.sqrt(3) / 2) path.moveTo(-0.5, -s / 2) path.lineTo(-0.5, s / 2) path.lineTo(0, s) path.lineTo(0.5, s / 2) path.lineTo(0.5, -s / 2) path.lineTo(0, -s) path.lineTo(-0.5, -s / 2) return path
def _contains_point(item: pg.FillBetweenItem, point: QPointF) -> bool: curve1, curve2 = item.curves x_data_lower, y_data_lower = curve1.curve.getData() x_data_upper, y_data_upper = curve2.curve.getData() pts = [QPointF(x, y) for x, y in zip(x_data_lower, y_data_lower)] pts += [QPointF(x, y) for x, y in reversed(list(zip(x_data_upper, y_data_upper)))] pts += pts[:1] path = QPainterPath() path.addPolygon(QPolygonF(pts)) return path.contains(point)
def test_graphicspathobject(self): obj = GraphicsPathObject() path = QPainterPath() obj.setFlag(GraphicsPathObject.ItemIsMovable) path.addEllipse(20, 20, 50, 50) obj.setPath(path) self.assertEqual(obj.path(), path) self.assertTrue(obj.path() is not path, msg="setPath stores the path not a copy") brect = obj.boundingRect() self.assertTrue(brect.contains(path.boundingRect())) with self.assertRaises(TypeError): obj.setPath("This is not a path") brush = QBrush(QColor("#ffbb11")) obj.setBrush(brush) self.assertEqual(obj.brush(), brush) self.assertTrue(obj.brush() is not brush, "setBrush stores the brush not a copy") pen = QPen(QColor("#FFFFFF"), 1.4) obj.setPen(pen) self.assertEqual(obj.pen(), pen) self.assertTrue(obj.pen() is not pen, "setPen stores the pen not a copy") brect = obj.boundingRect() self.assertGreaterEqual(area(brect), (50 + 1.4 * 2)**2) self.assertIsInstance(obj.shape(), QPainterPath) positions = [] obj.positionChanged[QPointF].connect(positions.append) pos = QPointF(10, 10) obj.setPos(pos) self.assertEqual(positions, [pos]) self.scene.addItem(obj) self.view.show() self.qWait()
def __init__(self, id, title='', title_above=False, title_location=AxisMiddle, line=None, arrows=0, plot=None, bounds=None): QGraphicsItem.__init__(self) self.setFlag(QGraphicsItem.ItemHasNoContents) self.setZValue(AxisZValue) self.id = id self.title = title self.title_location = title_location self.data_line = line self.plot = plot self.graph_line = None self.size = None self.scale = None self.tick_length = (10, 5, 0) self.arrows = arrows self.title_above = title_above self.line_item = QGraphicsLineItem(self) self.title_item = QGraphicsTextItem(self) self.end_arrow_item = None self.start_arrow_item = None self.show_title = False self.scale = None path = QPainterPath() path.setFillRule(Qt.WindingFill) path.moveTo(0, 3.09) path.lineTo(0, -3.09) path.lineTo(9.51, 0) path.closeSubpath() self.arrow_path = path self.label_items = [] self.label_bg_items = [] self.tick_items = [] self._ticks = [] self.zoom_transform = QTransform() self.labels = None self.values = None self._bounds = bounds self.auto_range = None self.auto_scale = True self.zoomable = False self.update_callback = None self.max_text_width = 50 self.text_margin = 5 self.always_horizontal_text = False
def test_layout(self): one_desc, negate_desc, cons_desc = self.widget_desc() one_item = NodeItem() one_item.setWidgetDescription(one_desc) one_item.setPos(0, 150) self.scene.add_node_item(one_item) cons_item = NodeItem() cons_item.setWidgetDescription(cons_desc) cons_item.setPos(200, 0) self.scene.add_node_item(cons_item) negate_item = NodeItem() negate_item.setWidgetDescription(negate_desc) negate_item.setPos(200, 300) self.scene.add_node_item(negate_item) link = LinkItem() link.setSourceItem(one_item) link.setSinkItem(negate_item) self.scene.add_link_item(link) link = LinkItem() link.setSourceItem(one_item) link.setSinkItem(cons_item) self.scene.add_link_item(link) layout = AnchorLayout() self.scene.addItem(layout) self.scene.set_anchor_layout(layout) layout.invalidateNode(one_item) layout.activate() p1, p2 = one_item.outputAnchorItem.anchorPositions() self.assertTrue(p1 > p2) self.scene.node_item_position_changed.connect(layout.invalidateNode) path = QPainterPath() path.addEllipse(125, 0, 50, 300) def advance(): t = time.process_time() cons_item.setPos(path.pointAtPercent(t % 1.0)) negate_item.setPos(path.pointAtPercent((t + 0.5) % 1.0)) timer = QTimer(negate_item, interval=5) timer.start() timer.timeout.connect(advance) self.qWait() timer.stop()
def test_layout(self): file_desc, disc_desc, bayes_desc = self.widget_desc() file_item = NodeItem() file_item.setWidgetDescription(file_desc) file_item.setPos(0, 150) self.scene.add_node_item(file_item) bayes_item = NodeItem() bayes_item.setWidgetDescription(bayes_desc) bayes_item.setPos(200, 0) self.scene.add_node_item(bayes_item) disc_item = NodeItem() disc_item.setWidgetDescription(disc_desc) disc_item.setPos(200, 300) self.scene.add_node_item(disc_item) link = LinkItem() link.setSourceItem(file_item) link.setSinkItem(disc_item) self.scene.add_link_item(link) link = LinkItem() link.setSourceItem(file_item) link.setSinkItem(bayes_item) self.scene.add_link_item(link) layout = AnchorLayout() self.scene.addItem(layout) self.scene.set_anchor_layout(layout) layout.invalidateNode(file_item) layout.activate() p1, p2 = file_item.outputAnchorItem.anchorPositions() self.assertGreater(p1, p2) self.scene.node_item_position_changed.connect(layout.invalidateNode) path = QPainterPath() path.addEllipse(125, 0, 50, 300) def advance(): t = time.clock() bayes_item.setPos(path.pointAtPercent(t % 1.0)) disc_item.setPos(path.pointAtPercent((t + 0.5) % 1.0)) self.singleShot(20, advance) advance() self.app.exec_()
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 setPath(self, path): """Set the items `path` (:class:`QPainterPath`). """ if not isinstance(path, QPainterPath): raise TypeError("%r, 'QPainterPath' expected" % type(path)) if self.__path != path: self.prepareGeometryChange() # Need to store a copy of object so the shape can't be mutated # without properly updating the geometry. self.__path = QPainterPath(path) self.__boundingRect = None self.update()
class ClusterGraphicsItem(QGraphicsPathItem): #: The untransformed source path in 'dendrogram' logical coordinate #: system sourcePath = QPainterPath() # type: QPainterPath sourceAreaShape = QPainterPath() # type: QPainterPath __shape = None # type: Optional[QPainterPath] __boundingRect = None # type: Optional[QRectF] #: An extended path describing the full mouse hit area #: (extends all the way to the base of the dendrogram) __mouseAreaShape = QPainterPath() # type: QPainterPath def setGeometryData(self, path, hitArea): # type: (QPainterPath, QPainterPath) -> None """ Set the geometry (path) and the mouse hit area (hitArea) for this item. """ super().setPath(path) self.prepareGeometryChange() self.__boundingRect = self.__shape = None self.__mouseAreaShape = hitArea def shape(self): # type: () -> QPainterPath if self.__shape is None: path = super().shape() # type: QPainterPath self.__shape = path.united(self.__mouseAreaShape) return self.__shape def boundingRect(self): # type: () -> QRectF if self.__boundingRect is None: sh = self.shape() pw = self.pen().widthF() / 2.0 self.__boundingRect = sh.boundingRect().adjusted( -pw, -pw, pw, pw) return self.__boundingRect
def venn_intersection(paths, key): if not any(key): return QPainterPath() # first take the intersection of all included paths path = reduce(QPainterPath.intersected, (path for path, included in zip(paths, key) if included)) # subtract all the excluded sets (i.e. take the intersection # with the excluded set complements) path = reduce(QPainterPath.subtracted, (path for path, included in zip(paths, key) if not included), path) return path