[PyKDE] How to do *INTERACTIVE* plotting with PyQt (or PyQwt)

Peter Lipa porl3141 at hotmail.com
Wed Sep 11 01:37:00 BST 2002


Hi Guys,
Thanks again for every suggestion and help with this problem. I am thinking
about and pondering every idea you guys suggested. If you any of you sends
me your snail mail address I'll send the promised postcard from Tucson!

Sorry if I can't comment each one here. I just focus on the 2 concrete
examples suggested by Gerard Vermeulden and Boudewijn Rempt:

Gerard's solution - to have a PyQt app shell running another instance of the
interpreter inside is working! Thanks for your suggestions and sharing the
testcode, Gerard! I really appreciate it!

Comments to Gerard's solution:
It's great, it works, and is the obvious way to go ..., however, my
colleague  is developed already a python shell (called Neuralab) on Windows
using MFC  and has the python interpreter embedded in C. Now we would like
to run PyQt graphics within the this shell. Having another PyQt interpreter
shell (pycute.py) running inside the C-embedded python shell is possible,
but the communication then has to go through 2 layers of python interpreters
(and makes things a little ugly, I guess). So this solution is perfect if
one targets a PyQt shell or re-writes Neuralab in PyQt.  A good, portable
opensouce PyQt shell would be of great benefit for everybody. (PyQt crashes
the PythonWin shell from ActiveState, and does not integrate smoothly into
wxWindows and Tkinter based shells either...)

Comments ot Boudewijn's sample code:
Thanks a lot for this example. It is half of what I want. My key problem is
that I need a mechanism to push those buttons in your command window by user
commands entered from the python COMMAND LINE (I never had a problem issuing
commands from a GUI element such as a button.) Gerard embeds another
interpreter instance within the pyQt main app and thus circumvents the
problem. I modified your code a little bit (the last 2 lines in the file
plotter.py) so that the whole app runs in a python thread and gives control
back to the command line (see the attached plotter1.py example). NOW the
problem is that I am not able to construct ANY mechanism to 'push' those
'command buttons' from the command line. Whatever I tried (with QThreads or
python treads) locked up all pyqt GUIs. The problem seems to be that all
pyQt function calls have to be done from within one python thread. So I
attached a second thread to the QMainWindow which listens to messages via
global string variables (with approrate locks) and calls the approriate
'slots'. Apparently that doesn't work any slot function called from this 2nd
thread calls indirectly pyqt code and the rule is that all QT functions have
to be called from one and the same python thread only! The real problem is
that I can not get INTO the Qt eventloop from withing python. If there were
a pyQt function that would be called from within the PyQt eventloop at each
iteration (a callback function) I think it would work - but I haven't found
such a function in PyQt!
In a Nutshell, Boudewijn, if you can modify the plotter1.py example such
that I can type in the interpreter any of the three commands:

>>> import plotter1 as p1
this brings up the command window with the 4 buttons

>>> p1.NewPlotter()
brings up a new graph window (just like the button press)

>>> p1.StartComputation()
>>> p1.StopComputation()

then my problem would be solved! This would be my preferred solution (over
embedding another python instance in a pyQt
shell such as pycute.py). Boudewijn, maybe you have an idea how to do that!


Jonathan Gardner suggested using sockets for communication between the
threads - something I haven't tried yet, but I doubt that it would solve the
threading problem. However, I still have to think about that some more....

Thanks again for all your support!
Cheers
Peter Lipa
porl3141 at hotmail.com
plipa at earthlink.net



----- Original Messages From Gerard Vermeulen and Boudewijn Rempt  -----
From: "Gerard Vermeulen" <gvermeul at grenoble.cnrs.fr>
To: "Peter Lipa" <porl3141 at hotmail.com>
Cc: "Phil Thompson" <phil at river-bank.demon.co.uk>; "Gerard Vermeulen"
<gvermeul at grenoble.cnrs.fr>; "Boudewijn Rempt" <boud at valdyas.org>;
<pykde at mats.gmd.de>; <thaneplummer at hotmail.com>
Sent: Monday, September 09, 2002 12:14 AM
Subject: Re: [PyKDE] How to do *INTERACTIVE* plotting with PyQt (or PyQwt)


