Embed 3D plot inside Tkinter Window in Python

In this tutorial, you’ll learn how to embed a 3D plot created with Matplotlib inside a Tkinter window.

You’ll use Python Matplotlib 3D plotting capabilities along with the Tkinter library to build a data visualization tool.

 

 

Embed Matplotlib Figure in Tkinter

To embed the Matplotlib figure in the Tkinter window, you can use FigureCanvasTkAgg:

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
root = tk.Tk()
root.title("3D Plot in Tkinter")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
root.mainloop()

Output:

Embed Matplotlib Figure in Tkinter

The canvas is then packed to fill the entire window.

 

Add Navigation Toolbar

To add a navigation toolbar, you can use NavigationToolbar2Tk:

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
root = tk.Tk()
root.title("3D Plot in Tkinter")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas_widget.pack(fill=tk.BOTH, expand=True)
root.mainloop()

Output:

Add Navigation Toolbar

This code adds a navigation toolbar below the canvas so you can use standard Matplotlib navigation tools like zoom, pan, and save.

 

Implement Plot Update

To update the plot dynamically, you can create a function that modifies the plot data and redraws the canvas:

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
root = tk.Tk()
root.title("3D Plot in Tkinter")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas_widget.pack(fill=tk.BOTH, expand=True)
def update_plot():
    global Z
    Z = np.sin(np.sqrt(X**2 + Y**2) + np.random.rand())
    ax.clear()
    ax.plot_surface(X, Y, Z, cmap='viridis')
    canvas.draw()
update_button = tk.Button(root, text="Update Plot", command=update_plot)
update_button.pack()
root.mainloop()

Output:

Implement Plot Update

This code adds an “Update Plot” button that, when clicked, updates the Z values with a random offset and redraws the plot.

 

Add Additional Tkinter Widgets

You can add sliders to control plot parameters dynamically:

import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
root = tk.Tk()
root.title("3D Plot in Tkinter")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas_widget.pack(fill=tk.BOTH, expand=True)
def update_plot(frequency):
    global Z
    Z = np.sin(frequency * np.sqrt(X**2 + Y**2))
    ax.clear()
    ax.plot_surface(X, Y, Z, cmap='viridis')
    canvas.draw()
frequency_slider = ttk.Scale(root, from_=0.1, to=2, orient=tk.HORIZONTAL, command=lambda x: update_plot(float(x)))
frequency_slider.set(1)
frequency_slider.pack()
root.mainloop()

Output:

Add Additional Tkinter Widgets

The slider controls the frequency of the sinusoidal function so you can modify the plot.

 

Zooming and Rotation

Matplotlib 3D plots already support zooming and rotation using the mouse.

However, you can add custom controls for finer adjustments:

import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
root = tk.Tk()
root.title("3D Plot in Tkinter")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas_widget.pack(fill=tk.BOTH, expand=True)
def update_view(elev, azim):
  ax.view_init(elev=elev, azim=azim)
  canvas.draw()
elev_slider = ttk.Scale(root, from_=0, to=90, orient=tk.HORIZONTAL)
elev_slider.set(30)
elev_slider.pack()
azim_slider = ttk.Scale(root, from_=0, to=360, orient=tk.HORIZONTAL)
azim_slider.set(45)
azim_slider.pack()
elev_slider.config(command=lambda x: update_view(float(x), azim_slider.get()))
azim_slider.config(command=lambda x: update_view(elev_slider.get(), float(x)))
root.mainloop()

Output:

Zooming and Rotation

This code adds two sliders to control the elevation and azimuth angles of the 3D plot.

 

Integrate with Data Sources for Real-time Plotting

You can integrate real-time data sources to update the plot continuously:

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
root = tk.Tk()
root.title("Real-time 3D Plot in Tkinter")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = np.zeros_like(X)
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas_widget.pack(fill=tk.BOTH, expand=True)
def update_plot():
    global Z
    Z = np.sin(np.sqrt(X**2 + Y**2) + np.random.rand())
    ax.clear()
    ax.plot_surface(X, Y, Z, cmap='viridis')
    canvas.draw()
    root.after(100, update_plot)  # Schedule the next update
update_plot()  # Start the update loop
root.mainloop()

This code simulates real-time data by updating the plot every 100 milliseconds with new random data.

 

Save and Load Plots

You can add functionality to save and load plot data:

import tkinter as tk
from tkinter import filedialog
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import pickle
root = tk.Tk()
root.title("3D Plot in Tkinter with Save/Load")
root.geometry("800x600")
fig = plt.figure(figsize=(6, 4), dpi=100)
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
surf = ax.plot_surface(X, Y, Z, cmap='viridis')
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.pack(fill=tk.BOTH, expand=True)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas_widget.pack(fill=tk.BOTH, expand=True)

def save_plot():
  file_path = filedialog.asksaveasfilename(defaultextension=".pkl", filetypes=[("Pickle files", "*.pkl")])
  if file_path:
      # Retrieve current elevation and azimuth angles
      elev = ax.elev
      azim = ax.azim
      # Save Z data and angles in a dictionary
      plot_data = {'Z': Z, 'elev': elev, 'azim': azim}
      with open(file_path, 'wb') as file:
          pickle.dump(plot_data, file)

def load_plot():
  file_path = filedialog.askopenfilename(filetypes=[("Pickle files", "*.pkl")])
  if file_path:
      with open(file_path, 'rb') as file:
          plot_data = pickle.load(file)
          global Z
          Z = plot_data['Z']
          elev = plot_data['elev']
          azim = plot_data['azim']
          ax.clear()
          ax.plot_surface(X, Y, Z, cmap='viridis')
          # Restore the elevation and azimuth angles
          ax.view_init(elev=elev, azim=azim)
          canvas.draw()
save_button = tk.Button(root, text="Save Plot", command=save_plot)
save_button.pack(side=tk.LEFT)
load_button = tk.Button(root, text="Load Plot", command=load_plot)
load_button.pack(side=tk.LEFT)
root.mainloop()

Output:

Save and Load Plots

This code adds “Save Plot” and “Load Plot” buttons to the Tkinter window.

The save_plot function now retrieves the current elevation and azimuth angles using ax.elev and ax.azim.

It stores these angles along with the Z data in a dictionary, which is then pickled and saved to a file.

The load_plot function loads the dictionary from the file, extracts the Z data, elevation, and azimuth angles, and updates the plot.

It uses ax.view_init(elev=elev, azim=azim) to restore the view angles.

Leave a Reply

Your email address will not be published. Required fields are marked *