matplotlib-pyodide icon indicating copy to clipboard operation
matplotlib-pyodide copied to clipboard

AttributeError: 'TimerWasm' object has no attribute '_timer'

Open sadukie opened this issue 1 year ago • 8 comments

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'

sadukie avatar Dec 28 '23 13:12 sadukie

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.

ryanking13 avatar Dec 30 '23 12:12 ryanking13

Ah... I see....

sadukie avatar Jan 02 '24 19:01 sadukie

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 .

ryanking13 avatar Jan 03 '24 09:01 ryanking13

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?

jburgy avatar Nov 16 '24 18:11 jburgy

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)

I have proposed supporting this directly in pyscript.

jburgy avatar Nov 20 '24 15:11 jburgy

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.

drdebmath avatar Apr 21 '25 17:04 drdebmath

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.

ryanking13 avatar Apr 22 '25 08:04 ryanking13

I went with direct animation using javascript by using json to send data from python output using js library of pyodide.

drdebmath avatar Apr 22 '25 10:04 drdebmath