pythonmatplotlibshapely

Clearing a subplot to update with a new one


I have some radiobuttons to select colour and width of a linestring in an embedded plot. I can't get the plot to clear when I select a new colour or width and the new plot is placed below the original plot. I have probably complicated things by using tkinter, matplotlib and shapely. I have been successful in destroying the entire window and rebuilidng it but it seemed pretty clunky. How can I remove the old plot or figure so I can update the colour and width?

I tried plt.clf() in the Selection method and it didn't remove it at all. I also tried putting plt.clf() at the end of the initial plot. This removed the plot but the new plot was still placed below the blank figure.

from tkinter import *
import tkinter as tk
import matplotlib.pyplot as plt
from shapely.geometry import LineString
from shapely.plotting import plot_line, plot_points
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk)


root =tk.Tk()
root.title("DATA")
topFrame=tk.Frame(root)
bottomFrame=tk.Frame(root)
topFrame.pack(side='top')
bottomFrame.pack(side='bottom')


#Method and call to create initial plot
def CreatePlot(lineColour,lineWidth):
        width = lineWidth
        colour = "#"+ lineColour
        peekFigure = Figure(figsize=(6, 4), dpi=100)
        fig = plt.figure (dpi = 90,clear=True)
        print("fig num: ", peekFigure)
        ax = fig.add_subplot (111)
        sample = LineString([(1.0,1.0), (2.0,2.0)])
        plot_line(sample, ax=ax, add_points=False,  alpha=0.7, color = colour,linewidth = lineWidth)
        axes = plt.gca()
        axes.set_aspect('equal')
        plt.axis('off')
        canvas = FigureCanvasTkAgg(fig, master = root) 
        canvas.draw()
        canvas.get_tk_widget().pack()
        
width = 5
colour = 'ff0000'
CreatePlot(colour,width)
#plt.clf()
        
#Method for change in radiobutton selection 
def Selection():
        plt.clf()
        CreatePlot(cVar.get(),wVar.get())
 

#Create buttons and labels
buttonExit =Button (bottomFrame, text = "Exit",command = root.destroy,width = 20)
buttonExit.pack()
labelColour = tk.Label(topFrame, text=" Colour")
labelColour.grid(row=0, column=1, sticky = 'w',padx=3, pady=2)
label3 = tk.Label(topFrame, text="Line Width")
label3.grid(row = 0, column = 2,padx=3, pady=2)


#Create radio buttons for line colour
colours = [{"colour":'red',"value":'ff0000'},{"colour":'blue',"value":"0000ff"},\
               {"colour": 'amber',"value":'ffa500'},{"colour":'magenta',"value":'ff00ff'}]
cVar = tk.StringVar()
rowNo=0
radC={}   
for c in colours:
        columnNo = 1
        rowNo=rowNo+1
        radC[f'{rowNo}']= tk.Radiobutton(topFrame,text=(c["colour"]),value=(c["value"]),\
            variable=cVar,command = Selection)
        radC[f'{rowNo}'].grid(row = rowNo, column = columnNo,sticky='w',padx=3, pady=2)
        if rowNo==1:
                radC[f'{rowNo}'].select()



# Create radio buttons for line widths
widths = ["5", "10", "20","30"]
wVar = tk.IntVar()
rowNo=0
WBoxList = []
radW={}
for width in widths:
    columnNo = 2
    rowNo=rowNo+1
    radW[f'{rowNo}']= tk.Radiobutton(topFrame,text=(width),value=(width),\
                        variable=wVar,command = Selection)
    radW[f'{rowNo}'].grid(row = rowNo, column = columnNo,sticky='w',padx=3, pady=2)
    if rowNo == 1:
        radW[f'{rowNo}'].select()

root.mainloop()

