2

I copied the example at Include matplotlib in pyqt5 with hover labels and placed the modifications suggested by @ImportanceOfBeingErnest . I have the sample app working with the notifications. The full code is at the bottom.

However, I now want to modify the contents of the plot from a button or menu item. For simplicity, I created a menu and added the action and defined the function updatePlot. I also tried with another function updatePlot2 which calls again to a modified get_canvas functions.

def updatePlot(self):
    print('executing update code')
    self.canvas.figure.axes.clear()
    x = np.linspace(0.1, 2*pi, 20)
    y = 3*cos(x)
    self.canvas.figure.axes.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)

def updatePlot2(self):
    print('executing update code')
    self.canvas.figure.axes.clear()
    x = np.linspace(0.1, 2*pi, 20)
    y = 3*cos(x)
    self.canvas = self.get_canvas2(2)

def get_canvas2(self,n):
    fig, ax = plt.subplots()
    ax.tick_params(labeltop=False, labelright=True)
    x = np.linspace(0.1, 2*pi, 20)
    y = n*cos(x)
    ax.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)
    canvas = FigureCanvas(fig)
    self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
    return canvas

No matter what I do, I cannot make it work. It gives me erros such as "list object has no atribute plot", or no axes attribute. If I use the updatePlot2, I get the correct output in the console (spyder) but no updating in the UI.

Thanks in advance for your help. Here is the full code:

import matplotlib.pyplot as plt import scipy.spatial as spatial import numpy as np from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, QDockWidget, QListWidget) from PyQt5.QtCore import Qt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import (NavigationToolbar2QT as NavigationToolbar) import sys

pi = np.pi cos = np.cos


def fmt(x, y):
    return 'Date: {x:0.2f}\nValue: {y:0.2f}\nDuration: {x:0.2f}'.format(x=x, y=y)