> Hi,
>
>
> On Fri, Sep 06, 2002 at 06:21:16PM -0700, Peter Lipa wrote:
> > Hi Guys (Phil, Gerard and Boudewijn),
> > Thanks so much for your replies - I really appreciate any help or
> > suggestion. Unfortunatley I am still desparate....
> > I seems to me, that only Gerard Vermeulen understood fully what I *need*
to
> > do (since he tried  that before).
> > I looked a bit at eric and this is not quite what I want (it is a qt app
> > with a single qApp in the main thread and runs
> > a independent - and isolated- python shell inside which in turn can run
a
> > single Qt app to be debugged).
> > My problem is the other way round. I want to run several Qt windows and
send
> > commands via the interpreter to these
> > windows, such as, add a line (xarray,yarray) to the current axes ).
>
> Here you have a little PyQt Python shell (can be integrate in any
application),
> called PyCute. It is still in the proof of concept stage, but it works
> with some limitations.
>
> Cut&paste from PyCute follows:
>
> Python 2.2.1 (#1, Aug 28 2002, 08:16:58)
> [GCC 2.95.3 20010315 (release)] on linux2
> Type "copyright", "credits" or "license" for more information.
> >>> from Numeric import *
> >>> from plot import *
> >>> x = arange(-2*pi, 2*pi, 0.01)
> >>> y1 = sin(x)
> >>> y2 = cos(x)
> >>> c1 = Curve(x, y1, red, "sin(x)")
> >>> c2 = Curve(x, y2, blue, "cos(x)")
> >>> p = Plot(title="Plot generated from PyCute", curves=[c1, c2])
> >>>
>
> It initializes x-axis data from -2*pi to 2*pi with steps of 0.01 and
> calculates the sine and cosine.
>
> The plot module is sugar coating for my wrappers of the Qwt C++ library.
> The curves c1 and c2 have x-data, y-data, color and a legend label
> The last statement creates a plot and shows it.
>
> Screenshots are included in the tarball
>
> Regards Gerard.
>
> PS: PyCute needs still quite a lot of work. You can steal ideas from
> PyShell.py (the idle environment that uses Tkinter), code.py (the Python
> library) and eric (PyQt's debugger).
>

----- Original Messages From Boudewijn Rempt  -----

I'd like to be sure I understand your requirements -- I've been
hacking a bit, but it's quite possible that what I've done
doesn't fit them. So, to summarize, you want:

* One command window where the user can type in Python and commands
* One or more windows where the results of those commands can be drawn.
* The results of those commands must be computed asynchronously: as soon
as the user types enter, the command must be dispatched to the window and
control given back to the user.
* The windows must be capable of performing long running commands
independently, without hindering the other windows.

Is that about right?

If you look at the attached little script you see a main window with
a few buttons (I was too lazy to code up a command line interpreter,
but the principle is the same, of course). You can add new windows,
but the commands always affect the latest window (I was too lazy
to code up focus management, too). One of the buttons starts a long
running thread, the other buttons directly paints a bit on the newest
window. Note that every window is QWidget: this is not necessary,
you could use any QWidget derived class, as long as there's only one
QApplication instance in your Python process.

This looks more or less like what you describe, I think.

--
Boudewijn Rempt | http://www.valdyas.org
-------------- next part --------------
#!/usr/bin/env python

import sys, time, threading, random, Queue
from qt import *

rand = random.Random()

class PlotWindow(QWidget):

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self.offset = 10
        self.running = 0
        
    def startComplicatedComputation(self):
        self.queue = Queue.Queue()
        self.timer = QTimer()
        self.connect(self.timer, SIGNAL("timeout()"),
                     self.periodicCall)
        self.timer.start(100)
        self.running = 1
        self.thread1 = threading.Thread(target = self.workerThread)
        self.thread1.start()


    def stopRunning(self):
        self.running = 0
        self.timer.stop()
        self.periodicCall()
        
        
    def periodicCall(self):
        while self.queue.qsize():
            try:
                (x,y) = self.queue.get(0)
                p = QPainter()
                p.begin(self)
                p.drawEllipse(x, y, x + 10, y + 10)
                p.flush()
                p.end()
            except Queue.Empty:
                pass


    def workerThread(self):
        while self.running:
            time.sleep(rand.random() * 0.3)
            x = rand.random() * 100
            y = rand.random() * 100
            self.queue.put((x,y))

        
    def addBitOfPlotting(self):
        p = QPainter()
        p.begin(self)
        p.drawRoundRect(self.offset, self.offset, 40, 40)
        self.offset += 10
        p.flush()
        p.end()

class CommandWindow(QMainWindow):

    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.mainWidget = QWidget(self)
        self.setCentralWidget(self.mainWidget)
        self.layout = QVBoxLayout(self.mainWidget)
        self.layout.setAutoAdd(1)
        self.bnNewPlotter = QPushButton("New Plotter", self.mainWidget)
        self.bnStartComp = QPushButton("Start computation in active plotter",
                                       self.mainWidget)
        self.bnStopComp = QPushButton("Stop computation in active plotter",
                                      self.mainWidget)
        self.bnAddBitOfPlotting = QPushButton("Add bit of plotting",
                                              self.mainWidget)
        self.connect(self.bnNewPlotter, SIGNAL("clicked()"),
                     self.slotNewPlotter)
        self.connect(self.bnStartComp, SIGNAL("clicked()"),
                     self.slotStartComp)
        self.connect(self.bnStopComp, SIGNAL("clicked()"),
                     self.slotStopComp)
        self.connect(self.bnAddBitOfPlotting, SIGNAL("clicked()"),
                     self.slotAddBitOfPlotting)

        self.plotters = []
        
    def slotNewPlotter(self):
        pw = PlotWindow()
        pw.show()
        self.plotters.append(pw)

    def slotStartComp(self):
        self.plotters[-1].startComplicatedComputation()

    def slotAddBitOfPlotting(self):
        self.plotters[-1].addBitOfPlotting()

    def slotStopComp(self):
        self.plotters[-1].stopRunning()

        
def main(argv=['']):
    app = QApplication(argv)
    mw = CommandWindow()
    app.setMainWidget(mw)
    mw.show()
    mw.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()'))
    app.exec_loop()

#if __name__ == "__main__":
#    main(sys.argv)
plotThread = threading.Thread(target=main)
plotThread.start()



More information about the PyQt mailing list