0

I'm writing a python application using pyside and matplotlib. Following a combination of this tutorial and this SO post, I have created a matplotlib widget that I can successfully add to a parent. However when I go to actually add data to it, nothing seems to get displayed.

If I add static data like the SO post had, it shows up, but when I change it to update on the fly (currently every second on a timer, but it will eventually be using a signal from another class), I never get anything but the empty axes to appear. I suspect that I'm missing a call to force a draw or invalidate or that there is something wrong with the way I'm calling update_datalim (though the values that get passed to it seem correct).

from PySide import QtCore, QtGui

import matplotlib
import random

matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'

from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle

from collections import namedtuple

DataModel = namedtuple('DataModel', ['start_x', 'start_y', 'width', 'height'])

class BaseWidget(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        self.axes.hold(False)
        self.axes.set_xlabel('X Label')
        self.axes.set_ylabel('Y Label')
        self.axes.set_title('My Data')

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)

        FigureCanvas.updateGeometry(self)

class DynamicWidget(BaseWidget):
    def set_data(self, the_data):
        self.axes.clear()
        xys = list()

        cmap = plt.cm.hot
        for datum in the_data:
            bottom_left = (datum.start_x, datum.start_y)
            top_right = (bottom_left[0] + datum.width, bottom_left[1] + datum.height)
            rect = Rectangle(
                xy=bottom_left,
                width=datum.width, height=datum.height, color=cmap(100)
            )
            xys.append(bottom_left)
            xys.append(top_right)
            self.axes.add_artist(rect)
        self.axes.update_datalim(xys)
        self.axes.figure.canvas.draw_idle()

class RandomDataWidget(DynamicWidget):
    def __init__(self, *args, **kwargs):
        DynamicWidget.__init__(self, *args, **kwargs)
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.generate_and_set_data)
        timer.start(1000)

    def generate_and_set_data(self):
        fake_data = [DataModel(
            start_x=random.randint(1, 100),
            width=random.randint(20, 40),
            start_y=random.randint(80, 160),
            height=random.randint(20, 90)
        ) for i in range(100)]

        self.set_data(fake_data)

Edit: I'm suspecting that there's an issue with updating the limits of the plot. When running the above code, the plot opens with limits of 0 and 1 on both the x and y axis. Since none of my generated data falls into that range, I created another subclass of DynamicWidget that plots only data between 0 and 1 (the same data from the linked SO post). When instantiating the class below, the data shows up successfully. Do I need to do something more than calling update_datalim to get the graph to re-bound itself?

class StaticWidget(DynamicWidget):
    def __init__(self):
        DynamicWidget.__init__(self)
        static_data = [
            DataModel(0.5, 0.05, 0.2, 0.05),
            DataModel(0.1, 0.2, 0.7, 0.2),
            DataModel(0.3, 0.1, 0.8, 0.1)
        ]
        self.set_data(static_data)
4
  • add self.axes.figure.canvas.draw_idle() into your set_data method. Commented Mar 31, 2016 at 20:54
  • @tcaswell I tried adding it to the end of the function, but it didn't seem to change anything. Commented Apr 1, 2016 at 12:10
  • I missed that you were calling update_datalim, that is an internal function, do not do that. Please. Commented Apr 2, 2016 at 2:27
  • @tcaswell Are you sure it's an internal function? The documentation for add_artist specifically mentions calling update_datalim Commented Apr 4, 2016 at 13:19

1 Answer 1

2

Yes, update_datalim only updates the bounding box that is kept internally by the axes. You also need to enable auto scaling for it to be used. Add self.axes.autoscale(enable=True) after the self.axes.clear() statement and it will work. Or you can set the axes' range to a fixed value by using self.axes.set_xlim and self.axes.set_ylim.

edit: here is my code, which works for me

from PySide import QtCore, QtGui

import matplotlib
import random, sys

matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'

from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle

from collections import namedtuple

DataModel = namedtuple('DataModel', ['start_x', 'start_y', 'width', 'height'])

class BaseWidget(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        self.axes.hold(False)
        #self.axes.autoscale(enable=True)
        self.axes.set_xlabel('X Label')
        self.axes.set_ylabel('Y Label')
        self.axes.set_title('My Data')

        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)

        FigureCanvas.updateGeometry(self)

class DynamicWidget(BaseWidget):
    def set_data(self, the_data):
        self.axes.clear()
        self.axes.autoscale(enable=True)
        #self.axes.set_xlim(0, 300)
        #self.axes.set_ylim(0, 300)
        xys = list()

        cmap = plt.cm.hot
        for datum in the_data:
            print datum
            bottom_left = (datum.start_x, datum.start_y)
            top_right = (bottom_left[0] + datum.width, bottom_left[1] + datum.height)
            rect = Rectangle(
                xy=bottom_left,
                width=datum.width, height=datum.height, color=cmap(100)
            )
            xys.append(bottom_left)
            xys.append(top_right)
            self.axes.add_artist(rect)
        self.axes.update_datalim(xys)
        self.axes.figure.canvas.draw_idle()

class RandomDataWidget(DynamicWidget):
    def __init__(self, *args, **kwargs):
        DynamicWidget.__init__(self, *args, **kwargs)
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.generate_and_set_data)
        timer.start(1000)

    def generate_and_set_data(self):
        fake_data = [DataModel(
            start_x=random.randint(1, 100),
            width=random.randint(20, 40),
            start_y=random.randint(80, 160),
            height=random.randint(20, 90)) for i in range(100)]

        self.set_data(fake_data)
        print "done:...\n\n"


def main():
    qApp = QtGui.QApplication(sys.argv)

    aw = RandomDataWidget()

    aw.show()
    aw.raise_()
    sys.exit(qApp.exec_())

if __name__ == "__main__":
    main()
Sign up to request clarification or add additional context in comments.

3 Comments

Adding only autoscale(enable=True) hasn't seemed to make it work for me. I appreciate the help, though. Any other ideas?
Be sure to add it after axes.clear, otherwise it will be reset. Or perhaps try to use set_xlim and set_ylim. In any case I have edited my answer to include my adapted code. This works for me so I would be very surprised if it doesn't work for you. Can you try it?
I think I'm an idiot - I had removed the draw_idle call whenever it hadn't worked for me. It's working now, thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.