如何在 pyqt4 中的行中添加箭头?
How to add a arrow head to my line in pyqt4?
我得到了这个代码:
from PyQt4 import QtGui, QtCore
class MyFrame(QtGui.QGraphicsView):
def __init__( self, parent = None ):
super(MyFrame, self).__init__(parent)
scene = QtGui.QGraphicsScene()
self.setScene(scene)
self.resize( 400, 240 )
# http://pyqt.sourceforge.net/Docs/PyQt4/qpen.html
pencil = QtGui.QPen( QtCore.Qt.black, 2)
pencil.setStyle( QtCore.Qt.SolidLine )
# pencil.setStyle( QtCore.Qt.UpArrow )
scene.addLine( QtCore.QLineF( 0, 0, 100, 100 ), pencil )
if ( __name__ == '__main__' ):
app = QtGui.QApplication([])
f = MyFrame()
f.show()
app.exec_()
画这个的window:
如何将箭头添加到线的一端,因为这些我用图像编辑器在最后一张图像上绘制:
我找到了这个 C++ 教程 http://www.codeproject.com/Articles/3274/Drawing-Arrows 和这个伪代码:
// ARROWSTRUCT
//
// Defines the attributes of an arrow.
typedef struct tARROWSTRUCT {
int nWidth; // width (in pixels) of the full base of the arrowhead
float fTheta; // angle (in radians) at the arrow tip between the two
// sides of the arrowhead
bool bFill; // flag indicating whether or not the arrowhead should be
// filled
} ARROWSTRUCT;
// ArrowTo()
//
// Draws an arrow, using the current pen and brush, from the current position
// to the passed point using the attributes defined in the ARROWSTRUCT.
void ArrowTo(HDC hDC, int x, int y, ARROWSTRUCT *pArrow);
void ArrowTo(HDC hDC, const POINT *lpTo, ARROWSTRUCT *pArrow);
Simply fill an ARROWSTRUCT with the desired attributes, make sure the current DC position is correct (MoveTo(), etc.), and call one of the two ArrowTo() functions. The size parameters (nWidth and fTheta) are defined as follows:
Technique
This goes back to high-school algebra and trigonometry. The ArrowTo() function first builds a vector of the full line. Then it calculates the points for the sides of the arrowhead based on the nWidth and fTheta attributes you pass. Badda-boom-badda-bing, you got your arrowhead.
Here's some pseudo-pseudocode:
lineVector = toPoint - fromPoint
lineLength = length of lineVector
// calculate point at base of arrowhead
tPointOnLine = nWidth / (2 * (tanf(fTheta) / 2) * lineLength);
pointOnLine = toPoint + -tPointOnLine * lineVector
// calculate left and right points of arrowhead
normalVector = (-lineVector.y, lineVector.x)
tNormal = nWidth / (2 * lineLength)
leftPoint = pointOnLine + tNormal * normalVector
rightPoint = pointOnLine + -tNormal * normalVector
此外,我还可以找到另一个问题 ,但它是针对 qt5 的。因此在pyqt4中用多边形绘制箭头是不是更好的方法?
我遇到了同样的问题,所以经过一些工作我想到了这个。
import math, sys
from PyQt5 import QtWidgets, QtCore, QtGui
class Path(QtWidgets.QGraphicsPathItem):
def __init__(self, source: QtCore.QPointF = None, destination: QtCore.QPointF = None, *args, **kwargs):
super(Path, self).__init__(*args, **kwargs)
self._sourcePoint = source
self._destinationPoint = destination
self._arrow_height = 5
self._arrow_width = 4
def setSource(self, point: QtCore.QPointF):
self._sourcePoint = point
def setDestination(self, point: QtCore.QPointF):
self._destinationPoint = point
def directPath(self):
path = QtGui.QPainterPath(self._sourcePoint)
path.lineTo(self._destinationPoint)
return path
def arrowCalc(self, start_point=None, end_point=None): # calculates the point where the arrow should be drawn
try:
startPoint, endPoint = start_point, end_point
if start_point is None:
startPoint = self._sourcePoint
if endPoint is None:
endPoint = self._destinationPoint
dx, dy = startPoint.x() - endPoint.x(), startPoint.y() - endPoint.y()
leng = math.sqrt(dx ** 2 + dy ** 2)
normX, normY = dx / leng, dy / leng # normalize
# perpendicular vector
perpX = -normY
perpY = normX
leftX = endPoint.x() + self._arrow_height * normX + self._arrow_width * perpX
leftY = endPoint.y() + self._arrow_height * normY + self._arrow_width * perpY
rightX = endPoint.x() + self._arrow_height * normX - self._arrow_width * perpX
rightY = endPoint.y() + self._arrow_height * normY - self._arrow_width * perpY
point2 = QtCore.QPointF(leftX, leftY)
point3 = QtCore.QPointF(rightX, rightY)
return QtGui.QPolygonF([point2, endPoint, point3])
except (ZeroDivisionError, Exception):
return None
def paint(self, painter: QtGui.QPainter, option, widget=None) -> None:
painter.setRenderHint(painter.Antialiasing)
painter.pen().setWidth(2)
painter.setBrush(QtCore.Qt.NoBrush)
path = self.directPath()
painter.drawPath(path)
self.setPath(path)
triangle_source = self.arrowCalc(path.pointAtPercent(0.1), self._sourcePoint) # change path.PointAtPercent() value to move arrow on the line
if triangle_source is not None:
painter.drawPolyline(triangle_source)
class ViewPort(QtWidgets.QGraphicsView):
def __init__(self):
super(ViewPort, self).__init__()
self.setViewportUpdateMode(self.FullViewportUpdate)
self._isdrawingPath = False
self._current_path = None
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
if event.button() == QtCore.Qt.LeftButton:
pos = self.mapToScene(event.pos())
self._isdrawingPath = True
self._current_path = Path(source=pos, destination=pos)
self.scene().addItem(self._current_path)
return
super(ViewPort, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
pos = self.mapToScene(event.pos())
if self._isdrawingPath:
self._current_path.setDestination(pos)
self.scene().update(self.sceneRect())
return
super(ViewPort, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
pos = self.mapToScene(event.pos())
if self._isdrawingPath:
self._current_path.setDestination(pos)
self._isdrawingPath = False
self._current_path = None
self.scene().update(self.sceneRect())
return
super(ViewPort, self).mouseReleaseEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
window = ViewPort()
scene = QtWidgets.QGraphicsScene()
window.setScene(scene)
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
此代码适用于任何类型的路径,包括贝塞尔曲线、正方形等。如果您想更改箭头位置,您应该将 path.PointAtPercent()
值更改为 0
和 [=14 之间的任何值=].例如,如果你想在线条的中间绘制箭头,使用 self.arrowCalc(path.pointAtPercent(0.5), path.pointAtPercent(0.51))
。此外,当您将点传递给 arrowCalc
时,请确保源点和目标点接近。
额外:
如果你想测试方形和贝塞尔曲线路径(用下面的方法替换直接路径方法):
def squarePath(self):
s = self._sourcePoint
d = self._destinationPoint
mid_x = s.x() + ((d.x() - s.x()) * 0.5)
path = QtGui.QPainterPath(QtCore.QPointF(s.x(), s.y()))
path.lineTo(mid_x, s.y())
path.lineTo(mid_x, d.y())
path.lineTo(d.x(), d.y())
return path
def bezierPath(self):
s = self._sourcePoint
d = self._destinationPoint
source_x, source_y = s.x(), s.y()
destination_x, destination_y = d.x(), d.y()
dist = (d.x() - s.x()) * 0.5
cpx_s = +dist
cpx_d = -dist
cpy_s = 0
cpy_d = 0
if (s.x() > d.x()) or (s.x() < d.x()):
cpx_d *= -1
cpx_s *= -1
cpy_d = (
(source_y - destination_y) / math.fabs(
(source_y - destination_y) if (source_y - destination_y) != 0 else 0.00001
)
) * 150
cpy_s = (
(destination_y - source_y) / math.fabs(
(destination_y - source_y) if (destination_y - source_y) != 0 else 0.00001
)
) * 150
path = QtGui.QPainterPath(self._sourcePoint)
path.cubicTo(destination_x + cpx_d, destination_y + cpy_d, source_x + cpx_s, source_y + cpy_s,
destination_x, destination_y)
return path
输出:
@Art 的回答提供了一个绘制箭头的解决方案 --> 受他的代码启发,我找到了一个解决方法来绘制像这样的箭头 ⇨。希望对您有所帮助。
from PyQt5 import QtWidgets, QtCore, QtGui
import math
# draw an arrow like this
# |\
# ___ _____| \
# length_width | | \ _____
# _|_ |_____ / |
# | / | arrow_width
# |/ __|__
#
# |<->|
# arrow_height
class Arrow(QtWidgets.QGraphicsPathItem):
def __init__(self, source: QtCore.QPointF, destination: QtCore.QPointF, arrow_height, arrow_width, length_width, *args, **kwargs):
super(Arrow, self).__init__(*args, **kwargs)
self._sourcePoint = source
self._destinationPoint = destination
self._arrow_height = arrow_height
self._arrow_width = arrow_width
self._length_width = length_width
def arrowCalc(self, start_point=None, end_point=None): # calculates the point where the arrow should be drawn
try:
startPoint, endPoint = start_point, end_point
if start_point is None:
startPoint = self._sourcePoint
if endPoint is None:
endPoint = self._destinationPoint
dx, dy = startPoint.x() - endPoint.x(), startPoint.y() - endPoint.y()
leng = math.sqrt(dx ** 2 + dy ** 2)
normX, normY = dx / leng, dy / leng # normalize
# parallel vector (normX, normY)
# perpendicular vector (perpX, perpY)
perpX = -normY
perpY = normX
# p2
# |\
# p4____p5 \
# | \ endpoint
# p7____p6 /
# | /
# |/
# p3
point2 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height + QtCore.QPointF(perpX, perpY) * self._arrow_width
point3 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height - QtCore.QPointF(perpX, perpY) * self._arrow_width
point4 = startPoint + QtCore.QPointF(perpX, perpY) * self._length_width
point5 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height + QtCore.QPointF(perpX, perpY) * self._length_width
point6 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height - QtCore.QPointF(perpX, perpY) * self._length_width
point7 = startPoint - QtCore.QPointF(perpX, perpY) * self._length_width
return QtGui.QPolygonF([point4, point5, point2, endPoint, point3, point6, point7])
except (ZeroDivisionError, Exception):
return None
def paint(self, painter: QtGui.QPainter, option, widget=None) -> None:
painter.setRenderHint(painter.Antialiasing)
my_pen = QtGui.QPen()
my_pen.setWidth(1)
my_pen.setCosmetic(False)
my_pen.setColor(QtGui.QColor(255, 0, 0, 100))
painter.setPen(my_pen)
arrow_polygon = self.arrowCalc()
if arrow_polygon is not None:
# painter.drawPolyline(arrow_polygon)
painter.drawPolygon(arrow_polygon)
我得到了这个代码:
from PyQt4 import QtGui, QtCore
class MyFrame(QtGui.QGraphicsView):
def __init__( self, parent = None ):
super(MyFrame, self).__init__(parent)
scene = QtGui.QGraphicsScene()
self.setScene(scene)
self.resize( 400, 240 )
# http://pyqt.sourceforge.net/Docs/PyQt4/qpen.html
pencil = QtGui.QPen( QtCore.Qt.black, 2)
pencil.setStyle( QtCore.Qt.SolidLine )
# pencil.setStyle( QtCore.Qt.UpArrow )
scene.addLine( QtCore.QLineF( 0, 0, 100, 100 ), pencil )
if ( __name__ == '__main__' ):
app = QtGui.QApplication([])
f = MyFrame()
f.show()
app.exec_()
画这个的window:
如何将箭头添加到线的一端,因为这些我用图像编辑器在最后一张图像上绘制:
我找到了这个 C++ 教程 http://www.codeproject.com/Articles/3274/Drawing-Arrows 和这个伪代码:
// ARROWSTRUCT
//
// Defines the attributes of an arrow.
typedef struct tARROWSTRUCT {
int nWidth; // width (in pixels) of the full base of the arrowhead
float fTheta; // angle (in radians) at the arrow tip between the two
// sides of the arrowhead
bool bFill; // flag indicating whether or not the arrowhead should be
// filled
} ARROWSTRUCT;
// ArrowTo()
//
// Draws an arrow, using the current pen and brush, from the current position
// to the passed point using the attributes defined in the ARROWSTRUCT.
void ArrowTo(HDC hDC, int x, int y, ARROWSTRUCT *pArrow);
void ArrowTo(HDC hDC, const POINT *lpTo, ARROWSTRUCT *pArrow);
Simply fill an ARROWSTRUCT with the desired attributes, make sure the current DC position is correct (MoveTo(), etc.), and call one of the two ArrowTo() functions. The size parameters (nWidth and fTheta) are defined as follows:
Technique
This goes back to high-school algebra and trigonometry. The ArrowTo() function first builds a vector of the full line. Then it calculates the points for the sides of the arrowhead based on the nWidth and fTheta attributes you pass. Badda-boom-badda-bing, you got your arrowhead.
Here's some pseudo-pseudocode:
lineVector = toPoint - fromPoint
lineLength = length of lineVector
// calculate point at base of arrowhead
tPointOnLine = nWidth / (2 * (tanf(fTheta) / 2) * lineLength);
pointOnLine = toPoint + -tPointOnLine * lineVector
// calculate left and right points of arrowhead
normalVector = (-lineVector.y, lineVector.x)
tNormal = nWidth / (2 * lineLength)
leftPoint = pointOnLine + tNormal * normalVector
rightPoint = pointOnLine + -tNormal * normalVector
此外,我还可以找到另一个问题
我遇到了同样的问题,所以经过一些工作我想到了这个。
import math, sys
from PyQt5 import QtWidgets, QtCore, QtGui
class Path(QtWidgets.QGraphicsPathItem):
def __init__(self, source: QtCore.QPointF = None, destination: QtCore.QPointF = None, *args, **kwargs):
super(Path, self).__init__(*args, **kwargs)
self._sourcePoint = source
self._destinationPoint = destination
self._arrow_height = 5
self._arrow_width = 4
def setSource(self, point: QtCore.QPointF):
self._sourcePoint = point
def setDestination(self, point: QtCore.QPointF):
self._destinationPoint = point
def directPath(self):
path = QtGui.QPainterPath(self._sourcePoint)
path.lineTo(self._destinationPoint)
return path
def arrowCalc(self, start_point=None, end_point=None): # calculates the point where the arrow should be drawn
try:
startPoint, endPoint = start_point, end_point
if start_point is None:
startPoint = self._sourcePoint
if endPoint is None:
endPoint = self._destinationPoint
dx, dy = startPoint.x() - endPoint.x(), startPoint.y() - endPoint.y()
leng = math.sqrt(dx ** 2 + dy ** 2)
normX, normY = dx / leng, dy / leng # normalize
# perpendicular vector
perpX = -normY
perpY = normX
leftX = endPoint.x() + self._arrow_height * normX + self._arrow_width * perpX
leftY = endPoint.y() + self._arrow_height * normY + self._arrow_width * perpY
rightX = endPoint.x() + self._arrow_height * normX - self._arrow_width * perpX
rightY = endPoint.y() + self._arrow_height * normY - self._arrow_width * perpY
point2 = QtCore.QPointF(leftX, leftY)
point3 = QtCore.QPointF(rightX, rightY)
return QtGui.QPolygonF([point2, endPoint, point3])
except (ZeroDivisionError, Exception):
return None
def paint(self, painter: QtGui.QPainter, option, widget=None) -> None:
painter.setRenderHint(painter.Antialiasing)
painter.pen().setWidth(2)
painter.setBrush(QtCore.Qt.NoBrush)
path = self.directPath()
painter.drawPath(path)
self.setPath(path)
triangle_source = self.arrowCalc(path.pointAtPercent(0.1), self._sourcePoint) # change path.PointAtPercent() value to move arrow on the line
if triangle_source is not None:
painter.drawPolyline(triangle_source)
class ViewPort(QtWidgets.QGraphicsView):
def __init__(self):
super(ViewPort, self).__init__()
self.setViewportUpdateMode(self.FullViewportUpdate)
self._isdrawingPath = False
self._current_path = None
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
if event.button() == QtCore.Qt.LeftButton:
pos = self.mapToScene(event.pos())
self._isdrawingPath = True
self._current_path = Path(source=pos, destination=pos)
self.scene().addItem(self._current_path)
return
super(ViewPort, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
pos = self.mapToScene(event.pos())
if self._isdrawingPath:
self._current_path.setDestination(pos)
self.scene().update(self.sceneRect())
return
super(ViewPort, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
pos = self.mapToScene(event.pos())
if self._isdrawingPath:
self._current_path.setDestination(pos)
self._isdrawingPath = False
self._current_path = None
self.scene().update(self.sceneRect())
return
super(ViewPort, self).mouseReleaseEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
window = ViewPort()
scene = QtWidgets.QGraphicsScene()
window.setScene(scene)
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
此代码适用于任何类型的路径,包括贝塞尔曲线、正方形等。如果您想更改箭头位置,您应该将 path.PointAtPercent()
值更改为 0
和 [=14 之间的任何值=].例如,如果你想在线条的中间绘制箭头,使用 self.arrowCalc(path.pointAtPercent(0.5), path.pointAtPercent(0.51))
。此外,当您将点传递给 arrowCalc
时,请确保源点和目标点接近。
额外:
如果你想测试方形和贝塞尔曲线路径(用下面的方法替换直接路径方法):
def squarePath(self):
s = self._sourcePoint
d = self._destinationPoint
mid_x = s.x() + ((d.x() - s.x()) * 0.5)
path = QtGui.QPainterPath(QtCore.QPointF(s.x(), s.y()))
path.lineTo(mid_x, s.y())
path.lineTo(mid_x, d.y())
path.lineTo(d.x(), d.y())
return path
def bezierPath(self):
s = self._sourcePoint
d = self._destinationPoint
source_x, source_y = s.x(), s.y()
destination_x, destination_y = d.x(), d.y()
dist = (d.x() - s.x()) * 0.5
cpx_s = +dist
cpx_d = -dist
cpy_s = 0
cpy_d = 0
if (s.x() > d.x()) or (s.x() < d.x()):
cpx_d *= -1
cpx_s *= -1
cpy_d = (
(source_y - destination_y) / math.fabs(
(source_y - destination_y) if (source_y - destination_y) != 0 else 0.00001
)
) * 150
cpy_s = (
(destination_y - source_y) / math.fabs(
(destination_y - source_y) if (destination_y - source_y) != 0 else 0.00001
)
) * 150
path = QtGui.QPainterPath(self._sourcePoint)
path.cubicTo(destination_x + cpx_d, destination_y + cpy_d, source_x + cpx_s, source_y + cpy_s,
destination_x, destination_y)
return path
输出:
@Art 的回答提供了一个绘制箭头的解决方案 --> 受他的代码启发,我找到了一个解决方法来绘制像这样的箭头 ⇨。希望对您有所帮助。
from PyQt5 import QtWidgets, QtCore, QtGui
import math
# draw an arrow like this
# |\
# ___ _____| \
# length_width | | \ _____
# _|_ |_____ / |
# | / | arrow_width
# |/ __|__
#
# |<->|
# arrow_height
class Arrow(QtWidgets.QGraphicsPathItem):
def __init__(self, source: QtCore.QPointF, destination: QtCore.QPointF, arrow_height, arrow_width, length_width, *args, **kwargs):
super(Arrow, self).__init__(*args, **kwargs)
self._sourcePoint = source
self._destinationPoint = destination
self._arrow_height = arrow_height
self._arrow_width = arrow_width
self._length_width = length_width
def arrowCalc(self, start_point=None, end_point=None): # calculates the point where the arrow should be drawn
try:
startPoint, endPoint = start_point, end_point
if start_point is None:
startPoint = self._sourcePoint
if endPoint is None:
endPoint = self._destinationPoint
dx, dy = startPoint.x() - endPoint.x(), startPoint.y() - endPoint.y()
leng = math.sqrt(dx ** 2 + dy ** 2)
normX, normY = dx / leng, dy / leng # normalize
# parallel vector (normX, normY)
# perpendicular vector (perpX, perpY)
perpX = -normY
perpY = normX
# p2
# |\
# p4____p5 \
# | \ endpoint
# p7____p6 /
# | /
# |/
# p3
point2 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height + QtCore.QPointF(perpX, perpY) * self._arrow_width
point3 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height - QtCore.QPointF(perpX, perpY) * self._arrow_width
point4 = startPoint + QtCore.QPointF(perpX, perpY) * self._length_width
point5 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height + QtCore.QPointF(perpX, perpY) * self._length_width
point6 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height - QtCore.QPointF(perpX, perpY) * self._length_width
point7 = startPoint - QtCore.QPointF(perpX, perpY) * self._length_width
return QtGui.QPolygonF([point4, point5, point2, endPoint, point3, point6, point7])
except (ZeroDivisionError, Exception):
return None
def paint(self, painter: QtGui.QPainter, option, widget=None) -> None:
painter.setRenderHint(painter.Antialiasing)
my_pen = QtGui.QPen()
my_pen.setWidth(1)
my_pen.setCosmetic(False)
my_pen.setColor(QtGui.QColor(255, 0, 0, 100))
painter.setPen(my_pen)
arrow_polygon = self.arrowCalc()
if arrow_polygon is not None:
# painter.drawPolyline(arrow_polygon)
painter.drawPolygon(arrow_polygon)