Interference between the Matplotlib Graphs in Tkinter GUI - matplotlib

I learned how to embed a matploblib graph into a Tkinter GUI via this post https://matplotlib.org/examples/user_interfaces/embedding_in_tk.html. It seems working fine.
However, I had a problem with getting multiple matplotlib graphs to work correctly simultaneously, let me explain a bit more here. The code below generates two buttons, each links to a new window with two new buttons (load data and plot data). You may generate some dummy two column data, say data1.txt, data2.txt, data3.txt, data4.txt, for plotting. What I observed is (1) If I invoke the window separately, I have no problem loading and plotting data1.txt, data2.txt, data3.txt, data4.txt. However, if I open the two windows at the same time, I can only plot freely and correctly in the second window, while the first window plots nothing. It seems as if the first window were suppressed by the existence of the second.
Can anyone help me understand what is happening here. My matplotlib version is 2.0.2. Tkinter version is $Revision:81008$. Python version 2.7.15. Thank you!
from Tkinter import *
import Tkinter as tk
import ttk
import tkFileDialog
import numpy
##loading matplotlib modules
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
class Overall_Look:
def __init__(self, master):
self.master = master
self.top_frame = ttk.Frame(self.master, padding = (10, 10))
self.top_frame.pack()
##here are the layout for step 1, load structure files
ttk.Button(self.top_frame, text = "Button_1", command = self.plot_one,
style = "TButton").grid(row = 1, column = 0, columnspan = 2, padx = 5, sticky = "sw")
ttk.Button(self.top_frame, text = "Button_2",command = self.plot_two,
style = "TButton").grid(row = 1, column = 2, columnspan = 2, padx = 5, sticky = "sw")
def plot_one(self):
self.plot_one = tk.Toplevel(self.master)
self.GUI = Plot_One(self.plot_one)
def plot_two(self):
self.plot_two = tk.Toplevel(self.master)
self.GUI = Plot_Two(self.plot_two)
class Plot_One():
def __init__(self, master):
self.master = master
self.top_frame = ttk.Frame(self.master, padding = (10, 10))
self.top_frame.pack()
##here are the layout for step 1, load structure files
ttk.Button(self.top_frame, text = "Load Data 1", command = self.load_data_1,
style = "TButton").grid(row = 1, column = 0, columnspan = 2, padx = 5, sticky = "sw")
ttk.Button(self.top_frame, text = "Plot Data 1",command = self.start_plot_one,
style = "TButton").grid(row = 1, column = 2, columnspan = 2, padx = 5)
self.bottom_frame = ttk.Frame(self.master, padding = (10, 10))
self.bottom_frame.pack()
self.fig_1 = plt.figure(figsize=(5, 5), dpi=100) ##create a figure; modify the size here
self.fig_1.add_subplot(111)
self.fig_1.tight_layout()
self.canvas = FigureCanvasTkAgg(self.fig_1, master = self.bottom_frame)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.bottom_frame)
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def load_data_1(self):
self.data_1 = tkFileDialog.askopenfilename(defaultextension = ".txt",
filetypes = [("Text Documents", "*.txt")])
def start_plot_one(self):
data = numpy.loadtxt(self.data_1).transpose()
x = data[0]
y = data[1]
self.fig_1.clf()
self.fig_1.add_subplot(111)
plt.plot(x, y, 'b-', lw=2)
self.fig_1.tight_layout()
self.canvas.draw()
class Plot_Two():
def __init__(self, master):
self.master = master
self.top_frame = ttk.Frame(self.master, padding = (10, 10))
self.top_frame.pack()
##here are the layout for step 1, load structure files
ttk.Button(self.top_frame, text = "Load Data 2", command = self.load_data_2,
style = "TButton").grid(row = 1, column = 0, columnspan = 2, padx = 5, sticky = "sw")
ttk.Button(self.top_frame, text = "Plot Data 2",command = self.start_plot_two,
style = "TButton").grid(row = 1, column = 2, columnspan = 2, padx = 5)
self.bottom_frame = ttk.Frame(self.master, padding = (10, 10))
self.bottom_frame.pack()
self.fig_2 = plt.figure(figsize=(5, 5), dpi=100) ##create a figure; modify the size here
self.fig_2.add_subplot(111)
self.fig_2.tight_layout()
self.canvas = FigureCanvasTkAgg(self.fig_2, master = self.bottom_frame)
self.canvas.show()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.bottom_frame)
self.toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def load_data_2(self):
self.data_2 = tkFileDialog.askopenfilename(defaultextension = ".txt",
filetypes = [("Text Documents", "*.txt")])
def start_plot_two(self):
data = numpy.loadtxt(self.data_2).transpose()
x = data[0]
y = data[1]
self.fig_2.clf()
self.fig_2.add_subplot(111)
plt.plot(x, y, 'b-', lw=2)
self.fig_2.tight_layout()
self.canvas.draw()
def main():
root = Tk()
GUI = Overall_Look(root)
root.mainloop()
if __name__ == "__main__": main()

