我得到了这个代码:
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_()
哪个绘制这个窗口:
如何将箭头添加到线条的一端,因为我使用图像编辑器在最后一张图像上绘制了这些箭头:
我找到了这个 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);
只需用所需的属性填充 ARROWSTRUCT,确保当前 DC 位置正确(MoveTo() 等),然后调用两个 ArrowTo() 函数之一。尺寸参数(nWidth 和 fTheta)定义如下:
技术
这可以追溯到高中代数和三角学。 ArrowTo() 函数首先构建整条线的向量。然后它根据您传递的 nWidth 和 fTheta 属性计算箭头两侧的点。 Badda-boom-badda-bing,你得到了箭头。
这是一些伪伪代码:
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
此外,我还可以找到另一个问题Drawing a Polygon in PyQt,但它是针对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
和 1
之间的任意位置。例如,如果您想在线中间绘制箭头,请使用 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)
@art 的 C++ 版本答案:
void drawArrow(QPainter *painter, const QPointF &p1, const QPointF &p2,
float width) {
const auto brush = painter->brush();
painter->save();
const auto kArrowWidth = width;
const auto kArrowHeight = width;
const float dx = p1.x() - p2.x();
const float dy = p1.y() - p2.y();
const float len = std::sqrt(std::pow(dx, 2) + std::pow(dy, 2));
const float normalX = dx / len;
const float normalY = dy / len;
const float perpX = -normalY;
const float perpY = normalX;
const float leftX = p2.x() + kArrowHeight * normalX + kArrowWidth * perpX;
const float leftY = p2.y() + kArrowHeight * normalY + kArrowWidth * perpY;
const float rightX = p2.x() + kArrowHeight * normalX - kArrowWidth * perpX;
const float rightY = p2.y() + kArrowHeight * normalY - kArrowWidth * perpY;
const QPolygonF polygon({QPointF(leftX, leftY), p2, QPointF(rightX, rightY)});
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setBrush(brush);
painter->drawPolygon(polygon);
painter->restore();
}