jan
jan

Reputation: 143

Updating a Matplotlib plot with user imput

Once again I am in need of aid... I am very new to python and I am trying to plot the Mandelbrot set in a GUI. Currently I am working on a function where I can change the colors in which the fractal is rendered in. The problem is that I cant figure out how to replace the old plot with a new one. Everything up to the point where the plot needs to be re-plotted works (the terminal even pauses as if it is recalculating but does not yield anything). I have tried inserting fig.clf() in all the different places that have been suggested by the internet but I still cannot figure it out. Attached is a excerpt of the code will run. Specific locations of this code are located in the function called mandelbrot_image and the class MainPage. Thank you in advance.

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

import tkinter as tk
from tkinter import ttk
from tkinter import *
#import tkinter.messagebox
from tkinter import messagebox
import numpy as np
from numba import jit


from matplotlib import colors



#maths and display code derived/inspired from Jean Francois Puget 
#https://www.ibm.com/developerworks/community/blogs/jfp/entry/My_Christmas_Gift?lang=en


@jit
def mandelbrot(z,maxiter,horizon,log_horizon):
    c = z
    for n in range(maxiter):
        az = abs(z)
        if az > horizon:
            return n - np.log(np.log(az))/np.log(2) + log_horizon
        z = z*z + c
    return 0

@jit
def mandelbrot_set(xmin,xmax,ymin,ymax,width,height,maxiter):
    horizon = 2.0 ** 40
    log_horizon = np.log(np.log(horizon))/np.log(2)
    r1 = np.linspace(xmin, xmax, width)
    r2 = np.linspace(ymin, ymax, height)
    n3 = np.empty((width,height))
    for i in range(width):
        for j in range(height):
            n3[i,j] = mandelbrot(r1[i] + 1j*r2[j],maxiter,horizon, log_horizon)
    return (r1,r2,n3)

def mandelbrot_image(xmin=-2.,xmax=0.5,ymin=-1.25,ymax=1.25,width=10,height=10,\
             maxiter=1000,cmap='hot',gamma=0.3): #the coords and cmap are essentially a filler for the imput in the plot() function at the bottom of the code


    dpi = 80
    img_width = dpi * width
    img_height = dpi * height
    x,y,z = mandelbrot_set(xmin,xmax,ymin,ymax,img_width,img_height,maxiter)

    fig = Figure(figsize=(width, height))

    ax = fig.add_subplot(111)

    ticks = np.arange(0,img_width,3*dpi)
    x_ticks = xmin + (xmax-xmin)*ticks/img_width
    ax.set_xticks(ticks); ax.set_xticklabels(x_ticks)
    y_ticks = ymin + (ymax-ymin)*ticks/img_width
    ax.set_yticks(ticks); ax.set_yticklabels(y_ticks)
    ax.set_title("The Mandelbrot set")
    norm = colors.PowerNorm(gamma)
    #fig.clf()
    ax.imshow(z.T,cmap=cmap,origin='lower',norm=norm)

    return fig

LARGE_FONT= ("Verdana", 12)
NORM_FONT= ("Verdana", 10)