Solution

  • my attempt:

    from tkinter import *
    import tkinter as tk
    import matplotlib.pyplot as plt
    from shapely.geometry import LineString
    from shapely.plotting import plot_line, plot_points
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk)
    
    
    root =tk.Tk()
    root.title("DATA")
    topFrame=tk.Frame(root)
    bottomFrame=tk.Frame(root)
    topFrame.pack(side='top')
    bottomFrame.pack(side='bottom')
    
    
    #Method and call to create initial plot
    def CreatePlot(lineColour,lineWidth):
        
            width = lineWidth
            colour = "#"+ lineColour
            # peekFigure = Figure(figsize=(6, 4), dpi=100) not needed
            
            fig = plt.figure(num=1, dpi = 90,clear=True)
            
            print('fig : ', fig, fig.number)
            fig.clear()
            
            # print("fig num: ", peekFigure)    #not needed 
            ax = fig.add_subplot (111)
            
            sample = LineString([(1.0,1.0), (2.0,2.0)])
            plot_line(sample, ax=ax, add_points=False,  alpha=0.7, color = colour,linewidth = lineWidth)
            axes = plt.gca()
            axes.set_aspect('equal')
            plt.axis('off')
            
            
            # print('root : ', root ,)
            
            # for i in  dir(root):
            #     print(i)
     
            print(root.focus_get())   
            
            if root.focus_get():
            
                root.focus_get().destroy()
                    
            else: 
                
                pass
     
            canvas = FigureCanvasTkAgg(fig, master = root) 
        
            canvas.draw()
            
            canvas.get_tk_widget().pack()
                
       
            
    width = 5
    colour = 'ff0000'
    CreatePlot(colour,width)
    #plt.clf()
            
    #Method for change in radiobutton selection 
    def Selection():
            # plt.clf() # non funza 
            
            # plt.cla() # non funza 
            
            # plt.close() #non funza
            
            CreatePlot(cVar.get(),wVar.get())
     
    
    #Create buttons and labels
    buttonExit =Button (bottomFrame, text = "Exit",command = root.destroy,width = 20)
    buttonExit.pack()
    labelColour = tk.Label(topFrame, text=" Colour")
    labelColour.grid(row=0, column=1, sticky = 'w',padx=3, pady=2)
    label3 = tk.Label(topFrame, text="Line Width")
    label3.grid(row = 0, column = 2,padx=3, pady=2)
    
    
    #Create radio buttons for line colour
    colours = [{"colour":'red',"value":'ff0000'},{"colour":'blue',"value":"0000ff"},\
                   {"colour": 'amber',"value":'ffa500'},{"colour":'magenta',"value":'ff00ff'}]
    cVar = tk.StringVar()
    rowNo=0
    radC={}   
    for c in colours:
            columnNo = 1
            rowNo=rowNo+1
            radC[f'{rowNo}']= tk.Radiobutton(topFrame,text=(c["colour"]),value=(c["value"]),\
                variable=cVar,command = Selection)
            radC[f'{rowNo}'].grid(row = rowNo, column = columnNo,sticky='w',padx=3, pady=2)
            if rowNo==1:
                    radC[f'{rowNo}'].select()
    
    
    
    # Create radio buttons for line widths
    widths = ["5", "10", "20","30"]
    wVar = tk.IntVar()
    rowNo=0
    WBoxList = []
    radW={}
    for width in widths:
        columnNo = 2
        rowNo=rowNo+1
        radW[f'{rowNo}']= tk.Radiobutton(topFrame,text=(width),value=(width),\
                            variable=wVar,command = Selection)
        radW[f'{rowNo}'].grid(row = rowNo, column = columnNo,sticky='w',padx=3, pady=2)
        if rowNo == 1:
            radW[f'{rowNo}'].select()
    
    root.mainloop()
    

    I just added:

            print(root.focus_get())   
            
            if root.focus_get():
            
                root.focus_get().destroy()
                    
            else: 
                
                pass
    

    before:

     `canvas = FigureCanvasTkAgg(fig, master = root)`
    

    in def CreatePlot(lineColour,lineWidth):

    enter image description here

    But I suspect your code its not the correct way to implement what are you trying to achieve. Not an expert though.

    While using the

    Shapely’s plot_line returns a Matplotlib PathPatch object. That has set_edgecolor and set_linewidth methods, so you can update the colour and width without needing to clear/destroy anything. – RuthC Commented 12 hours ago

    approach suggested in comments I've:

    from tkinter import *
    import tkinter as tk
    import matplotlib.pyplot as plt
    from shapely.geometry import LineString
    from shapely.plotting import plot_line, plot_points
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk)
    
    
    root =tk.Tk()
    root.title("DATA")
    topFrame=tk.Frame(root)
    bottomFrame=tk.Frame(root)
    topFrame.pack(side='top')
    bottomFrame.pack(side='bottom')
    
    
    #Method and call to create initial plot
    # def CreatePlot(lineColour,lineWidth, line):
        
    def CreatePlot(lineColour,lineWidth):
            width = lineWidth
            colour = "#"+ lineColour
            # colour = lineColour
            
            # peekFigure = Figure(figsize=(6, 4), dpi=100)
            fig = plt.figure (dpi = 90,clear=True)
            # print("fig num: ", peekFigure)
            ax = fig.add_subplot (111)
            sample = LineString([(1.0,1.0), (2.0,2.0)])
            line = plot_line(sample, ax=ax, add_points=False,  alpha=0.7, color = colour,linewidth = lineWidth)
            axes = plt.gca()
            axes.set_aspect('equal')
            plt.axis('off')
            canvas = FigureCanvasTkAgg(fig, master = root) 
            canvas.draw()
            canvas.get_tk_widget().pack()
            
            return line, canvas
            
    width = 5
    colour = 'ff0000'
    # sample = LineString([(1.0,1.0), (2.0,2.0)])
    # line = plot_line(sample, ax=ax, add_points=False,  alpha=0.7, color = colour,linewidth = lineWidth)
    # CreatePlot(colour,width, line)
    
    line = CreatePlot(colour,width)
    
    #plt.clf()
            
    #Method for change in radiobutton selection 
    def Selection(line):
        
            print('line :', line)    
        
            # plt.clf()
            # CreatePlot(cVar.get(),wVar.get())
            
            print('cVar.get() : ', cVar.get())
            print('wVar.get() : ', wVar.get())
            
            
            line[0].set_edgecolor('#'+cVar.get())
            line[0].set_linewidth(wVar.get())
            
            line[1].draw()
     
    
    #Create buttons and labels
    buttonExit =Button (bottomFrame, text = "Exit",command = root.destroy,width = 20)
    buttonExit.pack()
    labelColour = tk.Label(topFrame, text=" Colour")
    labelColour.grid(row=0, column=1, sticky = 'w',padx=3, pady=2)
    label3 = tk.Label(topFrame, text="Line Width")
    label3.grid(row = 0, column = 2,padx=3, pady=2)
    
    
    #Create radio buttons for line colour
    colours = [{"colour":'red',"value":'ff0000'},{"colour":'blue',"value":"0000ff"},\
                   {"colour": 'amber',"value":'ffa500'},{"colour":'magenta',"value":'ff00ff'}]
    cVar = tk.StringVar()
    rowNo=0
    radC={}   
    for c in colours:
            columnNo = 1
            rowNo=rowNo+1
            # radC[f'{rowNo}']= tk.Radiobutton(topFrame,text=(c["colour"]),value=(c["value"]),\
            #     variable=cVar,command = Selection)
            
            radC[f'{rowNo}']= tk.Radiobutton(topFrame,text=(c["colour"]),value=(c["value"]),\
                variable=cVar,command=lambda: Selection(line))
            
            radC[f'{rowNo}'].grid(row = rowNo, column = columnNo,sticky='w',padx=3, pady=2)
            if rowNo==1:
                    radC[f'{rowNo}'].select()
    
    
    
    # Create radio buttons for line widths
    widths = ["5", "10", "20","30"]
    wVar = tk.IntVar()
    rowNo=0
    WBoxList = []
    radW={}
    for width in widths:
        columnNo = 2
        rowNo=rowNo+1
        # radW[f'{rowNo}']= tk.Radiobutton(topFrame,text=(width),value=(width),\
        #                     variable=wVar,command = Selection)
        
        radW[f'{rowNo}']= tk.Radiobutton(topFrame,text=(width),value=(width),\
                            variable=wVar,command=lambda: Selection(line))
        
        radW[f'{rowNo}'].grid(row = rowNo, column = columnNo,sticky='w',padx=3, pady=2)
        if rowNo == 1:
            radW[f'{rowNo}'].select()
    
    root.mainloop()
    

    enter image description here

    After having the answer as accepted I tried to better structure the code, ended up with:

    from tkinter import *
    import tkinter as tk
    import matplotlib.pyplot as plt
    from shapely.geometry import LineString
    from shapely.plotting import plot_line, plot_points
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk)
    
    
    
    colours = [{"colour":'red',"value":'ff0000'},{"colour":'blue',"value":"0000ff"},\
                    {"colour": 'amber',"value":'ffa500'},{"colour":'magenta',"value":'ff00ff'}]
    
    
    
    
    class TkinterApp(tk.Tk):
    
        def __init__(self):
            tk.Tk.__init__(self)
            self.title("DATA")
            self.topFrame=tk.Frame()
            self.bottomFrame=tk.Frame()
            self.topFrame.pack(side='top')
            self.bottomFrame.pack(side='bottom')
            
            #Create buttons and labels
            self.buttonExit =Button (self.bottomFrame, text = "Exit",command = self.destroy,width = 20)
            self.buttonExit.pack()
            self.labelColour = tk.Label(self.topFrame, text=" Colour")
            self.labelColour.grid(row=0, column=1, sticky = 'w',padx=3, pady=2)
            self.label3 = tk.Label(self.topFrame, text="Line Width")
            self.label3.grid(row = 0, column = 2,padx=3, pady=2)
            
            
            #about plot and selection
            self.Selection = Selection
            
            
            self.linewidth = 5
            self.linecolour = 'ff0000'        
            self.sample = LineString([(1.0,1.0), (2.0,2.0)])
            
            self.CreatePlot = CreatePlot
            line = self.CreatePlot(self.linecolour, self.linewidth,  self.sample, self)
           
    
            #Create radio buttons for line colour
            self.colours = colours
            
    
            self.cVar = tk.StringVar()
            
            # self.cVar.set('ff0000' )
            self.cVar.set(self.linecolour)
            
            self.rowNo=0
            self.radC={}   
            for c in self.colours:
                    self.columnNo = 1
                    self.rowNo=self.rowNo+1
                    
                    self.radC[f'{self.rowNo}']= tk.Radiobutton(self.topFrame,text=(c["colour"]),value=(c["value"]),\
                        variable=self.cVar,command=lambda: self.Selection(line, self.cVar,  self.wVar))
                    
                    self.radC[f'{self.rowNo}'].grid(row = self.rowNo, column = self.columnNo,sticky='w',padx=3, pady=2)
                    if self.rowNo==1:
                            self.radC[f'{self.rowNo}'].select()
    
    
    
            # Create radio buttons for line widths
            self.widths = ["5", "10", "20","30"]
            self.wVar = tk.IntVar()
            
            # self.wVar.set(5)
            self.wVar.set(self.linewidth)
            
            self.rowNo=0
            self.WBoxList = []
            self.radW={}
            for width in self.widths:
                self.columnNo = 2
                self.rowNo=self.rowNo+1
                
                self.radW[f'{self.rowNo}']= tk.Radiobutton(self.topFrame,text=(width),value=(width),\
                                    variable=self.wVar,command=lambda: self.Selection(line, self.cVar,  self.wVar))
                
                self.radW[f'{self.rowNo}'].grid(row = self.rowNo, column = self.columnNo,sticky='w',padx=3, pady=2)
                if self.rowNo == 1:
                    self.radW[f'{self.rowNo}'].select()
                    
           
    # #Method and call to create initial plot
    def CreatePlot(lineColour,lineWidth, sample, root):
            width = lineWidth
            colour = "#"+ lineColour
    
            fig = plt.figure (dpi = 90,clear=True)
    
            ax = fig.add_subplot (111)
    
            line = plot_line(sample, ax=ax, add_points=False,  alpha=0.7, color = colour,linewidth = width)
            axes = plt.gca()
            axes.set_aspect('equal')
            plt.axis('off')
            canvas = FigureCanvasTkAgg(fig, master = root) 
            canvas.draw()
            canvas.get_tk_widget().pack()
            
            return line, canvas
            
    
    def Selection(line, cVar , wVar):
        
            print('line :', line, '\n',type(line),'\n---------------')    
        
            
            print('cVar.get() : ', cVar.get())
            print('wVar.get() : ', wVar.get())
            
            
            line[0].set_edgecolor('#'+cVar.get())
            line[0].set_linewidth(wVar.get())
            
            line[1].draw()
     
    
    
    root = TkinterApp()
    root.mainloop()
    

    But still I think this kind of apps could be better structured , what I do not get at all is how to pass different shapely shapes (in this case is plot_line but I guess could be different) to my def CreatePlot because plot_line needs an axes (ax = fig.add_subplot(111) in this case) to be instantiated. Probably I should split def CreatePlot to have my main TkinterApp Class to provide the space/figure/frame to it maybe leaving def CreatePlot to retrieve the sample data and plot them as lines or points (ie shapely plot_line or plot_points). [see this answer in How do I plot Shapely polygons and objects using Matplotlib?