def plot_vert_line_graph(self, qp, x_line, color, c, arrow_up=False, arrow_down=False): if x_line < self._start_date or x_line > self._end_date: return x_line -= self._start_date qp.save() qp.setPen(color) qp.setBrush(color) qp.setRenderHint(QPainter.Antialiasing) arrowSize = 2.0 x, y = self.origGraph(c) line = QLineF(x + self.convX(x_line), y + 10, x + self.convX(x_line), y + 50) qp.drawLine(line) if arrow_up: arrowP1 = line.p1() + QPointF(arrowSize, arrowSize * 3) arrowP2 = line.p1() + QPointF(-arrowSize, arrowSize * 3) qp.drawLine(line.p1(), arrowP1) qp.drawLine(line.p1(), arrowP2) if arrow_down: arrowP1 = line.p2() + QPointF(arrowSize, - arrowSize * 3) arrowP2 = line.p2() + QPointF(-arrowSize, - arrowSize * 3) qp.drawLine(line.p2(), arrowP1) qp.drawLine(line.p2(), arrowP2) qp.restore()
def setLine(self, line): """ Set the arrow base line (a `QLineF` in object coordinates). """ if self.__line != line: self.__line = 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.__line = line # parent item coordinate system geom.translate(self.pos()) self.setGeometry(geom)
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)
def adjust(self): if not self.source or not self.dest: return line = QLineF(self.mapFromItem(self.source, 0, 0), self.mapFromItem(self.dest, 0, 0)) length = line.length() self.prepareGeometryChange() if length > 20.0: edgeOffset = QPointF((line.dx() * 10) / length, (line.dy() * 10) / length) self.sourcePoint = line.p1() + edgeOffset self.destPoint = line.p2() - edgeOffset else: self.sourcePoint = line.p1() self.destPoint = line.p1()
def drawPath(self, startPoint, endPoint): path = QPainterPath() one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2 two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2 path.moveTo(startPoint) angle = math.pi / 2 bLine1 = QLineF() bLine1.setP1(startPoint) if startPoint.x() > endPoint.x(): dist = startPoint.x() - endPoint.x() one = (bLine1.p1() + QPointF(math.sin(angle) * dist, math.cos(angle) * dist)) bLine1.setP1(endPoint) two = (bLine1.p1() + QPointF(math.sin(angle) * dist, math.cos(angle) * dist)) path.cubicTo(one, two, endPoint) return path, QLineF(one, two)
def drawToolButtonMenuIndicator(self, option, painter, widget=None): arrow_rect = self.proxy().subControlRect(QStyle.CC_ToolButton, option, QStyle.SC_ToolButtonMenu, widget) text_color = option.palette.color(QPalette.WindowText if option.state & QStyle.State_AutoRaise else QPalette.ButtonText) button_color = option.palette.color(QPalette.Button) background_color = self.background_color(button_color, 0.5) painter.save() # draw separating vertical line if option.state & (QStyle.State_On|QStyle.State_Sunken): top_offset, bottom_offset = 4, 3 else: top_offset, bottom_offset = 2, 2 if option.direction == Qt.LeftToRight: separator_line = QLineF(arrow_rect.x()-3, arrow_rect.top()+top_offset, arrow_rect.x()-3, arrow_rect.bottom()-bottom_offset) else: separator_line = QLineF(arrow_rect.right()+3, arrow_rect.top()+top_offset, arrow_rect.right()+3, arrow_rect.bottom()-bottom_offset) light_gradient = QLinearGradient(separator_line.p1(), separator_line.p2()) light_gradient.setColorAt(0.0, ColorScheme.shade(self.background_top_color(button_color), ColorScheme.LightShade, 0.0)) light_gradient.setColorAt(1.0, ColorScheme.shade(self.background_bottom_color(button_color), ColorScheme.MidlightShade, 0.5)) separator_color = ColorScheme.shade(self.background_bottom_color(button_color), ColorScheme.MidShade, 0.0) painter.setRenderHint(QPainter.Antialiasing, False) painter.setPen(QPen(light_gradient, 1)) painter.drawLine(separator_line.translated(-1, 0)) painter.drawLine(separator_line.translated(+1, 0)) painter.setPen(QPen(separator_color, 1)) painter.drawLine(separator_line) # draw arrow arrow = QPolygonF([QPointF(-3, -1.5), QPointF(0.5, 2.5), QPointF(4, -1.5)]) if option.direction == Qt.LeftToRight: arrow.translate(-2, 1) else: arrow.translate(+2, 1) pen_thickness = 1.6 painter.setRenderHint(QPainter.Antialiasing, True) painter.translate(arrow_rect.center()) painter.translate(0, +1) painter.setPen(QPen(self.calc_light_color(background_color), pen_thickness, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawPolyline(arrow) painter.translate(0, -1) painter.setPen(QPen(self.deco_color(background_color, text_color), pen_thickness, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawPolyline(arrow) painter.restore()
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
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
def drawPath(self, startPoint, endPoint): path = QPainterPath() one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2 two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2 path.moveTo(startPoint) if startPoint.x() > endPoint.x(): dist = (startPoint.x() - endPoint.x()) * 2 tLine = QLineF((dist / 2), 0.0, -(dist / 2), 0.0).translated(QLineF(startPoint, endPoint).pointAt(0.5)) one = tLine.p1() two = tLine.p2() path.cubicTo(one, two, endPoint) self.__path = path return path, QLineF(one, two)
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 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 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)
class EEdge(QGraphicsObject): def __init__(self, head, tail, uuid): QGraphicsObject.__init__(self) if not issubclass(head.__class__, dict) and not isinstance(tail.__class__, dict): raise AttributeError self.setZValue(0.0) self.__kId = uuid self.__head = head self.__tail = tail if head[ENode.kGuiAttributeType].match(EAttribute.kTypeInput): self.__head = tail self.__tail = head self.__head[ENode.kGuiAttributeParent].onMove.connect(self.update) self.__tail[ENode.kGuiAttributeParent].onMove.connect(self.update) self.__headPoint = QPointF(0.0, 0.0) self.__tailPoint = QPointF(0.0, 0.0) self.__pen = QPen(QColor(43, 43, 43), 2, Qt.SolidLine) self.update() @property def Id(self): return self.__kId @property def Head(self): return self.__head @property def Tail(self): return self.__tail def pen(self): return self.__pen def setPen(self, pen): if not isinstance(pen, QPen): raise AttributeError self.__pen = pen def update(self): QGraphicsObject.prepareGeometryChange(self) self.__headPoint = self.mapFromItem(self.__head[ENode.kGuiAttributeParent], self.__head[ENode.kGuiAttributePlug]) self.__tailPoint = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent], self.__tail[ENode.kGuiAttributePlug]) self.__headOffsetLine = QLineF(self.__headPoint, QPointF(self.__headPoint.x() + 15, self.__headPoint.y())) self.__tailOffsetLine = QLineF(self.__tailPoint, QPointF(self.__tailPoint.x() - 15, self.__tailPoint.y())) line = QLineF(self.__headPoint, self.__tailPoint) self.__line = line def boundingRect(self): extra = (self.pen().width() * 64) / 2 return QRectF(self.__line.p1(), QSizeF(self.__line.p2().x() - self.__line.p1().x(), self.__line.p2().y() - self.__line.p1().y())).normalized().adjusted(-extra, -extra, extra, extra) def shape(self): return QGraphicsObject.shape(self) def drawPath(self, startPoint, endPoint): path = QPainterPath() one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2 two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2 path.moveTo(startPoint) angle = math.pi / 2 bLine1 = QLineF() bLine1.setP1(startPoint) if startPoint.x() > endPoint.x(): dist = startPoint.x() - endPoint.x() one = (bLine1.p1() + QPointF(math.sin(angle) * dist, math.cos(angle) * dist)) bLine1.setP1(endPoint) two = (bLine1.p1() + QPointF(math.sin(angle) * dist, math.cos(angle) * dist)) path.cubicTo(one, two, endPoint) return path, QLineF(one, two) def paint(self, painter, option, widget=None): painter.setPen(self.pen()) headCenter = self.mapFromItem(self.__head[ENode.kGuiAttributeParent], self.__head[ENode.kGuiAttributeParent].boundingRect().center()) tailCenter = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent], self.__tail[ENode.kGuiAttributeParent].boundingRect().center()) centerPoint = QLineF(headCenter, tailCenter).pointAt(0.5) centerPoint.setX(self.__headOffsetLine.p2().x()) lineFromHead = QLineF(self.__headOffsetLine.p2(), centerPoint) centerPoint.setX(self.__tailOffsetLine.p2().x()) lineFromTail = QLineF(self.__tailOffsetLine.p2(), centerPoint) painter.drawPath(self.drawPath(self.__headOffsetLine.p1(), self.__tailOffsetLine.p1())[0])
def paint(self, painter, option, widget): if not self.source or not self.dest: return # Draw the line itself. line = QLineF(self.sourcePoint, self.destPoint) if line.length() == 0.0: return palette = QPalette() self.setZValue(self.state) if self.state == 3: pen = QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) if self.state == 2: pen = QPen(Qt.red, 2, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin) elif self.state == 1: pen = QPen(palette.color(QPalette.Disabled, QPalette.WindowText), 0, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) elif self.state == 0: pen = QPen() pen.setBrush(QBrush(Qt.NoBrush)) painter.setPen(pen) painter.drawLine(line) angle = math.acos(line.dx() / line.length()) if line.dy() >= 0: angle = Edge.TwoPi - angle # draw arrowheads if self.state == 2 or self.state == 3: # Draw the arrows if there's enough room. sourceArrowP1 = self.sourcePoint + QPointF( math.sin(angle + Edge.Pi / 3) * self.arrowSize, math.cos(angle + Edge.Pi / 3) * self.arrowSize) sourceArrowP2 = self.sourcePoint + QPointF( math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize, math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize) destArrowP1 = self.destPoint + QPointF( math.sin(angle - Edge.Pi / 3) * self.arrowSize, math.cos(angle - Edge.Pi / 3) * self.arrowSize) destArrowP2 = self.destPoint + QPointF( math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize, math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize) painter.setPen( QPen(Qt.red, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.setBrush(QBrush(Qt.red, Qt.SolidPattern)) painter.drawPolygon( QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2])) #painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2])) if self.state > 0 and self.source > self.dest: point = QPointF((self.sourcePoint.x() + self.destPoint.x()) / 2, (self.sourcePoint.y() + self.destPoint.y()) / 2) point = QPointF(point.x() + math.sin(angle) * 16, point.y() + math.cos(angle) * 16) painter.drawText(point, self.text)
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()
def render(o): path = QPainterPath() # unit_x gives offset and direction of the x base vector. Start and end should be the grid points. # move the endpoints inwards an unnoticable bit, so that the intersection detector # won't trip on the common endpoint. u_x = QLineF(o.unit_x.pointAt(0.0001), o.unit_x.pointAt(0.9999)) path.moveTo(u_x.p1()) if o.is_straight: path.lineTo(u_x.p2()) return path if o.flipped: u_x = QLineF(u_x.p2(), u_x.p1()) u_y = u_x.normalVector() # move y unit to start at (0,0). u_y.translate(-u_y.p1()) scaling = o.length_base / u_x.length() * o.size_correction if o.basewidth * scaling > 0.8: # Plug is too large for the edge length. Make it smaller. scaling = 0.8 / o.basewidth # some magic numbers here... carefully fine-tuned, better leave them as they are. ends_ctldist = 0.4 #base_lcdist = 0.1 * scaling base_ucdist = 0.05 * scaling knob_lcdist = 0.6 * o.knobsize * scaling knob_ucdist = 0.8 * o.knobsize * scaling # set up piece -- here is where the really interesting stuff happens. # We will work from the ends inwards, so that symmetry counterparts are adjacent. # The QLine.pointAt function is used to transform everything into the coordinate # space defined by the us. # -- end points r1y = ends_ctldist * o.basepos * dsin(o.startangle) q6y = ends_ctldist * (1. - o.basepos) * dsin(o.endangle) p1 = u_x.p1() p6 = u_x.p2() r1 = u_x.pointAt( ends_ctldist * o.basepos * dcos(o.startangle)) + u_y.pointAt(r1y) q6 = u_x.pointAt(1. - ends_ctldist * (1. - o.basepos) * dcos(o.endangle)) + u_y.pointAt(q6y) # -- base points p2x = o.basepos - 0.5 * o.basewidth * scaling p5x = o.basepos + 0.5 * o.basewidth * scaling if p2x < 0.1 or p5x > 0.9: # knob to large. center knob on the edge. (o.basewidth * scaling < 0.8 -- see above) p2x = 0.5 - 0.5 * o.basewidth * scaling p5x = 0.5 + 0.5 * o.basewidth * scaling #base_y = r1y > q6y ? r1y : q6y #base_y = 0.5*(r1y + q6y) base_y = -o.baseroundness * ends_ctldist * min(p2x, 1. - p5x) if base_y > 0: base_y = 0 base_lcy = base_y * 2.0 base_y += base_ucdist / 2 base_lcy -= base_ucdist / 2 #base_lcy = r1y #if (q6y < r1y): base_lcy = q6y # at least -base_ucdist from base_y #if (base_lcy > base_y - base_ucdist): base_lcy = base_y-base_ucdist q2 = u_x.pointAt(p2x) + u_y.pointAt(base_lcy) r5 = u_x.pointAt(p5x) + u_y.pointAt(base_lcy) p2 = u_x.pointAt(p2x) + u_y.pointAt(base_y) p5 = u_x.pointAt(p5x) + u_y.pointAt(base_y) r2 = u_x.pointAt(p2x) + u_y.pointAt(base_y + base_ucdist) q5 = u_x.pointAt(p5x) + u_y.pointAt(base_y + base_ucdist) if o._is_plugless: if not o.flipped: path.cubicTo(r1, q2, p2) path.cubicTo(r2, q5, p5) path.cubicTo(r5, q6, p6) else: path.cubicTo(q6, r5, p5) path.cubicTo(q5, r2, p2) path.cubicTo(q2, r1, p1) return path # -- knob points p3x = p2x - o.knobsize * scaling * dsin(o.knobangle - o.knobtilt) p4x = p5x + o.knobsize * scaling * dsin(o.knobangle + o.knobtilt) # for the y coordinate, knobtilt sign was swapped. Knobs look better this way... # like x offset from base points y, but that is 0. p3y = o.knobsize * scaling * dcos(o.knobangle + o.knobtilt) + base_y p4y = o.knobsize * scaling * dcos(o.knobangle - o.knobtilt) + base_y q3 = u_x.pointAt(p3x) + u_y.pointAt(p3y - knob_lcdist) r4 = u_x.pointAt(p4x) + u_y.pointAt(p4y - knob_lcdist) p3 = u_x.pointAt(p3x) + u_y.pointAt(p3y) p4 = u_x.pointAt(p4x) + u_y.pointAt(p4y) r3 = u_x.pointAt(p3x) + u_y.pointAt(p3y + knob_ucdist) q4 = u_x.pointAt(p4x) + u_y.pointAt(p4y + knob_ucdist) # done setting up. construct path. # if flipped, add points in reverse. if not o.flipped: path.cubicTo(r1, q2, p2) path.cubicTo(r2, q3, p3) path.cubicTo(r3, q4, p4) path.cubicTo(r4, q5, p5) path.cubicTo(r5, q6, p6) else: path.cubicTo(q6, r5, p5) path.cubicTo(q5, r4, p4) path.cubicTo(q4, r3, p3) path.cubicTo(q3, r2, p2) path.cubicTo(q2, r1, p1) return path
class EEdge(QGraphicsObject): def __init__(self, head, tail, uuid): QGraphicsObject.__init__(self) if not issubclass(head.__class__, dict) and not isinstance( tail.__class__, dict): raise AttributeError self.setZValue(0.0) self.__kId = uuid self.__head = head self.__tail = tail if head[ENode.kGuiAttributeType].match(EAttribute.kTypeInput): self.__head = tail self.__tail = head self.__head[ENode.kGuiAttributeParent].onMove.connect(self.update) self.__tail[ENode.kGuiAttributeParent].onMove.connect(self.update) self.__headPoint = QPointF(0.0, 0.0) self.__tailPoint = QPointF(0.0, 0.0) self.__pen = QPen(QColor(43, 43, 43), 2, Qt.SolidLine) self.update() @property def Id(self): return self.__kId @property def Head(self): return self.__head @property def Tail(self): return self.__tail def pen(self): return self.__pen def setPen(self, pen): if not isinstance(pen, QPen): raise AttributeError self.__pen = pen def update(self): QGraphicsObject.prepareGeometryChange(self) self.__headPoint = self.mapFromItem( self.__head[ENode.kGuiAttributeParent], self.__head[ENode.kGuiAttributePlug]) self.__tailPoint = self.mapFromItem( self.__tail[ENode.kGuiAttributeParent], self.__tail[ENode.kGuiAttributePlug]) self.__headOffsetLine = QLineF( self.__headPoint, QPointF(self.__headPoint.x() + 15, self.__headPoint.y())) self.__tailOffsetLine = QLineF( self.__tailPoint, QPointF(self.__tailPoint.x() - 15, self.__tailPoint.y())) line = QLineF(self.__headPoint, self.__tailPoint) self.__line = line def boundingRect(self): extra = (self.pen().width() * 64) / 2 return QRectF( self.__line.p1(), QSizeF(self.__line.p2().x() - self.__line.p1().x(), self.__line.p2().y() - self.__line.p1().y())).normalized().adjusted( -extra, -extra, extra, extra) def shape(self): return QGraphicsObject.shape(self) def drawPath(self, startPoint, endPoint): path = QPainterPath() one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2 two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2 path.moveTo(startPoint) angle = math.pi / 2 bLine1 = QLineF() bLine1.setP1(startPoint) if startPoint.x() > endPoint.x(): dist = startPoint.x() - endPoint.x() one = (bLine1.p1() + QPointF(math.sin(angle) * dist, math.cos(angle) * dist)) bLine1.setP1(endPoint) two = (bLine1.p1() + QPointF(math.sin(angle) * dist, math.cos(angle) * dist)) path.cubicTo(one, two, endPoint) return path, QLineF(one, two) def paint(self, painter, option, widget=None): painter.setPen(self.pen()) headCenter = self.mapFromItem( self.__head[ENode.kGuiAttributeParent], self.__head[ENode.kGuiAttributeParent].boundingRect().center()) tailCenter = self.mapFromItem( self.__tail[ENode.kGuiAttributeParent], self.__tail[ENode.kGuiAttributeParent].boundingRect().center()) centerPoint = QLineF(headCenter, tailCenter).pointAt(0.5) centerPoint.setX(self.__headOffsetLine.p2().x()) lineFromHead = QLineF(self.__headOffsetLine.p2(), centerPoint) centerPoint.setX(self.__tailOffsetLine.p2().x()) lineFromTail = QLineF(self.__tailOffsetLine.p2(), centerPoint) painter.drawPath( self.drawPath(self.__headOffsetLine.p1(), self.__tailOffsetLine.p1())[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 = title_pos + (v.p2() - v.p1())*(offset + QFontMetrics(self.title_item.font()).height()) else: title_pos = 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 * w/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))
def crop_line(self, line, line_point): global_rect = self.globalBoundingRect() # Go to local coordinate system - ellipse equations assume ellipse is centered on (0,0) local_trans = global_rect.center() local_line = QLineF(line.p1() - local_trans, line.p2() - local_trans) if(local_line.dx() == 0): return line # Solve line equation e_a = ((local_line.p2().y() - local_line.p1().y()) / (local_line.p2().x() - local_line.p1().x())) e_b = local_line.p1().y() - e_a * local_line.p1().x() # ellipse params e_c = global_rect.width()/2 e_d = global_rect.height()/2 # check condition if(e_c * e_d == 0): return line # precalculate things that are used more than once # a^2, b^2 ... ak = math.pow(e_a, 2) bk = math.pow(e_b, 2) ck = math.pow(e_c, 2) dk = math.pow(e_d, 2) # check another condition if((ak * ck + dk) == 0): return line # a^2*c^2, c^2*d^2 akck = ak * ck ckdk = ck * dk # a*b*c^2 abck = e_a*e_b*ck # parts of denomiator and numerator of x denom = (akck + dk) numer = math.sqrt(ck*dk*(akck-bk+dk)) # Decide which points to take xrel = (line.p1().x() > line.p2().x()) yrel = (line.p1().y() > line.p2().y()) if(line_point != 0): xrel = not xrel yrel = not yrel if((xrel and yrel) or (xrel and not yrel)): x1 = (-numer - abck) / denom y1 = (e_b*dk - e_a*math.sqrt(-ckdk*(-akck+bk-dk))) / denom intersectionPoint = QPointF(x1, y1) elif((not xrel and yrel) or (not xrel and not yrel)): x2 = (numer - abck) / denom y2 = -(e_b*dk - e_a*math.sqrt(-ckdk*(-akck+bk-dk))) / denom intersectionPoint = QPointF(x2, y2) # Go back to global coordinate system intersectionPoint = intersectionPoint + local_trans if(line_point == 0): return QLineF(intersectionPoint, line.p2()) else: return QLineF(line.p1(), intersectionPoint) return line
class EEdge(QGraphicsObject): def __init__(self, head, tail, uuid, arrowed=False): QGraphicsObject.__init__(self) self.__arrowed = arrowed if not issubclass(head.__class__, dict) and not isinstance(tail.__class__, dict): raise AttributeError self.setZValue(0.0) self.__kId = uuid self.__head = head self.__tail = tail self.__path = QPainterPath() self.__headPoint = QPointF(0.0, 0.0) self.__tailPoint = QPointF(0.0, 0.0) self.__head[ENode.kGuiAttributeParent].onMove.connect(self.update) self.__tail[ENode.kGuiAttributeParent].onMove.connect(self.update) self.__pen = QPen(QColor(43, 43, 43), 2, Qt.SolidLine) self.update() @property def Id(self): return self.__kId @property def Line(self): return QLineF(self.__headPoint, self.__tailPoint) @property def Head(self): return self.__head @Head.setter def Head(self, newHead): self.__head = newHead @property def Tail(self): return self.__tail @Tail.setter def Tail(self, newTail): self.__tail = newTail def pen(self): return self.__pen def setPen(self, pen): if not isinstance(pen, QPen): raise AttributeError self.__pen = pen def update(self): QGraphicsObject.prepareGeometryChange(self) self.__headPoint = self.mapFromItem(self.__head[ENode.kGuiAttributeParent], self.__head[ENode.kGuiAttributePlug]) self.__tailPoint = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent], self.__tail[ENode.kGuiAttributePlug]) self.__headOffsetLine = QLineF(self.__headPoint, QPointF(self.__headPoint.x() + 15, self.__headPoint.y())) self.__tailOffsetLine = QLineF(self.__tailPoint, QPointF(self.__tailPoint.x() - 15, self.__tailPoint.y())) line = QLineF(self.__headPoint, self.__tailPoint) self.__line = line def boundingRect(self): extra = (self.pen().width() * 64) / 2 return QRectF(self.__line.p1(), QSizeF(self.__line.p2().x() - self.__line.p1().x(), self.__line.p2().y() - self.__line.p1().y())).normalized().adjusted(-extra, -extra, extra, extra) def shape(self): if self.__arrowed: return QGraphicsObject.shape(self) return QPainterPath(self.__path) def getIntersectPoint(self, polygon, point1, point2): p1 = polygon[0] + point1 intersectPoint = QPointF() for i in polygon: p2 = i + point2 polyLine = QLineF(p1, p2) intersectType = polyLine.intersect(QLineF(point1, point2), intersectPoint) if intersectType == QLineF.BoundedIntersection: break p1 = p2 return intersectPoint def getArrow(self, line): Pi = math.pi TwoPi = 2.0 * Pi arrowSize = 14 if line.length() > 0: angle = math.acos(line.dx() / line.length()) if line.dy() >= 0: angle = TwoPi - angle sourceArrowP1 = line.p1() + QPointF(math.sin(angle + Pi / 3) * arrowSize, math.cos(angle + Pi / 3) * arrowSize) sourceArrowP2 = line.p1() + QPointF(math.sin(angle + Pi - Pi / 3) * arrowSize, math.cos(angle + Pi - Pi / 3) * arrowSize) destinationArrowP1 = line.p2() + QPointF(math.sin(angle - Pi / 3) * arrowSize, math.cos(angle - Pi / 3) * arrowSize) destinationArrowP2 = line.p2() + QPointF(math.sin(angle - Pi + Pi / 3) * arrowSize, math.cos(angle - Pi + Pi / 3) * arrowSize) arrows = [QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]), QPolygonF([line.p2(), destinationArrowP1, destinationArrowP2])] return arrows[0] return QPolygonF() def drawPath(self, startPoint, endPoint): path = QPainterPath() one = (QPointF(endPoint.x(), startPoint.y()) + startPoint) / 2 two = (QPointF(startPoint.x(), endPoint.y()) + endPoint) / 2 path.moveTo(startPoint) if startPoint.x() > endPoint.x(): dist = (startPoint.x() - endPoint.x()) * 2 tLine = QLineF((dist / 2), 0.0, -(dist / 2), 0.0).translated(QLineF(startPoint, endPoint).pointAt(0.5)) one = tLine.p1() two = tLine.p2() path.cubicTo(one, two, endPoint) self.__path = path return path, QLineF(one, two) def paint(self, painter, option, widget=None): painter.setPen(self.pen()) if not self.__arrowed: headCenter = self.mapFromItem(self.__head[ENode.kGuiAttributeParent], self.__head[ENode.kGuiAttributeParent].boundingRect().center()) tailCenter = self.mapFromItem(self.__tail[ENode.kGuiAttributeParent], self.__tail[ENode.kGuiAttributeParent].boundingRect().center()) centerPoint = QLineF(headCenter, tailCenter).pointAt(0.5) centerPoint.setX(self.__headOffsetLine.p2().x()) centerPoint.setX(self.__tailOffsetLine.p2().x()) painter.drawPath(self.drawPath(self.__headOffsetLine.p1(), self.__tailOffsetLine.p1())[0]) else: painter.drawLine(self.__line) painter.setPen(Qt.NoPen) painter.setBrush(QColor(43, 43, 43)) headCutPoint = self.getIntersectPoint(self.__head[ENode.kGuiAttributeParent].Polygon, self.__line.p1(), self.__line.p2()) tailCutPoint = self.getIntersectPoint(self.__tail[ENode.kGuiAttributeParent].Polygon, self.__line.p2(), self.__line.p1()) painter.drawPolygon(self.getArrow(QLineF(headCutPoint, tailCutPoint)))
class SliderLine(QGraphicsObject): """A movable slider line.""" valueChanged = Signal(float) linePressed = Signal() lineMoved = Signal() lineReleased = Signal() rangeChanged = Signal(float, float) 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() self._pen = QPen() super().__init__(parent, **kwargs) self.setAcceptedMouseButtons(Qt.LeftButton) self.setPen(make_pen(brush=QColor(50, 50, 50), width=1, cosmetic=True)) if self._orientation == Qt.Vertical: self.setCursor(Qt.SizeVerCursor) else: self.setCursor(Qt.SizeHorCursor) def setPen(self, pen): pen = QPen(pen) if self._pen != pen: self.prepareGeometryChange() self._pen = pen self._line = None self.update() def pen(self): return QPen(self._pen) def setValue(self, value): value = min(max(value, self._min), self._max) if self._value != value: self.prepareGeometryChange() self._value = value self._line = None self.valueChanged.emit(value) def value(self): return self._value def setRange(self, minval, maxval): maxval = max(minval, maxval) if minval != self._min or maxval != self._max: self._min = minval self._max = maxval self.rangeChanged.emit(minval, maxval) self.setValue(self._value) def setLength(self, length): if self._length != length: self.prepareGeometryChange() self._length = length self._line = None def length(self): return self._length def setOrientation(self, orientation): if self._orientation != orientation: self.prepareGeometryChange() self._orientation = orientation self._line = None if self._orientation == Qt.Vertical: self.setCursor(Qt.SizeVerCursor) else: self.setCursor(Qt.SizeHorCursor) def mousePressEvent(self, event): event.accept() self.linePressed.emit() def mouseMoveEvent(self, event): pos = event.pos() if self._orientation == Qt.Vertical: self.setValue(pos.y()) else: self.setValue(pos.x()) self.lineMoved.emit() event.accept() def mouseReleaseEvent(self, event): if self._orientation == Qt.Vertical: self.setValue(event.pos().y()) else: self.setValue(event.pos().x()) self.lineReleased.emit() event.accept() def boundingRect(self): 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) def paint(self, painter, *args): if self._line is None: self.boundingRect() painter.save() painter.setPen(self.pen()) painter.drawLine(self._line) painter.restore()
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 = title_pos + (v.p2() - v.p1()) * ( offset + QFontMetrics(self.title_item.font()).height()) else: title_pos = 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 * w / 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))