class FollowDotCursor(object):
    """Display the x,y location of the nearest data point.
    https://stackoverflow.com/a/4674445/190597 (Joe Kington)
    https://stackoverflow.com/a/13306887/190597 (unutbu)
    https://stackoverflow.com/a/15454427/190597 (unutbu)
    """
    def __init__(self, ax, x, y, tolerance=5, formatter=fmt, offsets=(-20, 20)):
        try:
            x = np.asarray(x, dtype='float')
        except (TypeError, ValueError):
            x = np.asarray(mdates.date2num(x), dtype='float')
        y = np.asarray(y, dtype='float')
        mask = ~(np.isnan(x) | np.isnan(y))
        x = x[mask]
        y = y[mask]
        self._points = np.column_stack((x, y))
        self.offsets = offsets
        y = y[np.abs(y-y.mean()) <= 3*y.std()]
        self.scale = x.ptp()
        self.scale = y.ptp() / self.scale if self.scale else 1
        self.tree = spatial.cKDTree(self.scaled(self._points))
        self.formatter = formatter
        self.tolerance = tolerance
        self.ax = ax
        self.fig = ax.figure
        self.ax.xaxis.set_label_position('top')
        self.dot = ax.scatter(
            [x.min()], [y.min()], s=180, color='green', alpha=0.7)
        self.annotation = self.setup_annotation()
        self.cid = self.fig.canvas.mpl_connect('motion_notify_event', self)

    def scaled(self, points):
        points = np.asarray(points)
        return points * (self.scale, 1)

    def __call__(self, event):
        ax = self.ax
        # event.inaxes is always the current axis. If you use twinx, ax could be
        # a different axis.
        if event.inaxes == ax:
            x, y = event.xdata, event.ydata
        elif event.inaxes is None:
            return
        else:
            inv = ax.transData.inverted()
            x, y = inv.transform([(event.x, event.y)]).ravel()
        annotation = self.annotation
        x, y = self.snap(x, y)
        annotation.xy = x, y
        annotation.set_text(self.formatter(x, y))
        self.dot.set_offsets((x, y))
        bbox = ax.viewLim
        event.canvas.draw()

    def setup_annotation(self):
        """Draw and hide the annotation box."""
        annotation = self.ax.annotate('', xy=(0, 0), ha = 'right', xytext = self.offsets, textcoords = 'offset points', va = 'bottom', bbox = dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.75),arrowprops = dict(arrowstyle='->', connectionstyle='arc3,rad=0'))
        return annotation

    def snap(self, x, y):
        """Return the value in self.tree closest to x, y."""
        dist, idx = self.tree.query(self.scaled((x, y)), k=1, p=1)
        try:
            return self._points[idx]
        except IndexError:
            # IndexError: index out of bounds
            return self._points[0]


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = 'Testing'
        self.setWindowTitle(self.title)
        self.width = 1000
        self.height = 800
        self.setGeometry(10, 30, self.width, self.height)

        self.statusBar().showMessage('Ready')

        # Create menu and items
        mainMenu = self.menuBar()
        mainMenu.setNativeMenuBar(False)
        fileMenu = mainMenu.addMenu('Sources')
        # Update Plot
        btnUpdate = QAction(QIcon(''), 'Update Chart', self)
        btnUpdate.setShortcut('Ctrl+U')
        btnUpdate.setStatusTip('Clic to update chart.')
        btnUpdate.triggered.connect(self.updatePlot)
        fileMenu.addAction(btnUpdate)

        fileMenu.addSeparator()

        exitButton = QAction(QIcon('exit24.png'), 'Exit', self)
        exitButton.setShortcut('Ctrl+Q')
        exitButton.setStatusTip('Exit application')
        exitButton.triggered.connect(self.close)
        fileMenu.addAction(exitButton)

        self.canvas = self.get_canvas()
        self.toolbar = NavigationToolbar(self.canvas, self)


        w = QWidget()
        w.layout = QVBoxLayout()
        w.layout.addWidget(self.canvas)
        w.layout.addWidget(self.toolbar)
        w.setLayout(w.layout)

        self.setCentralWidget(w)
        # show maximize     
        self.showMaximized()

    def updatePlot(self):
        print('executing update code')
        self.canvas.figure.axes.clear()
        x = np.linspace(0.1, 2*pi, 20)
        y = 3*cos(x)
        self.canvas.figure.axes.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)

    def updatePlot2(self):
        print('executing update code')
        self.canvas.figure.axes.clear()
        x = np.linspace(0.1, 2*pi, 20)
        y = 3*cos(x)
        self.canvas = self.get_canvas2(2)

    def get_canvas2(self,n):
        fig, ax = plt.subplots()
        ax.tick_params(labeltop=False, labelright=True)
        x = np.linspace(0.1, 2*pi, 20)
        y = n*cos(x)
        ax.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)



        canvas = FigureCanvas(fig)

        self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
        return canvas

    def get_canvas(self):
        fig, ax = plt.subplots()
        ax.tick_params(labeltop=False, labelright=True)
        x = np.linspace(0.1, 2*pi, 20)
        y = cos(x)
        ax.plot(x, y, color='blue', marker='o', linestyle='dashed',linewidth=2, markersize=8)



        canvas = FigureCanvas(fig)

        self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
        return canvas

app = QApplication(sys.argv) win = MainWindow() sys.exit(app.exec_())

1 Answer 1

0

How the error is indicated:

AttributeError: 'list' object has no attribute 'plot'

axes is a list that has several AxesSubplot since with plt.subplots() you can generate several axes, so in your case the solution is to use the first (and only one that exists):

self.canvas.figure.axes[0].plot(...)
Sign up to request clarification or add additional context in comments.

5 Comments

Hi, thanks a lot for that. It was a silly mistake but you saved me hours!
Thanks, didn´t know about that!
By the way, I noticed that after clicking on update, the plot does not refresh until I hover into it... I made another implementation using qtcreator and it refreshed right after clicking the buttons (but without the annotations, that's why I went for this solution). Do you have any Idea on why is it happening?
@Juano that's another problem so you should create another question in a new post
Just for the record, I solved the non-refreshing issue by calling self.canvas.draw() within the update function!

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.