[英]trying to draw a line with floating point x, y values in pyqt5
I am creating a graph drawing app and currently facing a problem drawing the edges from one node to the other.我正在创建一个绘图应用程序,目前面临从一个节点到另一个节点绘制边的问题。 The problem is that to draw the edge between two nodes I must find the points of intersection of the two nodes (circles) and the line intersecting their centers, which then gives me 4 (x, y) points, then by finding the two closest points that is where I draw the edge.
问题是要绘制两个节点之间的边,我必须找到两个节点(圆圈)的交点和与它们中心相交的线,然后给出 4 (x, y) 点,然后找到两个最接近的点点就是我画边的地方。 For this I made a function (No need to actually understand how it operates its just math):
为此,我做了一个 function(不需要真正理解它是如何运行的,只是数学运算):
def computeIntersections(self, node1, node2):
a, b = node1.x, node1.y
c, d = node2.x, node2.y
r1 = node1.radius
r2 = node2.radius
m = (d - b) / (c - a)
g = 1+m**2
xa = g
xb = -(2*a*g)
xc = g*a**2 - r1**2
x1Node1 = (-xb + sqrt(xb**2 - 4*xa*xc)) / (2*xa)
x2Node1 = (-xb - sqrt(xb**2 - 4*xa*xc)) / (2*xa)
y1Node1 = m*(x1Node1 - a) + b
y2Node1 = m*(x2Node1 - a) + b
xa = g
xb = -(2*c*g)
xc = g*c**2 - r2**2
x1Node2 = (-xb + sqrt(xb**2 - 4*xa*xc)) / (2*xa)
x2Node2 = (-xb - sqrt(xb**2 - 4*xa*xc)) / (2*xa)
y1Node2 = m*(x1Node2 - a) + b
y2Node2 = m*(x2Node2 - a) + b
node1Intersections = [(x1Node1, y1Node1), (x2Node1, y2Node1)]
node2Intersections = [(x1Node2, y1Node2), (x2Node2, y2Node2)]
distances = {}
for point1 in node1Intersections:
for point2 in node2Intersections:
x1 = point1[0]; y1 = point1[1]
x2 = point2[0]; y2 = point2[1]
distance = sqrt((x1 - x2)**2 + (y1 - y2)**2)
distances[distance] = (x1, y1, x2, y2)
return distances[min(distances.keys())]
at the end it returns a list of 4 values: x1, y1, x2, y2 which will be used to draw the edge.最后它返回一个包含 4 个值的列表:x1、y1、x2、y2 将用于绘制边缘。 The issue is that these values are always floats, and the method I use to draw the edges is with the QPainter and drawLine method which takes integers only not floats, so I thought I could probably round it to the nearest int and no one would notice because the pixels are so small.
问题是这些值总是浮点数,我用来绘制边缘的方法是使用 QPainter 和 drawLine 方法,它只接受整数而不是浮点数,所以我想我可能可以将它四舍五入到最接近的 int 并且没有人会注意到因为像素太小了。 This results in the edge looking something like this:
这导致边缘看起来像这样:
As you can see the starting point of the edge goes inside the Node when its rounded so I was wondering if there could be a way to draw a line with floating point x, y coordinates or if there is a different way to do this entirely without the math that I wasn't aware of正如您所看到的,边的起点在变圆时进入节点内部,所以我想知道是否有一种方法可以用浮点 x、y 坐标绘制一条线,或者是否有其他方法可以完全不用我不知道的数学
EDIT: Here is a minimal reproducible example:编辑:这是一个最小的可重现示例:
from PyQt5.QtWidgets import QLabel, QFrame, QApplication, QWidget
from PyQt5.QtGui import QPainter
from math import sqrt
import sys
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setGeometry(200, 200, 800, 800)
self.node1 = Node(self, 'A', 5, 100, 100)
self.node2 = Node(self, 'B', 20, 700, 700)
def computeIntersections(self, node1, node2):
a, b = node1.x, node1.y
c, d = node2.x, node2.y
r1 = node1.radius
r2 = node2.radius
m = (d - b) / (c - a)
g = 1+m**2
xa = g
xb = -(2*a*g)
xc = g*a**2 - r1**2
x1Node1 = (-xb + sqrt(xb**2 - 4*xa*xc)) / (2*xa)
x2Node1 = (-xb - sqrt(xb**2 - 4*xa*xc)) / (2*xa)
y1Node1 = m*(x1Node1 - a) + b
y2Node1 = m*(x2Node1 - a) + b
xa = g
xb = -(2*c*g)
xc = g*c**2 - r2**2
x1Node2 = (-xb + sqrt(xb**2 - 4*xa*xc)) / (2*xa)
x2Node2 = (-xb - sqrt(xb**2 - 4*xa*xc)) / (2*xa)
y1Node2 = m*(x1Node2 - a) + b
y2Node2 = m*(x2Node2 - a) + b
node1Intersections = [(x1Node1, y1Node1), (x2Node1, y2Node1)]
node2Intersections = [(x1Node2, y1Node2), (x2Node2, y2Node2)]
distances = {}
for point1 in node1Intersections:
for point2 in node2Intersections:
x1 = point1[0]; y1 = point1[1]
x2 = point2[0]; y2 = point2[1]
distance = sqrt((x1 - x2)**2 + (y1 - y2)**2)
distances[distance] = (x1, y1, x2, y2)
return distances[min(distances.keys())]
def paintEvent(self, event):
x1, y1, x2, y2 = [round(n) for n in self.computeIntersections(self.node1, self.node2)]
qp = QPainter()
qp.begin(self)
qp.drawLine(x1, y1, x2, y2)
qp.end()
class Node(QFrame):
def __init__(self, parent, name, heuristic, x, y):
super().__init__(parent)
self.parent = parent
self.name = name
self.heuristic = heuristic
self.x = x
self.y = y
self.radius = 50
self.initializeNode()
def initializeNode(self):
self.setGeometry(self.x-self.radius, self.y-self.radius, self.radius*2, self.radius*2)
self.nodeName = QLabel(self.name, self)
self.nodeName.setObjectName('nodeName')
self.nodeName.setGeometry(25, 15, 50, 50)
self.nodeHeuristic = QLabel(str(self.heuristic), self)
self.nodeHeuristic.setObjectName('nodeHeuristic')
self.nodeHeuristic.setGeometry(40, 70, 20, 20)
self.setDefaultStyle()
def setDefaultStyle(self):
self.setStyleSheet(defaultStyle)
defaultStyle = """
QFrame {
border: 3px solid black;
min-height: 100px;
min-width: 100px;
border-radius: 53px;
}
QFrame#nodeName {
qproperty-alignment: AlignCenter;
border: 0px;
font-size: 30px;
min-height: 50px;
min-width: 50px;
}
QFrame#nodeHeuristic {
height:10px;
width:10px;
min-height: 20px;
min-width: 20px;
border-radius: 12px;
qproperty-alignment: AlignCenter;
font-size: 15px;
}
"""
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
The problem has nothing to do with floating point values: as you can see, the line is off by more than a pixel, which wouldn't be caused by "imprecise" integer values.该问题与浮点值无关:如您所见,该线偏离了一个像素以上,这不是由“不精确”的 integer 值引起的。
The main cause is the fact that you've set a border for the widget with a specific width.主要原因是您为具有特定宽度的小部件设置了边框。
When a border is set from style sheets (or by the style when using QFrame subclasses), the actual final geometry includes the border width.当从样式表(或使用 QFrame 子类时通过样式)设置边框时,实际的最终几何图形包括边框宽度。 If you just
print()
the geometry()
of those nodes after they've been shown, you'll see that their size is 106x106, which is the actual size plus twice the border (left+right for the width and top+bottom for the height).如果您在显示这些节点后仅
print()
它们的geometry()
,您会看到它们的大小为 106x106,这是实际大小加上边框的两倍(宽度为左+右,顶部+底部为为高度)。
A simple workaround for your case is to consider those borders when preparing the values for the computation:针对您的情况的一个简单解决方法是在准备计算值时考虑这些边界:
def computeIntersections(self, node1, node2):
border = node1.frameWidth()
a, b = node1.x + border, node1.y + border
c, d = node2.x + border, node2.y + border
r1 = node1.radius + border
r2 = node2.radius + border
# ...
Note that using widgets for complex custom drawings (and especially when dealing with multiple objects, advanced hierarchies and precise positioning), is normally discouraged: widgets are normally intended as standard interface elements and their implementation follows the same concept, which can cause problems if used in the "wrong" way (like what happened to you).请注意,通常不鼓励将小部件用于复杂的自定义绘图(尤其是在处理多个对象、高级层次结构和精确定位时):小部件通常旨在用作标准界面元素,并且它们的实现遵循相同的概念,如果使用可能会导致问题以“错误”的方式(就像发生在你身上的事)。
For the same reason, using child widgets with fixed geometries can cause unexpected problems (try setting the heuristic to a value with 3 or 4 digits and you'll see the result).出于同样的原因,使用具有固定几何形状的子部件可能会导致意外问题(尝试将启发式设置为 3 或 4 位数字的值,您将看到结果)。
A better solution is to completely draw the elements with the QPainter functions, and compute the coordinates according to the contents.更好的方案是完全用QPainter函数绘制元素,根据内容计算坐标。 Note that, while your problem was not caused by the usage of integer values, it must be considered that all QPainter methods that use numeric arguments for coordinates accept integers only, and if you want to use floating points you need to use the floating point Qt classes: QPointF , QLineF , QRectF .
请注意,虽然您的问题不是由使用 integer 值引起的,但必须考虑所有使用数字 arguments 作为坐标的 QPainter 方法仅接受整数,如果您想使用浮点数,则需要使用浮点数 Qt类: QPointF 、 QLineF 、 QRectF 。
On the other hand, Qt provides simpler (and faster) methods to do connect two circles with a line that intersects their centers:另一方面,Qt 提供了更简单(也更快)的方法来用与圆心相交的线连接两个圆:
fromPolar()
, which will be the lines from the center of the circles to their circumference, along with the connection of their centers;fromPolar()
为两个圆创建两条线,这将是从圆心到圆周的线,以及它们中心的连接; Since we obviously don't need the style sheet anymore, we can subclass from QWidget instead of QFrame.由于我们显然不再需要样式表,因此我们可以从 QWidget 而不是 QFrame 继承。
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setGeometry(200, 200, 800, 800)
self.node1 = Node(self, 'A', 5, 100, 100)
self.node2 = Node(self, 'B', 20, 700, 700)
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# the angle between the two centers
angle = QLineF(self.node1.center, self.node2.center).angle()
# a line that uses the same angle and is equal to the radius
first = QLineF.fromPolar(self.node1.radius, angle)
# fromPolar always has the first point at (0, 0), so we need to
# translate it to the center of the first circle
startRadius = first.translated(self.node1.center)
# the same as above, but with an inverted angle
second = QLineF.fromPolar(self.node2.radius, angle + 180)
endRadius = second.translated(self.node2.center)
# draw the line connecting the end points of each radius
qp.drawLine(startRadius.p2(), endRadius.p2())
class Node(QWidget):
def __init__(self, parent, name, heuristic, x, y):
super().__init__(parent)
self.parent = parent
self.name = name
self.heuristic = heuristic
self.center = QPoint(x, y)
self.radius = 50
self.setGeometry(x - 50, y - 50, 100, 100)
def paintEvent(self, event):
qp = QPainter(self)
qp.setRenderHints(qp.Antialiasing)
qp.setPen(QPen(Qt.black, 3))
# the circle is drawn inside the widget rectangle, so we need to draw
# a circle that is smaller by half of the pen size
margin = 1.5
size = self.radius * 2 - margin * 2
qp.drawEllipse(QRectF(margin, margin, size, size))
font = self.font()
font.setPointSize(30)
qp.setFont(font)
# a horizontally centered rectangle that is slightly above the
# center, considering the font size
nameHeight = QFontMetrics(font).ascent()
nameRect = QRectF(0, self.height() / 2 - nameHeight,
self.width(), 30)
qp.drawText(nameRect, Qt.AlignCenter, self.name)
font.setPointSize(15)
qp.setFont(font)
fm = QFontMetrics(font)
# the circle must contain the text, we need to add some margin
heuText = ' ' + str(self.heuristic) + ' '
heuSize = max(fm.height(), fm.horizontalAdvance(heuText))
# the rect of the circle, placed at half its height from the bottom;
# note that I used QRectF for precision
heuRect = QRectF(self.width() / 2 - heuSize / 2,
self.height() - heuSize * 1.5,
heuSize, heuSize)
qp.drawEllipse(heuRect)
qp.drawText(heuRect, Qt.AlignCenter, heuText)
Final notes:最后说明:
x()
and y()
are existing, dynamic properties of all QWidgets, and you should not overwrite them; x()
和y()
是所有 QWidget 的现有动态属性,您不应该覆盖它们;paintEvent()
gets automatically destroyed when the function returns, there is no need to explicitly call end()
;paintEvent()
中使用的 QPainter 会自动销毁,无需显式调用end()
;self.update()
in their setter functions;self.update()
; for these situations, using child widgets just for their object name or property setter/getters is not a comparable benefit;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.