make plt graph fill area without padding - python

I am trying to integrate matplotlib into tkinter, this is doable with FigureCanvasTkAgg. However, in my case I want to remove the y axis ticks and then make the graph cover the entire available area (i.e. remove the padding). Removing the y axis was not difficult but I cannot find any information on removing the padding.
If I found a solution (for example: Remove padding from matplotlib plotting) it included using plt.savefig, but saving the image wouldn't help me. I guess I could save the image and display it that way although that would feel pretty hacky. Is there a better way?
My code so far:
import customtkinter
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib
import numpy as np
root = customtkinter.CTk()
root.geometry('600x500')
# figure
figure = plt.Figure()
ax = figure.add_subplot(111)
ax.yaxis.set_visible(False)
t = np.arange(0, 3, .01)
ax.plot(t, 2 * np.sin(2 * np.pi * t))
# add widget
canvas = FigureCanvasTkAgg(figure, master=root)
canvas.draw()
canvas.get_tk_widget().pack(fill='both', expand=True)
root.mainloop()

Related

Scatter plot in Tkinter using Matplotlib. No plot is showing

I have scoured the web for creating a live matplotlib scatter plot within a tkinter window but have found copious amounts of varying information making it confusing to decipher.As an example, I sometimes see some people use matplotlib.pyplot and I sometimes see some people use matplotlib.figure. I have no idea what the real differences between these two modules are.
I have created the example code below which I thought should simply create a matplotlib scatter plot inside the tkinter root window when I click the "Graph It" button. It does nothing when I click it though. The ultimate goal is to have a scatter plot within tkinter that updates whenever new data is read from a sensor but I'm starting simple. It should also be noted this is my first exposure to matplotlib so it may be something trivial I'm overlooking. Any help is appreciated.
#Python 3.7.9#
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,NavigationToolbar2Tk)
import numpy as np
figure = Figure(figsize = (5,5), dpi = 100)
ax = figure.add_subplot(111)
def plot():
x = np.random.rand(1,10)
y = np.random.rand(1,10)
ax.scatter(x,y)
root = tk.Tk()
canvas = FigureCanvasTkAgg(figure, root)
canvas.draw()
canvas.get_tk_widget().pack(pady = 10)
toolbar = NavigationToolbar2Tk(canvas,root)
toolbar.update()
canvas.get_tk_widget().pack()
button = tk.Button(root, text = "Graph It", command = plot)
button.pack()
root.mainloop()
The problem lies in that you do not "update" the plot.
With the minimal modification to your code, you just have to arrange to redraw the figure.
For example, you can use your wrapper canvas object (or the figure.canvas) to update the figure's canvas in each data update step as follows:
import tkinter as tk
import numpy as np
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
figure = Figure(figsize=(5, 5), dpi=100)
ax = figure.add_subplot(111)
def plot():
x = np.random.rand(1, 10)
y = np.random.rand(1, 10)
ax.scatter(x, y)
canvas.draw()
root = tk.Tk()
canvas = FigureCanvasTkAgg(figure, root)
canvas.draw()
canvas.get_tk_widget().pack(pady=10)
toolbar = NavigationToolbar2Tk(canvas, root)
toolbar.update()
canvas.get_tk_widget().pack()
button = tk.Button(root, text="Graph It", command=plot)
button.pack()
root.mainloop()
Output window:
However, in this case you add/create a new scatter plot in each update step. If you wish to extend the original data and update according to the updated data, you can find different methods in this post to update a single scatter plot.

matplotlib and tkinter: fixed figure size / scrolling or zooming on window resize

i want to show a figure using matplotlib in a tkinter window. This works fine using FigureCanvasTkAgg, but it ignores my proposed figsize
Is it omehow possible to show this plot in such a way that it preserves its figsize, no matter the window size? So when it is smaller than the window size (or its allocated space in a grid) than it should be centered in the middle, when its larger scroll bars should appear, but it should never be resized.
Additionally I would love to be able to "zoom in" to that Canvas, so again without changing the size of the plot itself, just zooming in and out, as if it would be a picture (thus resizing all text etc accordingly).
For both of these questions I have no idea where to start, since I have little experience with tkinter and would love some input or help.
Here i have a minimal working example which shows how it ignores the figsize:
from tkinter import *
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
import numpy as np
import matplotlib.pyplot as plt
root = Tk()
root.title('Plotviewer 0.2a')
root.geometry('800x500')
def graph():
# Data for plotting
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots(figsize=(20,10), dpi=80)
ax.plot(t, s)
ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='About as simple as it gets, folks')
ax.grid()
canvas = FigureCanvasTkAgg(fig, master=root, resize_callback=None) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().grid(row="0", column="1", sticky="NEWS")
button = Button(root, text="Graph", command=graph)
button.grid(row=0, column=0)
root.columnconfigure(0, weight="0")
root.columnconfigure(1, weight="1")
root.rowconfigure(0, weight="1")
graph()
root.mainloop()

tkinter NavigationToolbar2TkAgg change [z] format for imshow

