3

Below is python code to demonstrate the problem.
If there are 2 rows and 2 columns of images, for example, typing/erasing in the textbox is reasonably fast. However, if there are 5 rows and 5 columns, typing/erasing in the textbox is quite slow. If the xticks and yticks are drawn, interaction is even slower. So, it seems as if the entire figure is redrawn after every keystroke.
Is there a solution for this (apart from putting the textbox on a separate figure)?
(My development platform is MacOS Mojave, Python 3.7.5.)

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import TextBox

class Textbox_Demo(object):
    def __init__(self):
        self.fig = plt.figure(figsize=(8,8))
        self.string = 'label'

        self.rows = 5   # reducing rows speeds up textbox interaction
        self.cols = 5   # reducing cols speeds up textbox interaction
        self.plot_count = self.rows * self.cols
        self.gs = gridspec.GridSpec(self.rows, self.cols,
            left=0.05, right=1-0.02, top=1-.02,  bottom=0.10, wspace=0.3, hspace=0.4)
        for k in range(self.plot_count):
            ax = self.fig.add_subplot(self.gs[k])
            #ax.set_xticks([])  # showing axes slows textbox interaction
            #ax.set_yticks([])  # showing axes slows textbox interaction
            data = np.atleast_2d(np.sin(np.linspace(1,255,255) * 50))
            ax.imshow(data, aspect="auto", cmap='ocean')

        # this is the user-input textbox
        tb_axis = plt.axes([0.125, 0.02, 0.8, 0.05])
        self.tb = TextBox(tb_axis, 'Enter label:', initial=self.string, label_pad=0.01)
        self.tb.on_submit(self.on_submit)

        plt.show()

    def on_submit(self, text):
        pass

if __name__ == "__main__":
    Textbox_Demo()

1 Answer 1

2

Matplotlib's TextBox is inherently slow, because it uses the drawing tools provided by matplotlib itself and hence redraws the complete figure upon changes.

I would propose to use a text box of a GUI kit instead. For example for PyQt this might look like:

enter image description here

import numpy as np
import sys
from matplotlib.backends.backend_qt5agg import (
        FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.backends.qt_compat import QtCore, QtWidgets
import matplotlib.gridspec as gridspec
from matplotlib.figure import Figure


class Textbox_Demo(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        layout = QtWidgets.QVBoxLayout(self._main)
        layout.setContentsMargins(0,0,0,0)
        layout.setSpacing(0)

        self.fig = Figure(figsize=(8,8))
        self.canvas = FigureCanvas(self.fig)
        layout.addWidget(self.canvas)
        self.addToolBar(NavigationToolbar(self.canvas, self))

        self._textwidget = QtWidgets.QWidget()
        textlayout = QtWidgets.QHBoxLayout(self._textwidget)
        self.textbox = QtWidgets.QLineEdit(self)
        self.textbox.editingFinished.connect(self.on_submit)
        # or, if wanting to have changed apply directly:
        # self.textbox.textEdited.connect(self.on_submit)
        textlayout.addWidget(QtWidgets.QLabel("Enter Text: "))
        textlayout.addWidget(self.textbox)
        layout.addWidget(self._textwidget)

        self.fill_figure()

    def fill_figure(self):
        self.string = 'label'

        self.rows = 5   # reducing rows speeds up textbox interaction
        self.cols = 5   # reducing cols speeds up textbox interaction
        self.plot_count = self.rows * self.cols
        self.gs = gridspec.GridSpec(self.rows, self.cols,
            left=0.05, right=1-0.02, top=1-.02,  bottom=0.10, wspace=0.3, hspace=0.4)
        for k in range(self.plot_count):
            ax = self.fig.add_subplot(self.gs[k])
            #ax.set_xticks([])  # showing axes slows textbox interaction
            #ax.set_yticks([])  # showing axes slows textbox interaction
            data = np.atleast_2d(np.sin(np.linspace(1,255,255) * 50))
            ax.imshow(data, aspect="auto", cmap='ocean')

    def on_submit(self):
        text = self.textbox.text()
        print(text)
        pass


if __name__ == "__main__":
    qapp = QtWidgets.QApplication(sys.argv)
    app = Textbox_Demo()
    app.show()
    qapp.exec_()
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for confirming that matplotlib is redrawing the entire figure on each keystroke. Also, thanks for taking the time to provide a working Qt solution.

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.