How to draw a line with animation in PyQt4
Asked Answered
W

2

10

I have a list of points. For example, points = [[160, 75], [115, 567]].

How to draw a line in PyQt4, so it would be something like this: Line

Thanks in advance.

EDIT: For the record, I'm trying to implement Bezier Curves, so it looked like this: Quartic Bézier curve

Here is the code I have at the moment:

from PyQt4.QtGui import QWidget, QPolygonF, QPainter, QPen, QBrush, QColor, \
    QApplication, QIcon, QVBoxLayout, QSlider, QHBoxLayout, QPushButton, QLCDNumber
from PyQt4.QtCore import QObject, SIGNAL, SLOT, QPointF, Qt, QRectF, QPointF
import time, sys

#================================================================
# For the next block I used this post
# https://mcmap.net/q/451844/-bezier-path-widening, 
# but I wish to replace it with self.liniar_bez(),
# biliniar_bez(), self.cubic_bez() and self.fourG_bez()
# because I want to control 't' with self.slider

def avg(a, b):
  xa, ya = a
  xb, yb = b
  return (xa + xb) * 0.5, (ya + yb) * 0.5

def bez4split(p0, p1, p2, p3, p4):
    p01 = avg(p0, p1)
    p12 = avg(p1, p2)
    p23 = avg(p2, p3)
    p34 = avg(p3, p4)
    p012 = avg(p01, p12)
    p123 = avg(p12, p23)
    p234 = avg(p23, p34)
    p0123 = avg(p012, p123)
    p1234 = avg(p123, p234)
    p01234 = avg(p0123, p1234)
    return [(p0, p01, p012, p0123, p01234),
        (p01234, p1234, p234, p34, p4)]

def bez4(p0, p1, p2, p3, p4, levels=5):
    if levels <= 0:
        return [p0, p4]
    else:
        (a0, a1, a2, a3, a4), (b0, b1, b2, b3, b4) = bez4split(p0, p1, p2, p3, p4)
        return (bez4(a0, a1, a2, a3, a4, levels - 1) +
                bez4(b0, b1, b2, b3, b4, levels - 1)[1:])
#================================================================

points = [[160, 75], [115, 567], [292, 58], [685, 194], [734, 517]]
coords = bez4(*points)

class Bezier(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.lcd = QLCDNumber(self)
        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.setRange(0, 100)
        self.closeButton = QPushButton('Close')

        bottomLayout = QHBoxLayout()
        bottomLayout.addWidget(self.lcd)
        bottomLayout.addWidget(self.slider)
        bottomLayout.addWidget(self.closeButton)

        mainLayout = QVBoxLayout()
        mainLayout.addStretch(1)
        mainLayout.addLayout(bottomLayout)

        self.setLayout(mainLayout)

        self.time = 0
        self.time_step = 0.025
        self.timer_id = self.startTimer(1)

        self.tracking = None

        QObject.connect(self.closeButton, SIGNAL('clicked(bool)'), app.exit)
        QObject.connect(self.slider, SIGNAL('valueChanged(int)'), self.lcd, SLOT('display(float)'))
        #self.slider.valueChanged.connect(self.lcd.display)

        self.setWindowTitle('Bonus Example')

    def poly(self, pts):
        return QPolygonF(map(lambda p: QPointF(*p), pts))

    def bezAnimation(self):
        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)

        pts = points[:]
        crds = coords[:]

        painter.setPen(QPen(QColor(Qt.lightGray), 3, Qt.DashDotDotLine))
        painter.drawPolyline(self.poly(pts))

        painter.setBrush(QBrush(QColor(255, 025, 0)))
        painter.setPen(QPen(QColor(Qt.lightGray), 1))
        for x, y in pts:
            painter.drawEllipse(QRectF(x - 4, y - 4, 8, 8))

    def paintEvent(self, event):
        self.bezAnimation()

    # As you know, 0.00 > 't' > 1.00
    # and QSlider does not provide support for float numbers,
    # so we simply divide t by 100
    def liniar_bez(self, p, t):
        t = t / 100.0
        new_p[0] = (1 - t) * p[0][0] + t * p[1][0]
        new_p[1] = (1 - t) * p[0][1] + t * p[1][1]

    def biliniar_bez(self, p, t):
        t = t / 100.0
        new_p[0][0] = ((1 - t) ** 2) * p[0][0] + 2 * (1 - t) * t * p[1][0] + (t ** 2) * p[2][0]
        new_p[0][1] = ((1 - t) ** 2) * p[0][1] + 2 * (1 - t) * t * p[1][1] + (t ** 2) * p[2][1]

    def cubic_bez(self, p, t):
        t = t / 100.0
        new_p[0][0] = ((1 - t) ** 3) * p[0][0] + 3 * ((1 - t) ** 2) * t * p[1][0] + (t ** 2) * p[2][0] + (t ** 3) * p[3][0]
        new_p[0][1] = ((1 - t) ** 3) * p[0][1] + 3 * ((1 - t) ** 2) * t * p[1][1] + (t ** 2) * p[2][1] + (t ** 3) * p[3][1]

    def fourG_bez(self, p, t):
        t = t / 100.0
        new_p[0][0] = ((1 - t) ** 4) * p[0][0] + 4 * ((1 - t) ** 3) * t * p[1][0] + 4 * (1 - t) * (t ** 2) * p[2][0] + 4 * (1 - t) * (t ** 3) * p[3][0] + (t ** 4) * p[4][0]
        new_p[0][1] = ((1 - t) ** 4) * p[0][1] + 4 * ((1 - t) ** 3) * t * p[1][1] + 4 * (1 - t) * (t ** 2) * p[2][1] + 4 * (1 - t) * (t ** 3) * p[3][1] + (t ** 4) * p[4][1]

    def timerEvent(self, event):
        if self.timer_id == event.timerId():
            #self.bezAnimation()
            self.time += self.time_step
            self.update()

    def closeEvent(self, event):
        self.deleteLater()

    #================================================================
    # And this one too
    # https://mcmap.net/q/451844/-bezier-path-widening
    def mousePressEvent(self, event):
        i = min(range(len(points)),
            key=lambda i: (event.x() - points[i][0]) ** 2 +
                      (event.y() - points[i][1]) ** 2)

        self.tracking = lambda p: points.__setitem__(i, p)

    def mouseMoveEvent(self, event):
       if self.tracking:
            self.tracking((event.x(), event.y()))
            self.update()

    def mouseReleaseEvent(self, event):
        self.tracking = None
    #================================================================

