matplotlib-pyodide
matplotlib-pyodide copied to clipboard
AttributeError: 'TimerWasm' object has no attribute '_timer'
Problem
Another community member and I were trying to see if Matplotlib's animation.FuncAnimation would work in PyScript using this Matplotlib demo.
We ran into the following error:
AttributeError: 'TimerWasm' object has no attribute '_timer'
The code is available here on PyScript.com.
More Details
Here's the stack trace of the error:
Traceback (most recent call last):
File "/lib/python311.zip/_pyodide/_base.py", line 499, in eval_code
.run(globals, locals)
^^^^^^^^^^^^^^^^^^^^
File "/lib/python311.zip/_pyodide/_base.py", line 340, in run
coroutine = eval(self.code, globals, locals)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<exec>", line 78, in <module>
File "/lib/python3.11/site-packages/matplotlib/animation.py", line 1634, in __init__
super().__init__(fig, **kwargs)
File "/lib/python3.11/site-packages/matplotlib/animation.py", line 1395, in __init__
event_source = fig.canvas.new_timer(interval=self._interval)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/lib/python3.11/site-packages/matplotlib_pyodide/browser_backend.py", line 416, in new_timer
return TimerWasm(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/lib/python3.11/site-packages/matplotlib/backend_bases.py", line 1097, in __init__
self.interval = 1000 if interval is None else interval
^^^^^^^^^^^^^
File "/lib/python3.11/site-packages/matplotlib/backend_bases.py", line 1139, in interval
self._timer_set_interval()
File "/lib/python3.11/site-packages/matplotlib_pyodide/browser_backend.py", line 510, in _timer_set_interval
if self._timer is not None:
^^^^^^^^^^^
AttributeError: 'TimerWasm' object has no attribute '_timer'
Thanks for the report! It seems like there was a _timer attribute in TimerBase object in old matplotlib versions but it has been removed. So the TimerWasm object would need to be updated to be compatible with newer matplotlib versions.
Currently, no Pyodide maintainers are actively maintaining matplotlib-pyodide project, and AFAIK none of them have deep knowledge of matplotlib. Therefore, it would be great if people with knowledge of matplotlib could help resolve this issue.
Ah... I see....
self._timerwas set toNoneas part of__init__as late as v3.2.x: Backend Bases in Matplotlib v3.2.x- It's gone as of matplotlib 3.3.x - see this commit for details on the change.
Thanks for the check. Maybe we can remove the self._timer attribute from the TimerWasm object, if it is not used in TimerBase at all... but I think we don't have a test case so I am not sure what would be the side effect of it .
I managed to circumvent the immediate issue by adding
from matplotlib_pyodide.browser_backend import TimerWasm
class Timer(TimerWasm):
def __init__(self, interval=None):
self._timer = None
super().__init__(interval=interval)
and injecting that new implementation to FunctionAnimation via the event_source=Timer(interval=30) parameter. Unfortunately, only the axes and labels render, no animation! You can see the result on PyScript.com
@sadukie @ryanking13 any idea?
The animation was not working because pyscript.display saves the figure to PNG. A silly broke-around is to replace display(fig) by
# pyscript.display doesn't know .to_jshtml so call it ourself
html = ani.to_jshtml()
element = document.getElementById(current_target())
if element.tagName == "SCRIPT":
element = getattr(element, "target", element)
element.replaceChildren()
script_element = document.createRange().createContextualFragment(html)
element.append(script_element)
What is an alternative to display matplotlib animation in the browser? It would be okay for my usecase to just show the output as animation using another animation library that is js native as long as I can get the data from python code.
I do not know how matplotlib's animation feature works, so I cannot be sure. But I think you can try functions like savefig to save the image to a file (or bytesIO) and pass that data into some other animation library.
I went with direct animation using javascript by using json to send data from python output using js library of pyodide.