10

In PyQT, how can I plot small "Nodes" at given points and connect them with edges? All of the PyQT tutorials I find are "plot a button! plot a checkbox!"

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Mark
  • 6,123
  • 13
  • 41
  • 52

2 Answers2

4

It has been a pain to find a good explanation for this (as of by the end of 2014 already), and since this question asks exactely what I was looking for, I'll post a transcription (from C++ to Python) of what I found in this post.

The code is below, and here is the rationale:

  1. QGrahpicsItem, QPainterPath and QPainterPath.Element are the classes you are looking for. Specifically, QPainterPath implements the kind of vector functionality you expect in applications such as CorelDraw, Adobe Illustrator, or Inkscape.
  2. The example below benefits from the pre-existing QGraphicsEllipseItem (for rendering nodes) and QGraphicsPathItem (for rendering the path itself), which inherit from QGraphicsItem.
  3. The Path constructor iterates over the QPainterPath elements, creating Node items for each one; Each of them, in turn, send updates to the parent Path object, which updates its path property accordingly.
  4. I found much, much easier to study the C++ Qt4 Docs than the rather less structured PyQt docs found elsewhere. Once you get used to mentally translate between C++ and Python, the docs themselves are a powerful way to learn how to use each class.

#!/usr/bin/env python
# coding: utf-8

from PyQt4.QtGui import *
from PyQt4.QtCore import *

rad = 5

class Node(QGraphicsEllipseItem):
    def __init__(self, path, index):
        super(Node, self).__init__(-rad, -rad, 2*rad, 2*rad)

        self.rad = rad
        self.path = path
        self.index = index

        self.setZValue(1)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.setBrush(Qt.green)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionChange:
            self.path.updateElement(self.index, value.toPointF())
        return QGraphicsEllipseItem.itemChange(self, change, value)


class Path(QGraphicsPathItem):
    def __init__(self, path, scene):
        super(Path, self).__init__(path)
        for i in xrange(path.elementCount()):
            node = Node(self, i)
            node.setPos(QPointF(path.elementAt(i)))
            scene.addItem(node)
        self.setPen(QPen(Qt.red, 1.75))        

    def updateElement(self, index, pos):
        path.setElementPositionAt(index, pos.x(), pos.y())
        self.setPath(path)


if __name__ == "__main__":

    app = QApplication([])

    path = QPainterPath()
    path.moveTo(0,0)
    path.cubicTo(-30, 70, 35, 115, 100, 100);
    path.lineTo(200, 100);
    path.cubicTo(200, 30, 150, -35, 60, -30);

    scene = QGraphicsScene()
    scene.addItem(Path(path, scene))

    view = QGraphicsView(scene)
    view.setRenderHint(QPainter.Antialiasing)
    view.resize(600, 400)
    view.show()
    app.exec_()
heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • This code has helped me so much lately! Thanks! Also I'm trying to plot an function in class Node - mousePressEvent / middle click to add a control point to path lineTo.() So that from point A > B I can add point C in the middle.. so I can do a "L" shape... meaning > A/B top left/right bottom and C being corner... But I'm not sure if its the right way as I don't detect red draw line only circles when I press... any hints how I can do it? Thanks! – Dariusz Sep 21 '16 at 21:15
  • 1
    @Dariusz I'm sure there is some sort of built-in "mouseover" event or flag in Qt, but I don't remember it's name, neigher which class has it. I would start searching for that in `QGraphicsItem`. – heltonbiker Sep 23 '16 at 12:32
  • 1
    @Dariusz, actually I found them, they're the `hoverEnterEvent` and `hoverLeaveEvent`, found [here](http://doc.qt.io/qt-4.8/qgraphicsitem.html). – heltonbiker Sep 23 '16 at 12:35
3

If you want to be able to interact with the objects displayed in the plot, you will be better off using a QGraphicsScene. It handles zooming and panning and can contain other QGraphicsItem objects that can handle their own interactions.

It's very easy to use, but there is a bit of overhead involved, especially if you plan to make thousands of objects.

You can find a PyQt tutorial here. This and the API docs should get you started.

drxzcl
  • 2,952
  • 1
  • 26
  • 28
  • Don't know about the tutorial that you have linked to. I'm getting segmentation fault whenever I close the PyQt application. – rbaleksandar Feb 23 '16 at 22:25
  • The tutorial is from 2008, so some things might have changed in the meantime. Segfault-on-close is usually a sign that something is wrong with the lifetime/configuration of your basic QT classes (QApplication, QMainWindow etc). If you can run tutorials for your PyQt version, you should be able to incorporate the necessary items to get QGraphicsScene to work. – drxzcl Feb 27 '16 at 19:13
  • I've fixed my issues. But yeah, version is old. – rbaleksandar Feb 27 '16 at 19:21