if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = Bezier()
    form.setGeometry(10, 30, 871, 727)
    form.show()
    sys.exit(app.exec_())

So, my idea about how to solve this problem: 1. Buid all curves with self.biliniar_bez(),..., self.fourG_bez() 2. They share the same points, so they'll actually match in this points. 3. Move points (I think, that their position must match with t, so if t=0.25 then every point must pass at this time quarter of their lines)

The problem is in the way of moving points. I have coordinates of first and second point of every line. Need middle ones.

Walliw answered 24/4, 2012 at 15:51 Comment(1)
Have you tried anything yet? What part are you stuck on? I remember answering your other question on an animated wave, so I figured this would have gotten you started right? #9465547 . Its obviously not the math that has you stuck... so where are you having trouble more specifically?Hamforrd
H
3
  1. If you are using normal widgets then you would be looking at QPainter.drawPath()
  2. If you are using QGraphics, then you would be looking at QGraphicsPathItem

Either of these two would have similar approaches where during your draw you would have to build the line incrementally over time. Review your own previous question for a specific example of how to draw a line over time: Make an animated wave with drawPolyline in PySide/PyQt

As an example, if you were using QGraphics, then you would have a QGraphicsPathItem instance in your scene. Then you would animate it by looping and updating QPainterPath by incrementally setting the complete path: http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qpainterpath.html#cubicTo

Hamforrd answered 26/4, 2012 at 0:17 Comment(4)
I've added some code I have and post idea of solving, but, in my opinion, it's not good enough.Walliw
@Saul_Tigh: Is there any way you can break that code example down into a smaller specific and runnable problem? With all of that code I am not really sure where to address your issue. Also, its not runnable anyways. There is no self.updatePoints(painter). You need to ask a specific question. This is too vague.Hamforrd
my question is about how to move one point between two another points (like on first image) to draw a line. I have 2 points (no points between them) and that cause my problem.Walliw
@Saul_Tigh: well if i havent answered your question for that goal already then I dont know what else to suggest. You have to keep redrawing the complete line over time which makes it look like its growing. You cant just move a point. It has to be simulated. Say pt1 is (0,0) and final pt2 is (0,10). You loop and draw (0,0),(0,1) then (0,0),(0,2) and so onHamforrd
S
2

Refer the below code in your paint event to draw a line in Qt.

def paintEvent(self, event):
    painter = Qt.QPainter()
    painter.begin(self)
    painter.pen().setWidth(int width)
painter.setPen(Qt.QColor(100,100,100))
    painter.drawLine(x1,y1,x2,y2)
    painter.end()
    Qt.QFrame.paintEvent(self, event)
Supernova answered 14/2, 2013 at 7:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.