当前位置: 首页 > 知识库问答 >
问题:

使用PyQt5绘制多点曲线

巩枫
2023-03-14

如何连接多点与流动曲线,使用PyQt5?例如,我试图使用quitTo()对8个点执行此操作,使用交替点作为控制点,但弧不接触控制点(见下面的代码和图表)。我也尝试使用cubicTo(),但这也导致了一个奇怪的曲线。use是否有任何其他函数调用,我应该使用,或自定义的方式来做到这一点?

from PyQt5 import QtGui, QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = "PyQt5 Drawing Tutorial"
        self.top= 150
        self.left= 150
        self.width = 500
        self.height = 500
        self.InitWindow()
    def InitWindow(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.top, self.left, self.width, self.height)
        self.show()
    def paintEvent(self, event):
        painter = QPainter(self)
        path = QPainterPath()
        points = [
            QPoint(20,40),
            QPoint(60,10),
            QPoint(100,50),
            QPoint(80,200),
            QPoint(200,300),
            QPoint(150,400),
            QPoint(350,450),
            QPoint(400,350),
            ]

        # draw small red dots on each point
        painter.setPen(QtCore.Qt.red)
        painter.setBrush(QBrush(Qt.red))
        for i in range(len(points)):
            painter.drawEllipse(points[i], 3, 3)

        painter.setPen(QtCore.Qt.blue)
        painter.setBrush(QBrush(Qt.red, Qt.NoBrush)) #reset the brush
        path.moveTo(points[0])

        # connect the points with blue straight lines
        #for i in range(len(points)-1):  # 1 less than length
        #    path.lineTo(points[i+1])

        # connect points with curve
        for i in range(0,len(points),2):
            path.quadTo(points[i], points[i+1])

        painter.drawPath(path)

App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())

共有1个答案

沈弘文
2023-03-14

使用诸如quadTocubicTo之类的函数将不起作用,因为它们使用控制点来创建贝塞尔曲线,而这些点通常不是曲线的一部分。

我意识到我之前的回答不仅不准确,而且是错误的。我把它留在这个答案的底部,留档/历史用途。

精确的“样条”插值必须使用与可能曲线相切的线段;为了找到您需要的线段数据:

  1. 上一点和下一点
  2. 查找使用上一个/当前点和当前/下一个点创建的线段之间的角平分线
  3. 创建两条与该角度垂直的线段,从当前点开始,长度与每条线段成比例
  4. 将这些线段的endpoint用作控制点

在下图中,您可以看到所有重要的内容:

  • 红点:参考点;
  • 浅灰色线:线段
  • 天蓝色线:角平分线
  • 红线:目标线的参考(从当前点到下一个点)
  • 绿线:源线的参考(从上一个点到当前)
  • 橙色方块:控制点

请注意,第一条和最后一条曲线仅为二次曲线(不是三次曲线),因为只有一个控制点:第一个点的目标线参考,最后一个点的源线参考。

代码使用从第二个点循环到第二个点循环到最后一个点的for循环,还使用上一个循环中设置的控制点。

我建议您使用一个因子=.25,这应该会创建一条足够平滑的路径。值越低,曲线越“小”,而值越高,路径越“圆”。

class Window(QWidget):
    # ...

    def buildPath(self):
        factor = 
        self.path = QtGui.QPainterPath(points[0])
        for p, current in enumerate(points[1:-1], 1):
            # previous segment
            source = QtCore.QLineF(points[p - 1], current)
            # next segment
            target = QtCore.QLineF(current, points[p + 1])
            targetAngle = target.angleTo(source)
            if targetAngle > 180:
                angle = (source.angle() + source.angleTo(target) / 2) % 360
            else:
                angle = (target.angle() + target.angleTo(source) / 2) % 360

            revTarget = QtCore.QLineF.fromPolar(source.length() * factor, angle + 180).translated(current)
            cp2 = revTarget.p2()

            if p == 1:
                self.path.quadTo(cp2, current)
            else:
                # use the control point "cp1" set in the *previous* cycle
                self.path.cubicTo(cp1, cp2, current)

            revSource = QtCore.QLineF.fromPolar(target.length() * factor, angle).translated(current)
            cp1 = revSource.p2()

        # the final curve, that joins to the last point
        self.path.quadTo(cp1, points[-1])