Related

Insert PNG image in matplotlib figure with better quality

I am trying to insert PNG images in a matplotlib figure. Based on answers here and here, this is so far my code:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gs
from matplotlib.offsetbox import OffsetImage,AnnotationBbox
import seaborn as sns
sns.set_style("white")
image1 = "d1.PNG"
image2 = "d2.PNG"
image3 = "d3.PNG"
img1 = plt.imread(image1)
img2 = plt.imread(image2)
img3 = plt.imread(image3)
fig = plt.figure(tight_layout = True)
gs1 = gs.GridSpec(nrows = 2, ncols = 3)
ax1 = plt.subplot(gs1[:, 1])
ax1.text(x= 0.5, y = 0.5, s = "ax1", va = "center", ha = "center")
ax2 = plt.subplot(gs1[0, 2])
ax2.text(x= 0.5, y = 0.5, s = "ax2", va = "center", ha = "center")
ax3 = plt.subplot(gs1[1, 2])
ax3.text(x= 0.5, y = 0.5, s = "ax3", va = "center", ha = "center")
def add_image(img, coord):
im = OffsetImage(img, zoom = 0.07)
im.image.axes = ax1
ab = AnnotationBbox(im, (0.0, coord), xybox=(-100, 0.0), frameon=False, xycoords='data', boxcoords="offset points", pad=0.4)
ax1.add_artist(ab)
add_image(img1, 0.8)
add_image(img2, 0.45)
add_image(img3, 0.1)
sns.despine(ax = ax1, top = False, right = True, bottom = True)
sns.despine(ax = ax2, top = True, right = True)
sns.despine(ax = ax3, top = True, right = True)
plt.savefig("plot.pdf")
This produces:
The PNG images on the left appear pixelled. Is there a way I can insert these images with better quality?
I also have the images in PDF format. Would it be better if I try to transform the PDFs to PNGs and insert them later? According to what I have read, it is not possible to insert PDFs directly in matplotlib.
Thanks!
EDIT: This is what one of the original images look like:
I produced them with PowerPoint (might not be the best tool, I admit) and what I did is to make the slide size large (40cm x 20 cm) trying to improve the quality of the PNGs.

Matplotlib - plot wont show up

I want to plot a function graph (using matplotlib) when a button is pressed, to do so I wrote the following code:
##--IMPORT
#Tkinter
from tkinter import Tk, ttk
from tkinter import Frame, LabelFrame, Button
from tkinter import FALSE
#Numpy
from numpy import linspace
#Sympy
from sympy import symbols,sympify,diff,N,log
#MathPlotLib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
_x = symbols("x")
_sympyFunction = None
_SP_mainSubPlot = None
def pr_draw(plotToDrawTo):
_sympyFunction = sympify("log(x) + x")
valuesRange = linspace(0.01, 3, 100)
x = []
y = []
#Calculate y and x values
for i in range(0, len(valuesRange)):
tempValue = N(_sympyFunction.subs(_x,valuesRange[i]))
x.append(float(valuesRange[i]))
y.append(float(tempValue))
#Draw function graph
plotToDrawTo.plot(x,y)
##--MAIN
if __name__== "__main__":
_root = Tk()
_root.title("Grafico Approsimativo")
_root.resizable(width = FALSE, height = FALSE)
_mainFrame = Frame(_root, bg = "black")
_mainFrame.pack(fill = "both", expand = True)
#Frames
#Main Left
_F_LeftMainFrame = Frame(_mainFrame)
_F_LeftMainFrame.grid(row = 0, column = 0, sticky = "nw")
_F_RightMainFrame = Frame(_mainFrame, bg = "violet")
_F_RightMainFrame.grid(row = 0, column = 2, sticky = "ne")
#Left Content--------------------------
_B_calculate = Button(_F_LeftMainFrame, text = "Draw", command = lambda: pr_draw(_SP_mainSubPlot))
_B_calculate.grid(row = 0, column = 0, padx = 5, pady = 5, sticky = "w")
#Right Content--------------------------
_F_mainPlotWindow = Figure(figsize = None, dpi = 100)
_SP_mainSubPlot = _F_mainPlotWindow.add_subplot(111)
_SP_mainSubPlot.grid(True)
#HERE
#Set master frame for Figure Obj
canvas = FigureCanvasTkAgg(_F_mainPlotWindow, master = _F_RightMainFrame)
canvas.get_tk_widget().pack()
The problem here is that when the button is pressed, nothing shows up in the plot window, the only way I could get this to work is by calling pr_draw(_SP_mainSubPlot) where I inserted the #HERE line: If the function is called there it will work, but not from the button., why?
You would need to redraw the canvas after you have plotted to it.
Adding the line
plotToDrawTo.figure.canvas.draw_idle()
at the end of your pr_draw function should do that.
Note that I also had to add _root.mainloop() at the end of the script to actually show the window.

Colormap is not categorizing the data properly

Here is my script to plot data from a Geogtiff file using basemap. The data is categorical and there are 13 categories within this domain. The problem is that some categories get bunched up into one colour and thus some resolution is lost.
Unfortunately, I do not know how to fix this. I read that plt.cm.get_cmp is better for discrete datasets but I have not gotten it to work unfortunately.
gtif = 'some_dir'
ds = gdal.Open(gtif)
data = ds.ReadAsArray()
gt = ds.GetGeoTransform()
proj = ds.GetProjection()
xres = gt[1]
yres = gt[5]
xmin = gt[0] + xres
xmax = gt[0] + (xres * ds.RasterXSize) - xres
ymin = gt[3] + (yres * ds.RasterYSize) + yres
ymax = gt[3] - yres
xy_source = np.mgrid[xmin:xmax+xres:xres, ymax+yres:ymin:yres]
ds = None
fig2 = plt.figure(figsize=[12, 11])
ax2 = fig2.add_subplot(111)
ax2.set_title("Land use plot")
bm2 = Basemap(ax=ax2,projection='cyl',llcrnrlat=ymin,urcrnrlat=ymax,llcrnrlon=xmin,urcrnrlon=xmax,resolution='l')
bm2.drawcoastlines(linewidth=0.2)
bm2.drawcountries(linewidth=0.2)
data_new=np.copy(data)
data_new[data_new==255] = 0
nbins = np.unique(data_new).size
cb =plt.cm.get_cmap('jet', nbins+1)
img2 =bm2.imshow(np.flipud(data_new), cmap=cb)
ax2.set_xlim(3, 6)
ax2.set_ylim(50,53)
plt.show()
labels = [str(i) for i in np.unique(data_new)]
cb2=bm2.colorbar(img2, "right", size="5%", pad='3%', label='NOAH Land Use Category')
cb2.set_ticklabels(labels)
cb2.set_ticks(np.unique(data_new))
Here are the categories that are found within the domain (numbered classes):
np.unique(data_new)
array([ 0, 1, 4, 5, 7, 10, 11, 12, 13, 14, 15, 16, 17], dtype=uint8)
Thanks so much for any help here. I have also attached the output image that shows the mismatch. (not working)
First, this colormap problem is independent of the use of basemap. The following is therefore applicable to any matplotlib plot.
The problem here is that creating a colormap from n values distributes those values equally over the colormap range. Some values from the image therefore fall into the same colorrange within the colormap.
To prevent this, one can generate a colormap with the initial number of categories as shown below.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
# generate colormap and norm
unique = np.unique(data)
vals = np.arange(int(unique.max()+1))/float(unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(data, cmap=cmap, norm=norm)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.set_ticks(unique)
plt.show()
This can be extended to exclude the colors not present in the image as follows:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
unique, newdata = np.unique(data, return_inverse=1)
newdata = newdata.reshape(data.shape)
# generate colormap and norm
new_unique = np.unique(newdata)
vals = np.arange(int(new_unique.max()+1))/float(new_unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(new_unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=new_unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(newdata, cmap=cmap, norm=norm)
for i in range(newdata.shape[0]):
for j in range(newdata.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.ax.set_yticklabels(unique)
plt.show()

In a matplotlib plot consisting of histogram subplots, how can the height and bar edges of one histogram be changed?

I've got a little function that generates a plot of two subplots. One subplot is two histograms overlaid and the other subplot is the results of dividing one histogram by the other.
For the second subplot, I don't know how to remove the edges between histogram bars (like the one above it) and I don't know how to reduce its height (such that it is, say, half the height of the one above it). I'm also not sure how to set the title to the very top of the plot.
How could these things be done?
My code is as follows:
import numpy
import matplotlib.pyplot
import datavision # sudo pip install datavision
import shijian # sudo pip install shijian
def main():
a = numpy.random.normal(2, 2, size = 120)
b = numpy.random.normal(2, 2, size = 120)
save_histogram_comparison_matplotlib(
values_1 = a,
values_2 = b,
label_1 = "a",
label_2 = "b",
normalize = True,
label_ratio_x = "frequency",
label_y = "",
title = "comparison of a and b",
filename = "test.png"
)
def save_histogram_comparison_matplotlib(
values_1 = None,
values_2 = None,
filename = None,
number_of_bins = None,
normalize = True,
label_x = "",
label_y = None,
label_ratio_x = "frequency",
label_ratio_y = "ratio",
title = None,
label_1 = "1",
label_2 = "2",
overwrite = True,
LaTeX = False
):
matplotlib.pyplot.ioff()
if LaTeX is True:
matplotlib.pyplot.rc("text", usetex = True)
matplotlib.pyplot.rc("font", family = "serif")
if number_of_bins is None:
number_of_bins_1 = datavision.propose_number_of_bins(values_1)
number_of_bins_2 = datavision.propose_number_of_bins(values_2)
number_of_bins = int((number_of_bins_1 + number_of_bins_2) / 2)
if filename is None:
filename = shijian.propose_filename(
filename = title.replace(" ", "_") + ".png",
overwrite = overwrite
)
values = []
values.append(values_1)
values.append(values_2)
bar_width = 0.8
figure, (axis_1, axis_2) = matplotlib.pyplot.subplots(nrows = 2)
ns, bins, patches = axis_1.hist(
values,
normed = normalize,
histtype = "stepfilled",
bins = number_of_bins,
alpha = 0.5,
label = [label_1, label_2],
rwidth = bar_width,
linewidth = 0
)
axis_1.legend()
axis_2.bar(
bins[:-1],
ns[0] / ns[1],
edgecolor = "#ffffff", # "none"
alpha = 1,
width = bins[1] - bins[0]
)
axis_1.set_xlabel(label_x)
axis_1.set_ylabel(label_y)
axis_2.set_xlabel(label_ratio_x)
axis_2.set_ylabel(label_ratio_y)
matplotlib.pyplot.title(title)
matplotlib.pyplot.savefig(filename)
matplotlib.pyplot.close()
if __name__ == "__main__":
main()
You have 3 questions:
1. How to remove the edges between histogram bars
Here, you can set the linewidth to 0 for the call to bar:
axis_2.bar(
bins[:-1],
ns[0] / ns[1],
linewidth=0,
alpha = 1,
width = bins[1] - bins[0]
)
2. How to reduce the height of the second subplot
Here, we can send kwargs to gridspec when we create the subplots. The relevant option is height_ratios. We send them using the gridspec_kw option to subplots. If we set it to (2,1), that makes the first subplot twice the height of the second one.
figure, (axis_1, axis_2) = matplotlib.pyplot.subplots(
nrows = 2,
gridspec_kw={'height_ratios':(2,1)}
)
3. How to set the title to the very top of the plot
When you call matplotlib.pyplot.title(title), that is actually setting the title of the currently active subplot axes, which in this case is axis_2. To set the title of the overall figure, you can set the suptitle:
matplotlib.pyplot.suptitle(title)
Or alternatively, since you already named your figure, you can use:
figure.suptitle(title)
And likewise, you could use:
figure.savefig(filename)
to save a few keystrokes.
Putting it all together:

Matplotlib SOMETIMES updating in tkinter

I have a program with a GUI including multiple sliders and a graph. The sliders set parameters of a function, which is supposed to be plotted on the graph. I began by following the directions in http://matplotlib.org/examples/user_interfaces/embedding_in_tk.html, changed from pack to grid, and have experimented ad nauseum. What I have now updates the plot if I move one slider, but not if I move the other slider. I don't see a difference between the two.
import matplotlib
matplotlib.use('TkAgg')
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
from Tkinter import *
domain_min = 1
domain_max = 10
order_min = 0
order_max = 3
fig = Figure(figsize=(10,5), dpi=50)
def module(x):
global domain, order, output_message, fig, a, x_vals, y_vals
domain = float(domain_slide.get())
order = int(order_slide.get())
output_message = 'f(1) = %i\nf(2) = %i\nf(3) = %i\nf(4) = %i\nf(5) = %i\nf(6) = %i\n\
f(7) = %i\nf(8) = %i\nf(9) = %i\nf(10)= %i'%(1**order,2**order,3**order,4**order,5**order,\
6**order,7**order,8**order,9**order,10**order)
output_message_text.set(output_message)
x_vals = np.linspace(0,domain,100)
y_vals = x_vals**order
a = fig.add_subplot(111)
a.clear()
a.plot(x_vals,y_vals,color='blue')
#GUI
root = Tk()
domain_slide = DoubleVar()
order_slide = DoubleVar()
output_message_text = StringVar()
ds = Scale(root, variable = domain_slide, from_ = domain_min, to = domain_max, command = module)
ds.grid(column = 0, row = 0)
o_s = Scale(root, variable = order_slide, from_ = order_min, to = order_max, command = module)
o_s.grid(column = 1, row = 0)
out_message = Message(root, textvariable = output_message_text, width = 300, bg = 'white').grid(column = 0, row = 1, rowspan = 3)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().grid(column=3,row=1)
canvas.show()
label = Label(root)
label.grid()
root.mainloop()
What difference does python see in the two sliders? In a more in-depth version of the program, none of the sliders update the plot.
You need to add a
fig.cavas.draw()
to your callback function.
I am not quite sure why it was ever redrawing the canvas with out this.