Graphing code using PySimpleGUI, MatPlotLib and tkinter crashing on selecting function - python

Hello lovely clever people. I came across this beautiful piece of code that in theory should help me with a project I'm working on. However, each time I run it and select an option from the list, it crashes. The original piece of code was over 900 lines, and can be found here:
https://github.com/PySimpleGUI/PySimpleGUI/blob/e59b0060b6837cfb9ec3809da51e4fe04358b6ee/DemoPrograms/Demo_Matplotlib_Browser_Paned.py
I have reduced it to bare bones to see if I could get it working, but with no luck.
The goal is to get a functioning graphing options list to appear beside a canvas containing the corresponding graph and be able to update the graph based on the choice made by the user.
Thank you in advance!
I am using:
Pycharm 2.5 Community Edition
MatPlotLib version 3.1.1
PySimpleGui version 4.1
Numpy version 1.17
On Mac Os Mojave 10.14.6
I have updated everything that I could. I've also tried running it on a Windows machine, resulting in the same error message.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasAgg
import matplotlib.backends.tkagg as tkagg
import inspect
import numpy as np
import matplotlib.pyplot as plt
import tkinter as Tk
def PyplotHistogram():
np.random.seed(0)
n_bins = 10
x = np.random.randn(1000, 3)
fig, axes = plt.subplots(nrows=2, ncols=2)
ax0, ax1, ax2, ax3 = axes.flatten()
colors = ['red', 'tan', 'lime']
ax0.hist(x, n_bins, density=1, histtype='bar', color=colors, label=colors)
ax0.legend(prop={'size': 10})
ax0.set_title('bars with legend')
ax1.hist(x, n_bins, density=1, histtype='bar', stacked=True)
ax1.set_title('stacked bar')
ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False)
ax2.set_title('stack step (unfilled)')
# Make a multiple-histogram of data-sets with different length.
x_multi = [np.random.randn(n) for n in [10000, 5000, 2000]]
ax3.hist(x_multi, n_bins, histtype='bar')
ax3.set_title('different sample sizes')
fig.tight_layout()
return fig
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)
# Position: convert from top-left anchor to center anchor
canvas.create_image(loc[0] + figure_w/2, loc[1] + figure_h/2, image=photo)
# Unfortunately, there's no accessor for the pointer to the native renderer
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
# Return a handle which contains a reference to the photo object
# which must be kept live or else the picture disappears
return photo
fig_dict = {'Pyplot Histogram' : PyplotHistogram}
sg.ChangeLookAndFeel('LightGreen')
figure_w, figure_h = 650, 650
# define the form layout
listbox_values = [key for key in fig_dict.keys()]
col_listbox = [[sg.Listbox(values=listbox_values, change_submits=True, size=(28, len(listbox_values)), key='func')],
[sg.T(' ' * 12), sg.Exit(size=(5, 2))]]
col_multiline = sg.Column([[sg.Multiline(size=(70, 35), key='multiline')]])
col_canvas = sg.Column([[ sg.Canvas(size=(figure_w, figure_h), key='canvas')]])
layout = [[sg.Text('Matplotlib Plot Test', font=('current 18'))],
[sg.Column(col_listbox), sg.Pane([col_canvas, col_multiline], size=(800,600))],
]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',resizable=True, grab_anywhere=False).Layout(layout)
window.Finalize()
canvas_elem = window.FindElement('canvas')
multiline_elem= window.FindElement('multiline')
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
multiline_elem.Update(inspect.getsource(func))
plt.clf()
fig = func()
fig_photo = draw_figure(canvas_elem.TKCanvas, fig)
*** ERROR MESSAGE ***
/Users/thewarpdrive/PycharmProjects/sports/venv/bin/python "/Users/thewarpdrive/PycharmProjects/sports/breakdown graph options.py"
/Users/thewarpdrive/PycharmProjects/sports/breakdown graph options.py:5: MatplotlibDeprecationWarning:
The matplotlib.backends.tkagg module was deprecated in Matplotlib 3.0 and will be removed in 3.2.
import matplotlib.backends.tkagg as tkagg
*** Changing look and feel is not supported on Mac platform ***
Traceback (most recent call last):
File "/Users/thewarpdrive/PycharmProjects/sports/venv/lib/python3.7/site-packages/matplotlib/backends/tkagg.py", line 27, in blit
dataptr, colormode, bboxptr)
_tkinter.TclError: invalid command name "PyAggImagePhoto"
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/thewarpdrive/PycharmProjects/sports/breakdown graph options.py", line 103, in <module>
fig_photo = draw_figure(canvas_elem.TKCanvas, fig)
File "/Users/thewarpdrive/PycharmProjects/sports/breakdown graph options.py", line 56, in draw_figure
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
File "/Users/thewarpdrive/PycharmProjects/sports/venv/lib/python3.7/site-packages/matplotlib/backends/tkagg.py", line 28, in blit
except tk.TclError:
AttributeError: '_tkinter.tkapp' object has no attribute 'TclError'
Process finished with exit code 1