有一些算法允许构建插值的“样条”,但是你需要一些数学技能来理解它们,并创建一个创建平滑曲线的好系统。与此同时,一个可能的(但不是完美的)解决方案是创建控制点,这些控制点是从现有段的扩展中计算出来的(这类似于矢量图形编辑器所做的):

每个扩展的末端被用作贝塞尔曲线的控制点:对于第一段和最后一段,我使用二次曲线(一个控制点),而所有其他的都是三次曲线(两个控制点);这导致了一个可以接受的结果:

不幸的是,它远远不够完美,特别是对于某些角度和长度的组合:

我建议您仅在需要时构建路径(例如,点更改),而不是在paintEvent中。

class Window(QWidget):
    # ...

    def buildPath(self):
        self.path = QtGui.QPainterPath()
        self.path.moveTo(points[0])
        factor = .1412
        for p in range(len(points) - 2):
            p2 = points[p + 1]
            target = QtCore.QLineF(p2, points[p + 2])
            reverseTarget = QtCore.QLineF.fromPolar(
                target.length() * factor, target.angle() + 180).translated(p2)
            if not p:
                self.path.quadTo(reverseTarget.p2(), p2)
            else:
                p0 = points[p - 1]
                p1 = points[p]
                source = QtCore.QLineF(p0, p1)
                current = QtCore.QLineF(p1, p2)
                targetAngle = target.angleTo(current)
                if 90 < targetAngle < 270:
                    ratio = abs(sin(radians(targetAngle)))
                    reverseTarget.setLength(reverseTarget.length() * ratio)
                reverseSource = QtCore.QLineF.fromPolar(
                    source.length() * factor, source.angle()).translated(p1)
                sourceAngle = current.angleTo(source)
                if 90 < sourceAngle < 270:
                    ratio = abs(sin(radians(sourceAngle)))
                    reverseSource.setLength(reverseSource.length() * ratio)
                self.path.cubicTo(reverseSource.p2(), reverseTarget.p2(), p2)

        final = QtCore.QLineF(points[-3], points[-2])
        reverseFinal = QtCore.QLineF.fromPolar(
            final.length() * factor, final.angle()).translated(final.p2())
        self.path.quadTo(reverseFinal.p2(), points[-1])
 类似资料:
  • 我正在尝试使用GeoJSON在地图上绘制点。我阅读了文件,其中说明: 可以通过调用数据对象的loadGeoJSON()方法来加载和显示GeoJSON文件 (https://developers.google.com/maps/documentation/javascript/datalayer) 但是,同一页上的示例代码显示: map.data.loadGeoJson(…) 因此,我使用代码示例,

  • 问题内容: 我正在用Piccolo编写一个交互式applet,并且需要在其中包含高斯曲线(又称正态分布图)。 我想象任何一种Java实现都足够,但是我找不到。理想情况下,我想传递一组值并将图表绘制在面板,图像对象或可以嵌入在applet中的任何对象中。 在让我自己动手编写代码之前,有人知道做这件事的有用代码吗? 欢迎使用其他语言的实现,只要它们易于移植到Java中即可。 问题答案: 不知道它是否有

  • 问题内容: 我正在尝试用Java绘制曲线。一个简单的以(X,Y)开始,(X,Y)结束和曲线量的贝塞尔曲线就足够了。 我找不到在Swing中执行此操作的方法。如果不在Swing中,我可以使用一些简单的数学方法吗?我将如何在Swing中实现它? 编辑:我知道如何通过重写paint(Graphics g)方法绘制形状和线条。 问题答案: 您可以使用Java 2D Object Path2D.Double

  • 我画了4条线从中心到按钮,我给你看的照片。我不知道如何在图片中画出红色的曲线。 [在此处输入图像说明] 或 [在此输入图像说明(更简单)]

  • 问题内容: 这是我声明曲线的代码行: 现在我可以使用什么代码来绘制曲线?我尝试了类似的东西: 但显然那没有用。有什么建议? 问题答案: 我已经做了一个最小的测试用例,以证明您在这里的描述。该程序可以运行,但是除非能看到您正在使用的代码,否则我无法真正为您提供帮助。

  • 本文向大家介绍使用HTML5画布绘制贝塞尔曲线,包括了使用HTML5画布绘制贝塞尔曲线的使用技巧和注意事项,需要的朋友参考一下 是的,请使用HTML canvas方法在HTML5中绘制Bezier曲线。 示例 您可以尝试运行以下代码以使用画布绘制贝塞尔曲线: