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.
Related
I dont have a working code - but a snipet of my code can be as follows. I'm trying to use geopandas with mathplotlib, and trying to plot a map with links and points.
shape_file = os.path.join(os.getcwd(), "Healthboard")
healthboard = gp.read_file(os.path.join(shape_file, "healthboard.shp"))
healthboard = healthboard.to_crs({'init': 'epsg:4326'}) # re-projection
geo_df1 = geo_df1[geo_df1['HealthBoardArea2019Code'] == string1]
geo = geo_df[geo_df['Healthboard '] == string2]
new_shape_file = os.path.join(os.getcwd(), "Council_Shapefile")
council_to_healtboard = pd.read_csv("council_to_healthboard.csv")
council_to_healthboard = council_to_healtboard.rename(columns = {'CA': 'Council_area_code'})
council = gp.read_file(os.path.join(new_shape_file, "Council_shapefile.shp"))
council = council.to_crs({'init': 'epsg:4326'})
council = council.rename(columns = {'la_s_code':'Council_area_code'})
df = council.merge(council_to_healthboard, on = 'Council_area_code', how ='inner')
# Plotting stuff
fig, ax = plt.subplots(figsize=(15,15))
geo_df1.plot(ax = ax, markersize=35, color = "blue", marker = "*", label = "Postcode Sector")
geo.geometry.plot(ax = ax, color = "red", markersize=20, alpha = 0.8, label = 'SiteName')
#healthboard[healthboard["HBName"]=="Lothian"].plot(ax = ax, alpha = 0.6)
#healthboard[healthboard["HBName"]=="Lothian"].boundary.plot(ax = ax, color = "black", alpha = 0.6)
df[df["HB"]=="S08000024"].boundary.plot(ax =ax, color = "black", alpha = 0.1)
df[df["HB"]=="S08000024"].plot(ax =ax, cmap = "viridis", alpha = 0.1)
links_gp.plot(ax =ax, alpha = 0.25, color='brown', linestyle = "-")
My links_gp.plot has 40 time periods, as a result I want to make one plot, and have a button to adjust the parameters of time. Or if not possible a series of 40 plots. I've tried numerous ways but keep failing on this. I would really appreciate if someone could guide me on this.
I'm aware that you are using matplotlib, but if you don't mind using bokeh instead, you could use the following. To create an interactive plot with a possibility to adjust a parameter, bokeh provides a slider widget which can be used to change the plot based on a custom filter function.
An example from a geopandas dataframe with LineString geometries similar to the one you posted:
import geopandas as gpd
from bokeh.io import show, output_notebook
from bokeh.models import (CDSView, ColumnDataSource, CustomJS,
CustomJSFilter, Slider, Column)
from bokeh.layouts import column
from bokeh.plotting import figure
# prepare data source
links_gp['x'] = links_gp.apply(lambda row: list(row['geometry'].coords.xy[0]), axis=1)
links_gp['y'] = links_gp.apply(lambda row: list(row['geometry'].coords.xy[1]), axis=1)
# drop geometry column, because it can't be serialized to ColumnDataSource
links_gp.drop('geometry', axis=1, inplace=True)
linesource = ColumnDataSource(links_gp)
p = figure(title = 'Bokeh Time Slider',
plot_height = 500,
plot_width = 600,
toolbar_location = 'below',
tools = "pan, wheel_zoom, box_zoom, reset")
slider = Slider(title='Time Period', start=1, end=40, step=1, value=1)
# Callback triggers the filter when the slider moves
callback = CustomJS(args=dict(source=linesource),
code="""source.change.emit();""")
slider.js_on_change('value', callback)
# Custom filter that selects all lines of the time period based on the slider value
custom_filter = CustomJSFilter(args=dict(slider=slider),
code="""
var indices = [];
// iterate through rows of data source and check if time period value equals the slider value
for (var i = 0; i < source.get_length(); i++){
if (source.data['Time Period'][i] == slider.value){
indices.push(true);
} else {
indices.push(false);
}
}
return indices;
""")
# Use filter to determine which lines are visible
view = CDSView(source=linesource, filters=[custom_filter])
# plot lines to map
p.multi_line('x', 'y', source=linesource, color='red', line_width=3, view=view)
layout = column(p, slider)
show(layout)
This will be the result of the above code.
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()
I am trying to use a log scale as the margin plots for my seaborn jointplot. I am usings set_xticks() and set_yticks(), but my changes do not appear. Here is my code below and the resulting graph:
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import seaborn as sns
import pandas as pd
tips = sns.load_dataset('tips')
female_waiters = tips[tips['sex']=='Female']
def graph_joint_histograms(df1):
g=sns.jointplot(x = 'total_bill',y = 'tip', data = tips, space = 0.3,ratio = 3)
g.ax_joint.cla()
g.ax_marg_x.cla()
g.ax_marg_y.cla()
for xlabel_i in g.ax_marg_x.get_xticklabels():
xlabel_i.set_visible(False)
for ylabel_i in g.ax_marg_y.get_yticklabels():
ylabel_i.set_visible(False)
x_labels = g.ax_joint.get_xticklabels()
x_labels[0].set_visible(False)
x_labels[-1].set_visible(False)
y_labels = g.ax_joint.get_yticklabels()
y_labels[0].set_visible(False)
y_labels[-1].set_visible(False)
g.ax_joint.set_xlim(0,200)
g.ax_marg_x.set_xlim(0,200)
g.ax_joint.scatter(x = df1['total_bill'],y = df1['tip'],data = df1,c = 'y',edgecolors= '#080808',zorder = 2)
g.ax_joint.scatter(x = tips['total_bill'],y = tips['tip'],data = tips, c= 'c',edgecolors= '#080808')
ax1 =g.ax_marg_x.get_axes()
ax2 = g.ax_marg_y.get_axes()
ax1.set_yscale('log')
ax2.set_xscale('log')
ax1.set_yscale('log')
ax2.set_xscale('log')
ax2.set_xlim(1e0, 1e4)
ax1.set_ylim(1e0, 1e3)
ax2.xaxis.set_ticks([1e0,1e1,1e2,1e3])
ax2.xaxis.set_ticklabels(("1","10","100","1000"), visible = True)
plt.setp(ax2.get_xticklabels(), visible = True)
colors = ['y','c']
ax1.hist([df1['total_bill'],tips['total_bill']],bins = 10, stacked=True,log = True,color = colors, ec='black')
ax2.hist([df1['tip'],tips['tip']],bins = 10,orientation = 'horizontal', stacked=True,log = True,color = colors, ec='black')
ax2.set_ylabel('')
Any ideas would be much appreciated.
Here is the resulting graph:
You should actually get an error from the line g.ax_marg_y.get_axes() since an axes does not have a get_axes() method.
Correcting for that
ax1 =g.ax_marg_x
ax2 = g.ax_marg_y
should give you the desired plot. The ticklabels for the log axis are unfortunately overwritten by the histogram's log=True argument. So you can either leave that out (since you already set the axes to log scale anyways) or you need to set the labels after calling hist.
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset('tips')
def graph_joint_histograms(tips):
g=sns.jointplot(x = 'total_bill',y = 'tip', data = tips, space = 0.3,ratio = 3)
g.ax_joint.cla()
g.ax_marg_x.cla()
g.ax_marg_y.cla()
for xlabel_i in g.ax_marg_x.get_xticklabels():
xlabel_i.set_visible(False)
for ylabel_i in g.ax_marg_y.get_yticklabels():
ylabel_i.set_visible(False)
x_labels = g.ax_joint.get_xticklabels()
x_labels[0].set_visible(False)
x_labels[-1].set_visible(False)
y_labels = g.ax_joint.get_yticklabels()
y_labels[0].set_visible(False)
y_labels[-1].set_visible(False)
g.ax_joint.set_xlim(0,200)
g.ax_marg_x.set_xlim(0,200)
g.ax_joint.scatter(x = tips['total_bill'],y = tips['tip'],data = tips,
c = 'y',edgecolors= '#080808',zorder = 2)
g.ax_joint.scatter(x = tips['total_bill'],y = tips['tip'],data = tips,
c= 'c',edgecolors= '#080808')
ax1 =g.ax_marg_x
ax2 = g.ax_marg_y
ax1.set_yscale('log')
ax2.set_xscale('log')
ax2.set_xlim(1e0, 1e4)
ax1.set_ylim(1e0, 1e3)
ax2.xaxis.set_ticks([1e0,1e1,1e2,1e3])
ax2.xaxis.set_ticklabels(("1","10","100","1000"), visible = True)
plt.setp(ax2.get_xticklabels(), visible = True)
colors = ['y','c']
ax1.hist([tips['total_bill'],tips['total_bill']],bins = 10,
stacked=True, color = colors, ec='black')
ax2.hist([tips['tip'],tips['tip']],bins = 10,orientation = 'horizontal',
stacked=True, color = colors, ec='black')
ax2.set_ylabel('')
graph_joint_histograms(tips)
plt.show()
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:
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.