There is an issue opened on the PySimpleGUI GitHub about problems using Matplotlib 3.1.1 with PySimpleGUI.
Reported and discussed in these issues.
https://github.com/PySimpleGUI/PySimpleGUI/issues/1620
https://github.com/PySimpleGUI/PySimpleGUI/issues/1713
Try installing Matplotlib 3.0.3 instead.
You may find that you can get quicker answers by searching the project's GitHub.

Related

How to adapt this python script to apt installed matplotlib vs pip3 installed

I have a script (MWE supplied)
import matplotlib.pyplot as plt
import matplotlib
s_xLocs = [864]
s_yLocs = [357]
s_score = [0.33915146615180547]
sMax = 0.34704810474264386
for i in range(len(s_xLocs)):
plt.scatter(s_xLocs[i], s_yLocs[i], c=s_score[i], s=(20*(s_score[i]+1.5)**4), cmap="plasma", marker='.', vmin=0, vmax=sMax)
matplotlib.pyplot.close()
which was being used to generate some plots using matplotlib. On my dev machine, I used matplotlib installed via pip3. The script is now being used on some other machines managed by IT and limited to using the version of matplotlib installed via apt install python3-matplotlib. This has caused my script to fail, throwing the error
Traceback (most recent call last):
File "./heatmaps.py", line 9, in <module>
plt.scatter(s_xLocs[i], s_yLocs[i], c=s_score[i], s=(20*(s_score[i]+1.5)**4), cmap="plasma", marker='.', vmin=0, vmax=sMax)
File "/usr/lib/python3/dist-packages/matplotlib/pyplot.py", line 2836, in scatter
__ret = gca().scatter(
File "/usr/lib/python3/dist-packages/matplotlib/__init__.py", line 1601, in inner
return func(ax, *map(sanitize_sequence, args), **kwargs)
File "/usr/lib/python3/dist-packages/matplotlib/axes/_axes.py", line 4451, in scatter
self._parse_scatter_color_args(
File "/usr/lib/python3/dist-packages/matplotlib/axes/_axes.py", line 4264, in _parse_scatter_color_args
n_elem = c_array.shape[0]
IndexError: tuple index out of range
After reading this Q/A I was able to seemingly narrow down the issue to the colormap c argument. After also reading the documentation I also tried passing in the entire list of s_score with no indexing ala
plt.scatter(s_xLocs[i], s_yLocs[i], c=s_score, s=(20*(s_score[i]+1.5)**4), cmap="plasma", marker='.', vmin=0, vmax=sMax)
but that gave a different and more confusing (IMO) error:
...
ValueError: Invalid RGBA argument: 0.33915146615180547
During handling of the above exception, another exception occurred:
...
ValueError: 'c' argument has 1 elements, which is not acceptable for use with 'x' with size 1, 'y' with size 1.
I am hoping someone can provide a solution to this issue which will work with python3-matplotlib and perhaps also clarify the errors/what is different between the version installed with pip3 vs apt.
This could be occasioned because of different versions of matplotlib installed.
As the problem is with the c parameter, I suggest creating a pallet and then getting the color based on the float value:
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib as mpl
s_xLocs = [864]
s_yLocs = [357]
s_score = [0.33915146615180547]
sMax = 0.34704810474264386
palette = cm.get_cmap('plasma')
norm = mpl.colors.Normalize(vmin=0, vmax=sMax)
for i in range(len(s_xLocs)):
color = palette(norm(s_score[i]))
plt.scatter(s_xLocs[i], s_yLocs[i], color=color, s=(20*(s_score[i]+1.5)**4), marker='.')
Another solution that did not break other functionality was to change plotting to this method:
import matplotlib.pyplot as plt
import matplotlib
s_xLocs = [864]
s_yLocs = [357]
s_score = [0.33915146615180547]
sMax = 0.34704810474264386
sSizes = [(20*(size+1.5)**4) for size in s_score]
plt.scatter(s_xLocs, s_yLocs, c=s_score, s=sSizes, cmap="plasma", marker='.', vmin=0, vmax=sMax)
plt.show()

Fatal Python error: PyEval_RestoreThread: NULL tstate , live data plot , matplotlib, python

I am currently managing to show the updating live data with matplotlib, python.
But I see the error.
my code :
plt.rcParams['animation.html'] = 'jshtml'
fig = plt.figure()
ax = fig.add_subplot(111)
fig.show()
i = 0
plot_x , plot_y = [], []
while True :
###some code to get live updating x_in_data###
x_in_data = np.reshape(x_in, (1))
print(x_in_data)
plot_x.append(i)
plot_y.append(x_in_data[0])
ax.plot(plot_x, plot_y, color='b')
fig.canvas.draw()
ax.set_xlim(left=max(0, i-50), right= i+50)
time.sleep(0.1)
i += 1
I see this error message :
Fatal Python error: PyEval_RestoreThread: NULL tstate
Python runtime state: initialized
Current thread 0x00004fc4 (most recent call first):
It plots actually a small graph window, but not working.
Every solution that i found on the internet is not working for my case.
I wonder if it is the problem how the value is updated :
The data value is actually updated with newline,
[-18.75235738]
[-19.02642986]
[-18.75235738]
[-19.02642986]
[-18.75235738]
[-19.02642986]
[-18.92591474]
[-18.75235738]
like this ,and it is not being updated by changing itself.
I would like to know if it is related with the updating method or not.
Or I would like to also know it there is an efficient way to plot the "new line updating value".
Adding fig.canvas.flush_events() after fig.convas.draw() solves the issue.
# ==========Working sample of your code===========
import matplotlib.pyplot as plt
import time
import numpy as np
plt.rcParams['animation.html'] = 'jshtml'
fig = plt.figure()
ax = fig.add_subplot(111)
fig.show()
i = 0
plot_x, plot_y = [], []
while True:
###some code to get live updating x_in_data###
x_in_data = np.reshape(10, (1))
print(x_in_data)
plot_x.append(i)
plot_y.append(x_in_data[0])
ax.plot(plot_x, plot_y, color='b')
fig.canvas.draw()
ax.set_xlim(left=max(0, i - 50), right=i + 50)
fig.canvas.flush_events()
time.sleep(0.1)
i += 1
Referenced from: https://pythonguides.com/matplotlib-update-plot-in-loop/

UserWarning: Animation was deleted without rendering anything

Note: only the last code chunk brings an error. The earlier chunks are to give context to the animation that I want. This is all in Jupyter for Windows.
I have a matplotlib pyplot with two line segments, one is "wrong" and the other is "right." I want to animate the graph to start with both lines where the blue "wrong" line is,and have the red one pivot and move to be in the right place. The "wrong" line goes from (x,y) = (-1.25,9.1) to (0.75,8.0). The "right" line goes from (-1.25,9.7) to (0.75,7.5)
Here is the code for the static comparison:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
boring_fig = plt.figure()
blue = plt.plot([-1.25,.75], [9.1,8], color = 'b', label = 'wrong')
red = plt.plot([-1.25,.75], [9.7,7.5], color = 'r', label = 'right')
plt.show()
Now I want to start them both where the blue line is, and then have the red line incrementally move to the correct position. I made these two arrays for y coordinates to incrementally change between lines. Then I graph everything but the red line, in hopes of adding it as an animation after this.
y_left = np.array([9.1, 9.16, 9.22, 9.28, 9.34, 9.4, 9.46, 9.52, 9.58, 9.64, 9.7])
y_right = np.array([8.0, 7.95, 7.9, 7.85, 7.8, 7.75, 7.7, 7.65, 7.6, 7.55, 7.5])
fig = plt.figure()
blue = plt.plot([-1.25,.75], [9.1,8], color = 'b', label = 'wrong')
plt.show()
And then I try to animate the red line segment shifting along those incremented y values. I get the error somewhere in here:
def animate_1(i):
return plt.plot([-1.25,.75], [y_left[i],y_right[i]], color = 'r'),
anim = FuncAnimation(fig = fig, func = animate_1, interval = 100, frames = 10)
plt.show(anim)
And I get this message: "UserWarning: Animation was deleted without rendering anything. This is most likely unintended. To prevent deletion, assign the Animation to a variable that exists for as long as you need the Animation."
I spent hours trying to figure this out, but I am too much of a noob. Please help.
It turns out I had to install ffmpeg for windows:
https://www.wikihow.com/Install-FFmpeg-on-Windows
Now the graph shows up but I can't see it animating. Oh well. That is an entirely different question.
here is the end graph I made (with a couple of extra details)
Add this line to the beginning of your code in order to see the animation work:
%matplotlib notebook
In Jupyter Notebook (at least on Windows) specifying the GUI toolkit will allow you to see the animation updating, not just a static graph. You can read more about toolkits and user interfaces here.
If there is a message that says:
Warning: Cannot change to a different GUI toolkit: ...
then restart the Jupyter kernel and run the cell again to update the GUI. For troubleshooting the GUI, see this answer.
In regards to the original question, if you set blit = False in FuncAnimation then the animation should display. Otherwise, you can add a return blue, statement to the animate function. See my code below and feel free to ask questions!
Complete Code
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def move_line(num_frames):
x_vals = [-1.25,.75]
y_left_array = np.linspace(9.1, 9.7, num=num_frames, endpoint=True)
y_right_array = np.linspace(8.0, 7.5, num=num_frames, endpoint=True)
fig = plt.figure()
red = plt.plot(x_vals, [9.7,7.5], color = 'r', label = 'right')
blue, = plt.plot([], [], color = 'b', label = 'wrong')
def animate(I):
y_vals = [y_left_array[i],y_right_array[I]]
blue.set_data(x_vals,y_vals)
return blue,
anim = FuncAnimation(
fig,
animate,
frames = num_frames,
interval = 1000/30,
repeat = False,
blit = True
)
plt.show()
return anim
animate_lines = move_line(100)

Matplotlib: Toolbar vs Toolmanager, ToolbarQt vs NavigationToolbar2QT?

I'm trying to embed the "home" function (i.e., "reset original review") to a widget button combined with other operations. Below is an example code I have.
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.widgets import Button
from random import randrange
matplotlib.use('QtAgg')
plt.rcParams['toolbar'] = 'toolmanager' # switch this row to see the difference.
fig, ax = plt.subplots(nrows=1, figsize=(4,6))
ax.imshow([[1, 2], [5, 6]])
def change_color(event):
ax.imshow([[randrange(10), randrange(10)], [randrange(10), randrange(10)]])
plt.get_current_fig_manager().toolbar.home()
axprev0 = plt.axes([0.35, 0.05, 0.3, 0.0375])
bprev0 = Button(axprev0, 'Change Color')
bprev0.on_clicked(change_color)
plt.show()
My problem is that when plt.rcParams['toolbar'] = 'toolmanager' is on, it shows error message toolbar.home() won't work. The reason I need plt.rcParams['toolbar'] = 'toolmanager' is because it allows me to scroll the mouse wheel for zooming when I'm on the zoom or drag tool.
It seems that when we specify plt.rcParams['toolbar'] = 'toolmanager', it is using ToolbarQt. The ToolbarQt supports mouse wheel scrolling for zooming. While the name of the home function of ToolbarQt is different.
If we do not specify plt.rcParams['toolbar'] = 'toolmanager', plt is using NavigationToolbar2QT. However, it does not support mouse wheel scrolling for zooming.
plt.get_current_fig_manager().toolmanager.tools['home'].trigger(fig, event) # use this function when toolmanager

How to solve " 'PathCollection' object has no attribute 'yaxis' " error?

I'm a MSc Student and I used to make graphs and plots with commercial packages like OriginPro, Excel and Matlab. Although these softwares provide a great user experience, there are some major disadvantages as they are specific OS dependent and, in general, very expensive.
Hence, I started to learn Python using matplotlib library with VS Code, however I'm having some problems with some library functions and statements that seems to be standard from matplotlib and numPy, but it doesnt work.
For example, I'm making some templates for scatter plots and I can't control minor ticks because it doesn't recognize the statements xaxix and yaxix:
Sample of the code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, AutoMinorLocator
.
.
.
fig = plt.figure(figsize=(x_pixels/my_dpi, y_pixels/my_dpi), dpi=my_dpi)
ax = plt.scatter(x*format_x, y*format_y, s = size, alpha = transparency, color = color, label = legend_text)
.
.
.
# Major Ticks
plt.tick_params(axis = 'both', which = 'major', length = majorT_length, direction = majorT_direction, color = majorT_color, labelsize = label_size, top = 'on', right = 'on')
# Minor Ticks
plt.minorticks_on()
plt.tick_params(axis='both', which='minor', length = minorT_length, direction = minorT_direction, color = minorT_color, top = 'on', right = 'on')
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
ax.xaxis.set_minor_locator(AutoMinorLocator(2))
# Figure Layout
plt.tight_layout()
plt.savefig(output_file, dpi=my_dpi, bbox_inches=borders)
plt.show()
and the Terminal show this error:
File "c:/Users/luagu/Desktop/Python Matplotlib Training/Scatter_Template.py", line 128, in <module>
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
AttributeError: 'PathCollection' object has no attribute 'yaxis'
What I'm doing wrong?
Thanks in advance!
You wrote ax = plt.scatter but your ax here is an artist returned by the scatter method, not an Axes object. What you want to do is:
plt.scatter(...)
...
ax = plt.gca()
ax.yaxis.set_minor_locator(AutoMinorLocator(2))
ax.xaxis.set_minor_locator(AutoMinorLocator(2))

Categories