class base(tk.Tk):

    def __init__(self, *args, **kwargs):

        tk.Tk.__init__(self, *args, **kwargs)
        tk.Tk.wm_title(self, "Mandelbrot Renderer")

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand = True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)


        self.frames = {}

        for F in (StartPage, MainPage):

            frame = F(container, self)

            self.frames[F] = frame

            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(StartPage)

    def show_frame(self, cont):

        frame = self.frames[cont]
        frame.tkraise()


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self,parent)
        label = tk.Label(self, text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        button = ttk.Button(self, text="Lets Begin",
                        command=lambda: controller.show_frame(MainPage))
        button.pack()



class MainPage(tk.Frame):

    def var_states(self):  #this is supposed to send code to run plot() again but it doesnt do it
        print (self.combobox.get())
        print (self.colr)
        self.plot ()


    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = tk.Label(self, text="Graph Page!", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        values = ['jet', 'rainbow', 'ocean', 'hot', 'cubehelix','gnuplot','terrain','prism', 'pink']  
        button1 = ttk.Button(self, text="Back to Home",
                            command=lambda: controller.show_frame(StartPage))
        button1.pack()

        button2 = ttk.Button(self, text="Re-Render",
                            command=self.plot)
        button2.pack()
        self.mvar = IntVar()
        self.cbutton = ttk.Checkbutton(self, text="shadow",onvalue=0, offvalue=1, variable=self.mvar) 
        self.cbutton.pack()

        self.combobox = ttk.Combobox(self, values=values)
        self.combobox.current(0)
        self.combobox.pack(side = RIGHT)

        global colr 
        self.colr = self.combobox.get()
        self.plot ()

    def plot (self):
        colr = self.combobox.get()
        print (colr)
        #fig.clf() this does nothing and crashes
        fig = mandelbrot_image(-0.8,-0.7,0,0.1,cmap=colr) #this is calling the method with the coordinates of the plot and the color scheme
        #fig.clf()this works but just leaves the screen completely blank
        canvas = FigureCanvasTkAgg(fig, self)
        #fig.clf()this works but just leaves the screen completely blank
        canvas.show()
        #canvas.clf()
        #fig.clf() this works but just leaves the screen completely blank
        canvas.get_tk_widget().pack(side = BOTTOM, fill=tk.BOTH, expand=True)

        toolbar = NavigationToolbar2TkAgg(canvas, self)
        toolbar.update()
        canvas._tkcanvas.pack(side = BOTTOM, fill=tk.BOTH, expand=True)  

app = base()
app.geometry ("800x600")
app.mainloop()

Upvotes: 2

Views: 608

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339795

If you want to update the figure, you shouldn't create it in a function which is called several times. Instead you can create the figure in in the MainPage's init function and only update the content of its subplot. Therefore, the plot function could only clear the axes (not the figure!) and call the mandelbrot_image function to which the axes to plot to can be delivered as an argument. Finally the canvas has to be redrawn using canvas.draw() for the new plot to appear in the GUI.

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

import Tkinter as tk #replace with tkinter for python 3
import ttk
import numpy as np
from numba import jit
from matplotlib import colors
#maths and display code derived/inspired from Jean Francois Puget 
#https://www.ibm.com/developerworks/community/blogs/jfp/entry/My_Christmas_Gift?lang=en


@jit
def mandelbrot(z,maxiter,horizon,log_horizon):
    c = z
    for n in range(maxiter):
        az = abs(z)
        if az > horizon:
            return n - np.log(np.log(az))/np.log(2) + log_horizon
        z = z*z + c
    return 0

@jit
def mandelbrot_set(xmin,xmax,ymin,ymax,width,height,maxiter):
    horizon = 2.0 ** 40
    log_horizon = np.log(np.log(horizon))/np.log(2)
    r1 = np.linspace(xmin, xmax, width)
    r2 = np.linspace(ymin, ymax, height)
    n3 = np.empty((width,height))
    for i in range(width):
        for j in range(height):
            n3[i,j] = mandelbrot(r1[i] + 1j*r2[j],maxiter,horizon, log_horizon)
    return (r1,r2,n3)

def mandelbrot_image(ax, xmin=-2.,xmax=0.5,ymin=-1.25,ymax=1.25,width=10,height=10,\
             maxiter=1000,cmap='hot',gamma=0.3): #the coords and cmap are essentially a filler for the imput in the plot() function at the bottom of the code


    dpi = 80
    img_width = dpi * width
    img_height = dpi * height
    x,y,z = mandelbrot_set(xmin,xmax,ymin,ymax,img_width,img_height,maxiter)

    ticks = np.arange(0,img_width,3*dpi)
    x_ticks = xmin + (xmax-xmin)*ticks/img_width
    ax.set_xticks(ticks); ax.set_xticklabels(x_ticks)
    y_ticks = ymin + (ymax-ymin)*ticks/img_width
    ax.set_yticks(ticks); ax.set_yticklabels(y_ticks)
    ax.set_title("The Mandelbrot set")
    norm = colors.PowerNorm(gamma)
    ax.imshow(z.T,cmap=cmap,origin='lower',norm=norm)


LARGE_FONT= ("Verdana", 12)
NORM_FONT= ("Verdana", 10)

class base(tk.Tk):

    def __init__(self, *args, **kwargs):

        tk.Tk.__init__(self, *args, **kwargs)
        tk.Tk.wm_title(self, "Mandelbrot Renderer")

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand = True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)


        self.frames = {}

        for F in (StartPage, MainPage):

            frame = F(container, self)

            self.frames[F] = frame

            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(StartPage)

    def show_frame(self, cont):

        frame = self.frames[cont]
        frame.tkraise()


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self,parent)
        label = tk.Label(self, text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        button = tk.Button(self, text="Lets Begin",
                        command=lambda: controller.show_frame(MainPage))
        button.pack()



class MainPage(tk.Frame):

    def var_states(self):  #this is supposed to send code to run plot() again but it doesnt do it
        print (self.combobox.get())
        print (self.colr)
        self.plot ()


    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = tk.Label(self, text="Graph Page!", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        values = ['jet', 'rainbow', 'ocean', 'hot', 'cubehelix','gnuplot','terrain','prism', 'pink']  
        button1 = tk.Button(self, text="Back to Home",
                            command=lambda: controller.show_frame(StartPage))
        button1.pack()

        button2 = tk.Button(self, text="Re-Render",
                            command=self.plot)
        button2.pack()
        self.mvar = tk.IntVar()
        self.cbutton = tk.Checkbutton(self, text="shadow",onvalue=0, offvalue=1, variable=self.mvar) 
        self.cbutton.pack()

        self.combobox = ttk.Combobox(self, values=values)
        self.combobox.current(0)
        self.combobox.pack(side = tk.TOP)

        self.width, self.height = 10, 10
        fig = Figure(figsize=(self.width, self.height))
        self.ax = fig.add_subplot(111)

        self.canvas = FigureCanvasTkAgg(fig, self)
        self.canvas.show()
        toolbar = NavigationToolbar2TkAgg(self.canvas, self)
        toolbar.update()
        self.canvas.get_tk_widget().pack(side = tk.BOTTOM, fill=tk.BOTH, expand=True)

        self.plot ()

    def plot (self):
        colr = self.combobox.get()
        print (colr)
        self.ax.clear()
        mandelbrot_image(self.ax, -0.8,-0.7,0,0.1,cmap=colr)
        self.canvas.draw()


app = base()
app.geometry ("800x600")
app.mainloop()

Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg.

Upvotes: 3

Related Questions