I'm displaying an image using imshow inside a tkinter GUI and I have added a NavigationToolbar2TkAgg (python3.5). Very similarly to this question (check it out for visuals), I want to change the formatting of the coordinates and z values (in particular, all my z values are between 0-10000, so I want to write them out instead of using scientific notation).
For x and y this was pretty easy to do by changing the format_coord handle, but I can't seem to find anything to change the final bit [xxx]. I have tried format_cursor_data, but doesn't seem to be it either.
Anyone know a solution?
It seems that the [z] value is only shown for imshow, not for regular plots.
Here goes a minimal code sample to reproduce the problem
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,NavigationToolbar2TkAgg
top = tk.Tk()
fig = plt.figure()
plt.imshow(np.array([[0,1],[1,2]]))
ax = fig.gca()
ax.format_coord = lambda x,y: "x:%4u, y:%4u" % (x,y)
ax.format_cursor_data = lambda z: "Hello" % (z) # does nothing
canvas = FigureCanvasTkAgg(fig,master=top)
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
canvas.show()
toolbar = NavigationToolbar2TkAgg(canvas, top)
toolbar.update()
top.mainloop()
In the mouse_move() method of Matplotlib's NavigationToolbar2 class, the cursor data and its formatting are obtained from the topmost Artist in the Axes instance, rather than from the Axes instance itself, like for the coordinates. So what you should do is:
fig = Figure()
ax = fig.add_subplot(111)
img = np.array([[0,10000],[10000,20000]])
imgplot = ax.imshow(img, interpolation='none')
# Modify the coordinates format of the Axes instance
ax.format_coord = lambda x,y: "x:{0:>4}, y:{0:>4}".format(int(x), int(y))
# Modify the cursor data format of the Artist created by imshow()
imgplot.format_cursor_data = lambda z: "Hello: {}".format(z)

How to efficiently replace element in plot with ipywidgets?

How can I efficiently display similar plots with ipywidgets using Jupyter Notebook?
I wish to plot interactively a heavy plot (heavy in the sense that it has lots of data points and takes some time to plot it) and modify a single element of it using interact from ipywidgets without replotting all the complicated plot. Is there a builtin functionality to do this?
basically what I'm trying to do is
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact
import matplotlib.patches as patches
%matplotlib inline #ideally nbagg
def complicated plot(t):
plt.plot(HEAVY_DATA_SET)
ax = plt.gca()
p = patches.Rectangle(something_that_depends_on_t)
ax.add_patch(p)
interact(complicatedplot, t=(1, 100));
Right now it takes up to 2 seconds for each replot. I expect there are ways to keep the figure there and just replace that rectangle.
A hack would be to create a figure of the constant part, make it background to the plot and just plot the rectangle part. but the sounds too dirty
Thank you
This is an rough example of an interactive way to change a rectangle width (I'm assuming you are in an IPython or Jupyter notebook):
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import ipywidgets
from IPython.display import display
%matplotlib nbagg
f = plt.figure()
ax = plt.gca()
ax.add_patch(
patches.Rectangle(
(0.1, 0.1), # (x,y)
0.5, # width
0.5, # height
)
)
# There must be an easier way to reference the rectangle
rect = ax.get_children()[0]
# Create a slider widget
my_widget = ipywidgets.FloatSlider(value=0.5, min=0.1, max=1, step=0.1, description=('Slider'))
# This function will be called when the slider changes
# It takes the current value of the slider
def change_rectangle_width():
rect.set_width(my_widget.value)
plt.draw()
# Now define what is called when the slider changes
my_widget.on_trait_change(change_rectangle_width)
# Show the slider
display(my_widget)
Then if you move the slider, the width of the rectangle will change. I'll try to tidy up the code, but you may have the idea. To change the coordinates, you have to do rect.xy = (x0, y0), where x0 and y0 are new coordinates.

Programmatically drawing overlaid offset plots in matplotlib

I have 3 different plots that are currently each saved as separate figures. However, due to space constraints I would like to layer them behind each other and offset like so:
I am trying to convey that a similar pattern exists across each plot and this is a nice and compact way of doing so. I would like to programmatically draw such a figure using matplotlib, but I'm not sure how to layer and offset the graphs using the usual pyplot commands. Any suggestions would be helpful. The following code is a skeleton of what I have currently.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
window = 100
xs = np.arange(100)
ys = np.zeros(100)
ys[80:90] = 1
y2s = np.random.randn(100)/5.0+0.5
with sns.axes_style("ticks"):
for scenario in ["one", "two", "three"]:
fig = plt.figure()
plt.plot(xs, ys)
plt.plot(xs, y2s)
plt.title(scenario)
sns.despine(offset=10)
You can manually create the axes to plot into and position them as you like.
To highlight this approach modified your example as follows
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
window = 100
xs = np.arange(100)
ys = np.zeros(100)
ys[80:90] = 1
y2s = np.random.randn(100)/5.0+0.5
fig = plt.figure()
with sns.axes_style("ticks"):
for idx,scenario in enumerate(["one", "two", "three"]):
off = idx/10.+0.1
ax=fig.add_axes([off,off,0.65,0.65], axisbg='None')
ax.plot(xs, ys)
ax.plot(xs, y2s)
ax.set_title(scenario)
sns.despine(offset=10)
which gives a plot like
Here, I used fig.add_axes to add manually created axes objects to the predefined figure object. The arguments specify the position and size of the newly created axes, see docs.
Note that I also set the axes background to be transparent (axisbg='None').

Categories

Resources