0

In this example you can input point coordinates which are then saved in a treeview and plotted. Simple enough. What I've found though is that I can "initialize" my plot to be formatted how I want, but as soon as I add the first point, the plot seems to "grow" and take up more of the tkinter window. I have no idea why this happens or how to control this so it maintains its original size. Thanks.

import tkinter as tk
from tkinter import ttk
import numpy as np

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib
from matplotlib.figure import Figure
matplotlib.use("TkAgg")


class MainGUI(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title('Title')
        self.geometry('750x500')

        self.nb = ttk.Notebook(self)
        self.nb.grid(row=0, column=0, columnspan=5, rowspan=4, sticky='NESW')
        self.tab2 = ttk.Frame(self.nb)
        self.nb.add(self.tab2, text='Tab2')

        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        tab2_r = 8
        tab2_c = 4
        for i in range(tab2_r):
            self.tab2.rowconfigure(i, weight=1)
        for i in range(tab2_c):
            self.tab2.columnconfigure(i, weight=1)

        self.makeTable()
        self.makePlot()
        self.makeWidgets()

    def makeWidgets(self):
        self.Labels = []
        self.Entries = []
        self.labText = [('X Location:'), ('Y Location:')]

        self.xGrid = 1
        self.yGrid = int(np.ceil(len(self.labText) / self.xGrid))
        i = 0
        for j in range(0, self.xGrid + 1, 2):
            for k in range(self.yGrid):
                if(i == len(self.labText)):
                    break
                else:
                    self.label = tk.Label(self.tab2, text=self.labText[i])
                    self.label.grid(column=j, row=k + 8, sticky='SW')
                    self.Labels.append(self.label)
                    self.entry = tk.Entry(self.tab2)
                    self.entry.insert(0, '0.0000')
                    self.entry.grid(column=j + 1, row=k + 8, sticky='NS')
                    self.Entries.append(self.entry)
                    i += 1

        self.addBtn = tk.Button(self.tab2, text='Add Entry', command=self.addEntry)
        self.addBtn.grid(column=self.xGrid + 1, row=self.yGrid + 9, sticky='NSEW')

    def makeTable(self):
        tab_header = ['Pattern #', 'Description']
        self.tree = ttk.Treeview(self.tab2, columns=tab_header, height=5, show="headings")
        vsb = ttk.Scrollbar(self.tab2, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=vsb.set)
        self.tree.grid(column=0, row=0, columnspan=2, rowspan=5, sticky='NSEW')
        vsb.grid(column=1, row=0, rowspan=5, sticky='ENS')
        self.tree.heading(tab_header[0], text=tab_header[0].title())
        self.tree.column(tab_header[0], width=30, anchor='center')
        self.tree.heading(tab_header[1], text=tab_header[1].title())
        self.tree.column(tab_header[1], width=170, anchor='center')
        self.tree.insert('', 'end', values=("", "(new)"))
        self.counter = 1

    def addEntry(self):
        check = (len(self.tree.get_children()) == 1)
        self.description = "Location: " + self.Entries[0].get() + ", " + self.Entries[1].get()
        self.tree.insert('', 'end', values=(self.counter, self.description))
        newEntry = [float(self.Entries[0].get()), float(self.Entries[1].get())]
        if(check == True):
            self.points = newEntry
        else:
            self.points = np.vstack((self.points, newEntry))
        self.counter += 1
        self.plotstuff()

    def makePlot(self):
        self.fig = Figure(figsize=(1, 1), dpi=100)
        self.ptrnFig = self.fig.add_subplot(111)
        self.ptrnFig.plot([1], [1])
        self.ptrnFig.axis([-1, 1, -1, 1])
        self.ptrnFig.spines['left'].set_position('zero')
        self.ptrnFig.spines['bottom'].set_position('zero')
        self.ptrnFig.grid(True)
        self.canvas = FigureCanvasTkAgg(self.fig, self.tab2)
        self.canvas.draw()
        self.canvas.get_tk_widget().grid(column=2, row=0, columnspan=2, rowspan=5, sticky='NSEW')

    def plotstuff(self):
        self.ptrnFig.cla()
        if(np.ndim(self.points) == 1):
            x = self.points[0]
            y = self.points[1]
        else:
            x = self.points[:, 0]
            y = self.points[:, 1]
        self.ptrnFig.grid(True)
        self.ptrnFig.axis('equal')
        self.ptrnFig.scatter(x, y, c="b", marker="o")
        self.canvas = FigureCanvasTkAgg(self.fig, self.tab2)
        self.canvas.draw()
        self.canvas.get_tk_widget().grid(column=2, row=0, columnspan=2, rowspan=5, sticky='NSEW')


def main():
    MainGUI().mainloop()


if __name__ == '__main__':
    main()

1 Answer 1

1

I cannot really say why the plot area grows. What I can tell you, though, is that you are using the wrong strategy. You are creating a new figure and new canvas everytime you're adding a new point, which is wasting a lot of energy and causing you your problems. Instead, you should create an empty plot, with an empty artist, and add new points to this artist everytime the button is clicked.

Consider the code (the modifications I made are mostly in makePlot() and plotstuff()):

import tkinter as tk
from tkinter import ttk
import numpy as np

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib
from matplotlib.figure import Figure
matplotlib.use("TkAgg")


class MainGUI(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.title('Title')
        self.geometry('750x500')

        self.nb = ttk.Notebook(self)
        self.nb.grid(row=0, column=0, columnspan=5, rowspan=4, sticky='NESW')
        self.tab2 = ttk.Frame(self.nb)
        self.nb.add(self.tab2, text='Tab2')

        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        tab2_r = 8
        tab2_c = 4
        for i in range(tab2_r):
            self.tab2.rowconfigure(i, weight=1)
        for i in range(tab2_c):
            self.tab2.columnconfigure(i, weight=1)

        self.makeTable()
        self.makePlot()
        self.makeWidgets()

    def makeWidgets(self):
        self.Labels = []
        self.Entries = []
        self.labText = [('X Location:'), ('Y Location:')]

        self.xGrid = 1
        self.yGrid = int(np.ceil(len(self.labText) / self.xGrid))
        i = 0
        for j in range(0, self.xGrid + 1, 2):
            for k in range(self.yGrid):
                if(i == len(self.labText)):
                    break
                else:
                    self.label = tk.Label(self.tab2, text=self.labText[i])
                    self.label.grid(column=j, row=k + 8, sticky='SW')
                    self.Labels.append(self.label)
                    self.entry = tk.Entry(self.tab2)
                    self.entry.insert(0, '0.0000')
                    self.entry.grid(column=j + 1, row=k + 8, sticky='NS')
                    self.Entries.append(self.entry)
                    i += 1

        self.addBtn = tk.Button(self.tab2, text='Add Entry', command=self.addEntry)
        self.addBtn.grid(column=self.xGrid + 1, row=self.yGrid + 9, sticky='NSEW')

    def makeTable(self):
        tab_header = ['Pattern #', 'Description']
        self.tree = ttk.Treeview(self.tab2, columns=tab_header, height=5, show="headings")
        vsb = ttk.Scrollbar(self.tab2, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=vsb.set)
        self.tree.grid(column=0, row=0, columnspan=2, rowspan=5, sticky='NSEW')
        vsb.grid(column=1, row=0, rowspan=5, sticky='ENS')
        self.tree.heading(tab_header[0], text=tab_header[0].title())
        self.tree.column(tab_header[0], width=30, anchor='center')
        self.tree.heading(tab_header[1], text=tab_header[1].title())
        self.tree.column(tab_header[1], width=170, anchor='center')
        self.tree.insert('', 'end', values=("", "(new)"))
        self.counter = 1

    def addEntry(self):
        check = (len(self.tree.get_children()) == 1)
        self.description = "Location: " + self.Entries[0].get() + ", " + self.Entries[1].get()
        self.tree.insert('', 'end', values=(self.counter, self.description))
        newEntry = [float(self.Entries[0].get()), float(self.Entries[1].get())]
        if(check == True):
            self.points = np.array(newEntry, ndmin=2)
        else:
            self.points = np.vstack((self.points, newEntry))
        self.counter += 1
        self.plotstuff()

    def makePlot(self):
        self.fig = Figure(figsize=(1, 1), dpi=100)
        self.ptrnFig = self.fig.add_subplot(111)
        self.ptrnFig.axis([-1, 1, -1, 1])
        self.ptrnFig.spines['left'].set_position('zero')
        self.ptrnFig.spines['bottom'].set_position('zero')
        self.ptrnFig.grid(True)
        self.canvas = FigureCanvasTkAgg(self.fig, self.tab2)
        self.canvas.draw()
        self.canvas.get_tk_widget().grid(column=2, row=0, columnspan=2, rowspan=5, sticky='NSEW')
        self.scat = self.ptrnFig.scatter([], [], c="b", marker="o")

    def plotstuff(self):
        self.scat.set_offsets(self.points)
        # adjust the limits of the axes
        xmin = min(self.points[:, 0])
        xmax = max(self.points[:, 0])
        ymin = min(self.points[:, 1])
        ymax = max(self.points[:, 1])
        self.ptrnFig.set_xlim(xmin - 0.1 * (xmax - xmin), xmax + 0.1 * (xmax - xmin))
        self.ptrnFig.set_ylim(ymin - 0.1 * (ymax - ymin), ymax + 0.1 * (ymax - ymin))
        self.canvas.draw_idle()


def main():
    MainGUI().mainloop()


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

4 Comments

Thanks @Diziet, this works as I expected for the plotting. One thing that I left out of the original question is the capability to delete or update points in the table. As you can imagine the example I posted is quite a bit reduced from my real application. In the actual application I have a button which allows you to delete or update an entry in the table. How would I go about removing or updating the list of points I'm plotting? I keep each point (or set of points in reality since the actual application allows point sets) in a dictionary if that is at all helpful.
Also, can you please explain a bit about what your solution is actually doing. As I understand it you are first plotting a scatter plot of empty arrays. Then each time you add data you append/update the arrays which you are plotting, but I don't see exacltly how the self.canvas.draw_idle() call works (it seems to update the plot each time data is added?). For some reason when I run this in my real use-case it works fine for the first input, but when I add the next, it only plots the last set of entered points. Ideas why that might be?
in your addEntry(), you are appending a new [x,y] coordinates to a Nx2 array (self.points), The function plotstuff() simply updates the coordinates stored in the PathCollection object created by the initial call to scatter(). This is the same strategy used when people are creating animated scatter plot. You can do a search for scatter FuncAnimation here and you'll get plenty of example.
I cannot guess why the code works with the first input and not subsequent ones. I would make sure the shape of self.points remains Nx2 and that it contains the expected coordinates

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.