def draw_arrow(self, line, width, color): (x1, y1), (x2, y2) = line # compute points line = QLineF(x1, y1, x2, y2) # If the line is very small, we make our arrowhead smaller arrowsize = min(14, line.length()) lineangle = radians(line.angle()) arrowpt1 = line.p2() + QPointF(sin(lineangle - (pi/3)) * arrowsize, cos(lineangle - (pi/3)) * arrowsize) arrowpt2 = line.p2() + QPointF(sin(lineangle - pi + (pi/3)) * arrowsize, cos(lineangle - pi + (pi/3)) * arrowsize) head = QPolygonF([line.p2(), arrowpt1, arrowpt2]) # We have to draw the actual line a little short for the tip of the arrowhead not to be too wide adjustedLine = QLineF(line) adjustedLine.setLength(line.length() - arrowsize/2) # draw line painter = self.current_painter color = COLORS[color] painter.save() pen = QPen(painter.pen()) pen.setColor(color) pen.setWidthF(width) painter.setPen(pen) painter.drawLine(adjustedLine) # draw arrowhead painter.setPen(Qt.NoPen) brush = painter.brush() brush.setColor(color) brush.setStyle(Qt.SolidPattern) painter.setBrush(brush) painter.drawPolygon(head) painter.restore()
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 = QtGui.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 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 = QtGui.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(0.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 overlay_for(pt1, pt2, frequency): # Construct the line-geometry, we'll use this to construct the ellipsoid line = QLineF(pt1, pt2) # Determine the radius for the ellipsoid radius = fresnel_radius(line.length(), frequency) # Draw the ellipsoid zone = QPainterPath() zone.addEllipse(QPointF(0., 0.), line.length() / 2, radius) # Rotate the ellipsoid - same angle as the line transform = QTransform() transform.rotate(-line.angle()) zone = transform.map(zone) # Center the zone over the line lc = QRectF(pt1, pt2).center() zc = zone.boundingRect().center() zone.translate(lc.x() - zc.x(), lc.y() - zc.y()) return line, zone
def draw_arrow(self, line, width, color): (x1, y1), (x2, y2) = line # compute points line = QLineF(x1, y1, x2, y2) # If the line is very small, we make our arrowhead smaller arrowsize = min(14, line.length()) lineangle = radians(line.angle()) arrowpt1 = line.p2() + QPointF( sin(lineangle - (pi / 3)) * arrowsize, cos(lineangle - (pi / 3)) * arrowsize) arrowpt2 = line.p2() + QPointF( sin(lineangle - pi + (pi / 3)) * arrowsize, cos(lineangle - pi + (pi / 3)) * arrowsize) head = QPolygonF([line.p2(), arrowpt1, arrowpt2]) # We have to draw the actual line a little short for the tip of the arrowhead not to be too wide adjustedLine = QLineF(line) adjustedLine.setLength(line.length() - arrowsize / 2) # draw line painter = self.current_painter color = COLORS[color] painter.save() pen = QPen(painter.pen()) pen.setColor(color) pen.setWidthF(width) painter.setPen(pen) painter.drawLine(adjustedLine) # draw arrowhead painter.setPen(Qt.NoPen) brush = painter.brush() brush.setColor(color) brush.setStyle(Qt.SolidPattern) painter.setBrush(brush) painter.drawPolygon(head) painter.restore()
class MyArrow(QGraphicsLineItem): def __init__(self): super(MyArrow, self).__init__() self.source = QPointF(0, 250) self.dest = QPointF(120, 120) self.line = QLineF(self.source, self.dest) self.line.setLength(self.line.length() - 20) def paint(self, QPainter, QStyleOptionGraphicsItem, QWidget_widget=None): # setPen pen = QPen() pen.setWidth(5) pen.setJoinStyle(Qt.MiterJoin) #让箭头变尖 QPainter.setPen(pen) # draw line QPainter.drawLine(self.line)
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 __init__(self, sourceNode, destNode, state=1, text=""): super(Edge, self).__init__() self.arrowSize = 15.0 self.sourcePoint = QPointF() self.destPoint = QPointF() self.setAcceptedMouseButtons(Qt.NoButton) self.source = sourceNode self.dest = destNode self.source.addEdge(self) self.dest.addEdge(self) self.adjust() self.state = state self.text = text line = QLineF(sourceNode.pos(), destNode.pos()) self.weight = line.length()
class Line( QGraphicsLineItem ): """Defines a line by two points. If points are moved, line follows these movements.""" def __init__( self, startPoint, endPoint, ccs, paintToBorder = False, showIncline = False, color = 'orange', minLength = 0 ): super( Line, self ).__init__( ccs ) self.startPoint = startPoint self.endPoint = endPoint self.ccs = ccs self.paintToBorder = paintToBorder self.showIncline = showIncline self.color = color self.visible = True # by default we only want to draw lines if its two # defining points are not too close together self.drawAlways = False self.minLength = minLength # pixel # grmbl. Line was designed to be defined by two Point.Points. Now I want to be able to define # lines as well from QPointFs. try: self.Rect = QRectF( self.startPoint.x, self.startPoint.y, self.endPoint.x, self.endPoint.y ) except: self.Rect = QRectF( self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y() ) def boundingRect( self ): return self.Rect def paint( self, painter, option, widget=None ): if self.visible == True: painter.setPen( QColor( self.color ) ) # see try-catch (pardon me) above try: self.sp = CST.toCcsCoord( self.ccs, self.startPoint.x, self.startPoint.y ) self.ep = CST.toCcsCoord( self.ccs, self.endPoint.x, self.endPoint.y ) except: self.sp = CST.toCcsCoord( self.ccs, self.startPoint.x(), self.startPoint.y() ) self.ep = CST.toCcsCoord( self.ccs, self.endPoint.x(), self.endPoint.y() ) self.Rect = QRectF( self.sp, self.ep ) self.line = QLineF( self.sp, self.ep ) if self.line.length() > self.minLength or self.drawAlways == True: painter.drawLine( self.line ) if self.paintToBorder == True: # paint line to approximately the edge of the ccs. ep2 = self.line.pointAt( self.ccs.width / self.line.length() * 2) painter.drawLine(self.ep,ep2) sp2 = self.line.pointAt(-self.ccs.width / self.line.length() * 2) painter.drawLine(self.sp,sp2) if self.showIncline == True: incline = ( self.endPoint.y - self.startPoint.y ) / ( self.endPoint.x - self.startPoint.x ) # print text limited to 2 decimal digits. painter.setBackground ( QBrush ( QColor( 'lightGrey' ) ) ) painter.setBackgroundMode (Qt.BGMode(1)) painter.setPen( QColor( 'black' ) ) #~ painter.drawText( self.ep.x() + 10, self.ep.y() + 10, QString ( '%.2f' %(incline) ) ) def setVisible( self, value ): self.visible = value def setPosition( self, startPoint, endPoint ): self.startPoint = startPoint self.endPoint = endPoint def updateYourself( self, xDelta, yDelta ): # There is no action needed, as a line gets its information # from startPoint and endPoint # Just adjust self.Rect to avoid case where line disappears mysteriously and after then, # paint() is never called again self.Rect = QRectF( self.startPoint.x, self.startPoint.y, self.endPoint.x, self.endPoint.y )
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
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)
def __len__(self): line = QLineF(self.sourceNode().pos(), self.destNode().pos()) return line.length()