The latest version of matplotlib, 3.1.1, causes the demo programs that show integration with Matplotlib to crash. Simplest and shortest demo matplotlib program is Demo_Matplotlib.py
All
3.x
PySimpleGUI 3, 4 and up
#!/usr/bin/env python
import sys
if sys.version_info[0] >= 3:
import PySimpleGUI as sg
else:
import PySimpleGUI27 as sg
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasAgg
import matplotlib.backends.tkagg as tkagg
import tkinter as Tk
"""
Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
Basic steps are:
* Create a Canvas Element
* Layout form
* Display form (NON BLOCKING)
* Draw plots onto convas
* Display form (BLOCKING)
"""
#------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import NullFormatter # useful for `logit` scale
# Fixing random state for reproducibility
np.random.seed(19680801)
# make up some data in the interval ]0, 1[
y = np.random.normal(loc=0.5, scale=0.4, size=1000)
y = y[(y > 0) & (y < 1)]
y.sort()
x = np.arange(len(y))
# plot with various axes scales
plt.figure(1)
# linear
plt.subplot(221)
plt.plot(x, y)
plt.yscale('linear')
plt.title('linear')
plt.grid(True)
# log
plt.subplot(222)
plt.plot(x, y)
plt.yscale('log')
plt.title('log')
plt.grid(True)
# symmetric log
plt.subplot(223)
plt.plot(x, y - y.mean())
plt.yscale('symlog', linthreshy=0.01)
plt.title('symlog')
plt.grid(True)
# logit
plt.subplot(224)
plt.plot(x, y)
plt.yscale('logit')
plt.title('logit')
plt.grid(True)
plt.gca().yaxis.set_minor_formatter(NullFormatter())
plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
wspace=0.35)
fig = plt.gcf() # if using Pyplot then get the figure from the plot
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
#------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
#------------------------------- Beginning of Matplotlib helper code -----------------------
def draw_figure(canvas, figure, loc=(0, 0)):
""" Draw a matplotlib figure onto a Tk canvas
loc: location of top-left corner of figure on canvas in pixels.
Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py
"""
figure_canvas_agg = FigureCanvasAgg(figure)
figure_canvas_agg.draw()
figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds
figure_w, figure_h = int(figure_w), int(figure_h)
photo = Tk.PhotoImage(master=canvas, width=figure_w, height=figure_h)
canvas.create_image(loc[0] + figure_w/2, loc[1] + figure_h/2, image=photo)
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
return photo
#------------------------------- Beginning of GUI CODE -------------------------------
# define the window layout
layout = [[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(figure_w, figure_h), key='canvas')],
[sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', force_toplevel=True).Layout(layout).Finalize()
# add the plot to the window
fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig)
# show it all again and get buttons
event, values = window.Read()
This is not a PySimpleGUI problem, it's a demo program problem. The technique used in the demo programs to integrate Matplotlib to Tkinter is no longer supported and in fact will crash as a result.
I'm SURE there are one or more techniques posted on the net that demonstrate how to do this using 3.1.1.
What's needed is a new Demo_Matplotlib.py that runs with Matplotlib 3.1.1
this version runs on my machine with Matplotlib 3.1.1, but that's just this Demo ...
#!/usr/bin/env python
import sys
if sys.version_info[0] >= 3:
import PySimpleGUI as sg
else:
import PySimpleGUI27 as sg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as Tk
"""
Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
Basic steps are:
* Create a Canvas Element
* Layout form
* Display form (NON BLOCKING)
* Draw plots onto convas
* Display form (BLOCKING)
"""
#------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import NullFormatter # useful for `logit` scale
# Fixing random state for reproducibility
np.random.seed(19680801)
# make up some data in the interval ]0, 1[
y = np.random.normal(loc=0.5, scale=0.4, size=1000)
y = y[(y > 0) & (y < 1)]
y.sort()
x = np.arange(len(y))
# plot with various axes scales
plt.figure(1)
# linear
plt.subplot(221)
plt.plot(x, y)
plt.yscale('linear')
plt.title('linear')
plt.grid(True)
# log
plt.subplot(222)
plt.plot(x, y)
plt.yscale('log')
plt.title('log')
plt.grid(True)
# symmetric log
plt.subplot(223)
plt.plot(x, y - y.mean())
plt.yscale('symlog', linthreshy=0.01)
plt.title('symlog')
plt.grid(True)
# logit
plt.subplot(224)
plt.plot(x, y)
plt.yscale('logit')
plt.title('logit')
plt.grid(True)
plt.gca().yaxis.set_minor_formatter(NullFormatter())
plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
wspace=0.35)
fig = plt.gcf() # if using Pyplot then get the figure from the plot
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
#------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
#------------------------------- Beginning of Matplotlib helper code -----------------------
def draw_figure(canvas, figure, loc=(0, 0)):
""" Draw a matplotlib figure onto a Tk canvas
loc: location of top-left corner of figure on canvas in pixels.
Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py
"""
figure_canvas_agg = FigureCanvasTkAgg(figure,master=canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds
figure_w, figure_h = int(figure_w), int(figure_h)
return
#------------------------------- Beginning of GUI CODE -------------------------------
# define the window layout
layout = [[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(key='canvas')],
[sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', force_toplevel=False).Layout(layout).Finalize()
# add the plot to the window
fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig)
# show it all again and get buttons
event, values = window.Read()
Indeed this does work with the supplied simplified case.
When I tried to apply it to the other Demo Programs, I run into trouble with the Browser, Paned Browser. I haven't tried it with the animated ones.
the only way i got it to work was to forget() the old FigureCanvasTkAgg and pack a new one. No idea if that is really a solution
Any idea why the others draw badly? It's like the clear isn't working
I testet a bit and it seems that:
first every figure_canvas_agg = FigureCanvasTkAgg(fig,master=canvas_elem.TKCanvas) adds to the canvas. so it's not reused and I found no other way to clear it than to forget() and destroy() the old FigureCanvasTkAgg an pack() a new one with the new figure.
And second when you just draw again it seems to depend on how you build the figure, and it seems to be buggy
```python
def UnicodeMinus():
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
# Fixing random state for reproducibility
np.random.seed(19680801)
matplotlib.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots()
ax.plot(10 * np.random.randn(100), 10 * np.random.randn(100), 'o')
ax.set_title('Using hyphen instead of Unicode minus')
return fig
isn't drawn
this one is
```python
def UnicodeMinus():
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
fig=plt.figure(1)
# Fixing random state for reproducibility
np.random.seed(19680801)
matplotlib.rcParams['axes.unicode_minus'] = False
ax = fig.add_subplot()
ax.plot(10 * np.random.randn(100), 10 * np.random.randn(100), 'o')
ax.set_title('Using hyphen instead of Unicode minus')
return fig
but that don't seem to work with all of them or I did it wrong :). That's what I found out. Animation seems to work
seems to be the identifier of the figure(1). When all plots are drawn in the same figure it seems to work. I don't know needs testing.
got the Paned Browser working on my machine, plt.subplots returns a Figure() which is not the plt.figure() and has no num value for identification. should I post it here?
Post the core template here.... like the example. It doesn't have to have a big example in it. And sure, post the Paned Browser for sure!
core template? Sry I don't know what you mean
Don't worry about it, just upload what you have.
I'm not worrying :) I like to understand and learn things. Hope it works
Oh! I thought you were going to simply attach the file seeing how long this particular program is! Can you edit it and drop the .py file into the issue? I'm really sorry I wasn't more explicit!
Wow wow wow! It's working for me!
So, where's the draw_figure routine? The magic that makes all this possible?
its gone because the whole drawing is done with
graph.draw()
graph.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
That means the original drawings all had to change?
I don't know what you mean exactly, FigureCanvasAgg FigureCanvasTkAgg are different, I didn't get it working with the FigureCanvasAgg. And the draw routine from FigureCanvasTkAgg does the blit itself. They had to be changed because the plots are not drawn when they are not in the same figure which was passed to graph = FigureCanvasTkAgg(fig, master=canvas_elem.TKCanvas). Maybe there is a better way to it with FigureCanvasAgg but I didn't found it.
def draw(self):
super(FigureCanvasTkAgg, self).draw()
_backend_tk.blit(self._tkphoto, self.renderer._renderer, (0, 1, 2, 3))
self._master.update_idletasks()
I needed to state the requirements evidently.
The idea is to be able to run any of the Demo plots that are available on the Matplotlib site. So, for example, this page:
https://matplotlib.org/gallery/lines_bars_and_markers/barh.html#sphx-glr-gallery-lines-bars-and-markers-barh-py
Has this code on the page that produces the plot:
import matplotlib.pyplot as plt
import numpy as np
# Fixing random state for reproducibility
np.random.seed(19680801)
plt.rcdefaults()
fig, ax = plt.subplots()
# Example data
people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
error = np.random.rand(len(people))
ax.barh(y_pos, performance, xerr=error, align='center')
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis() # labels read top-to-bottom
ax.set_xlabel('Performance')
ax.set_title('How fast do you want to go today?')
plt.show()
The goal is for the user to be able to run these with no modification. Look at the very first example program, the "template", in this Issue, and you'll see an area marked "Paste your Matplotlib code here".
Matplotlib users are used to running code like the above code. The call that creates a window and shows the plot to the user is plt.show(). So, you could say that this line of code is that we're attempting to replace.
If I run the above code unchanged, the window I'm shown is:

You'll notice in these Matplotlib Demo Programs that the call to plt.show() is replaced by these 2 lines:
fig = plt.gcf() # if using Pyplot then get the figure from the plot
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
If I take those 2 lines and replace the single line plt.show() with those 2 lines, then I'm able to embed this drawing into a PySimpleGUI window.
Using the old 3.0.3 method, when I run the demo code in the Template I originally posted, the window created this this:

The Paned Browser is nothing but a big collection of Matplotlib Example Plots, downloaded directly from their site that I pasted into the code, modifying only the last line of code.
What we're looking for is another bit of code and functions we can add to Matplotlib supplied sample code that will replace plot.show()
That's it in a nutshell.
I see, the modifications I made are just because it is the browser, and different plots are shown in the same figure, when using all these plots as "single plots" nothing has to be changed.
Than there is still the possibility to delete the old FigureCanvasTkAgg and create a new one like I suggested before. Maybe that is what you are looking for. When you exchange these lines in the original Browser it should work.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
.....
canvas_elem = window.FindElement('canvas')
multiline_elem= window.FindElement('multiline')
fig=plt.figure()
graph = FigureCanvasTkAgg(fig, master=canvas_elem.TKCanvas)
while True:
event, values = window.Read()
# print(event)
# show it all again and get buttons
if event in (None, 'Exit'):
break
try:
choice = values['func'][0]
func = fig_dict[choice]
except:
pass
graph.get_tk_widget().forget()
graph.get_tk_widget().destroy()
multiline_elem.Update(inspect.getsource(func))
plt.clf()
fig = func()
graph = FigureCanvasTkAgg(fig, master=canvas_elem.TKCanvas)
graph.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
Not sure how to word this right.
Maybe this is the best way - Replace plt.show() with a another call / porting layer that instead of opening a window will "draw" onto a canvas.
Goals:
Would like the same architecture.... the user pastes their code into a PySimpleGUI "Template Demo" like the initial code of this Issue.
I'm unclear how to make the change you're talking about in your last bit of code. Can you post the entire working example? It doesn't have to be as text. You can drag and drop larger files.
Maybe I should get even more explicit? "Replace the draw_figure function with one that works with 3.1.1".
Now I understand why I wasn't understanding the code snipped posted above. It's of an event loop that's been heavily modified and doesn't look like the original.
I don't want to keep wasting your time here. We seem to be diverging instead of converging.
I think I understood what you mean, I just pointed out that the modification are due to the Browser architecture not the technique of integration. the new Example is working with the Browser without modification of the plots because for every plot a new figure is generated and passed to tkinter. before it was one figure which was passed ones to tkinter and than drawn new for every plot.
now I just modified the draw_figure() and added 2 lines before the event loop and modified the imports.Maybe that's what you looking for maybe not :)
Demo_Matplotlib_Browser_Paned.py.txt
hm didn't thought it was a good idea to import _backend_tk but it seems to work
from matplotlib.backends.backend_tkagg import _backend_tk
_backend_tk.blit(photo, figure_canvas_agg.get_renderer()._renderer, (0, 1, 2, 3))
instead of
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
For Demo programs, the _primary goal_ is not that "it works". It's a necessary, but not sufficient measurement for success in these programs.
For clarification:
I understood that a demo program only works, when it's not just running without an error but meets certain specs. In this case, like stated above that the user can post his or her matplotlib code and just has to change the plt.show() line. Maybe I got that wrong, but that was the reason I suggested in my last post two changes to the original code.
The import of _backend_tk and the use of the internal function
_backend_tk.blit(photo, figure_canvas_agg.get_renderer()._renderer, (0, 1, 2, 3))
instead of
python
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
The use of an internal function may or may not be a good idea or violate the specs.
I just downloaded and looked at your code again. It looks different than the first time I downloaded it, I THINK. I dunno, but the event loop looks different than what I recalled.
I'll give it some time today.
the code I uploaded was my second suggestion it is using the official API without changing the plot code.
If it is OK to use an internal function my last suggestion would be better, I think. because it is using less resources and is accomplishing the same by changing two lines of code in the original Demos. In essence to make the Demos work again one calls the new now internal blit function.
How does your solution compare with other people's integration with Matplotlib 3.1?
In the uploaded File I create a FigureCanvasTkAgg and pack the figure in it. That is the solution to integrate in tk from matplotlib. I didn't found anything about multiple plots. So in the file I create and then destroy the FigureCanvasTkAgg when a new fig/plot is needed, that is necessary because reusing a packed FigureCanvasTkAgg is only possible when the figure which is packed is modified and again drawn, which is impossible without editing the plot code which is no solution here, but the creating and destroying seems to be heavy on resources. Using the internal blit function is more similar to what the Demo programs are doing now, without using the from the API provided FigureCanvasTkAgg which is internally creating the tkphoto and bliting it. And it is working with all Demos without any problem I could find.
The Tkinter pack solution can be seen in use here: matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_tk_sgskip.html
I'm currently using the private method solution as a workaround:
_backend_tk.blit(photo, figure_canvas_agg.get_renderer()._renderer, (0, 1, 2, 3))
OK, I ended up using the pack proposal found here as well as in
https://github.com/PySimpleGUI/PySimpleGUI/issues/1620
I was about to turn my attention back to this when the other issue was updated with the pack.
I've updated both the single drawing and the browser with the new technique.
Thank you for your help @Em-Bo. It's taken a long time to get it released but it's out there now, working with 3.1.1!
Most helpful comment
this version runs on my machine with Matplotlib 3.1.1, but that's just this Demo ...