pyodide / matplotlib-pyodide

HTML5 backends for Matplotlib compatible with Pyodide
Mozilla Public License 2.0
27 stars 8 forks source link

plotting fails with `document.pyodideMplTarget` set #53

Open scbarton opened 4 months ago

scbarton commented 4 months ago

I attempted to use document.pyodideMplTarget to set a target element for a plot, and obtained this error message:

  File "<exec>", line 59, in tplot
  File "/lib/python3.11/site-packages/matplotlib/pyplot.py", line 389, in show
    return _get_backend_mod().show(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/matplotlib_pyodide/html5_canvas_backend.py", line 450, in show
    plt.gcf().canvas.show()
  File "/lib/python3.11/site-packages/matplotlib_pyodide/browser_backend.py", line 117, in show
    div = self._create_root_element()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.11/site-packages/matplotlib_pyodide/browser_backend.py", line 317, in _create_root_element
    mpl_target.appendChild(div)
    ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'appendChild'
    M https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    new_error https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    _pythonexc2js https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    callPyObjectKwargs https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    callPyObject https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    apply https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    apply https://cdn.jsdelivr.net/pyodide/v0.25.0/full/pyodide.asm.js:9
    replot http://localhost:8000/lumped_capacitance.html:98
    async* http://localhost:8000/lumped_capacitance.html:103
    EventListener.handleEvent* http://localhost:8000/lumped_capacitance.html:102

I've attached my html code for this error. For now, I'm just using the default append behavior. lumped_capacitance.html.txt

scbarton commented 4 months ago

Ok, I fixed the plotting fail by waiting for DOMContentLoaded before setting document.pyodideMplTarget.

Now, plotting works but the target div is not reused and I still get the default append behavior. html attached.

lumped_capacitance.html.txt

ryanking13 commented 4 months ago

Now, plotting works but the target div is not reused and I still get the default append behavior. html attached.

The target div is used, but what happens is that it does not clean up the old plot when showing a new plot. There is an open issue about this behavior (https://github.com/pyodide/matplotlib-pyodide/issues/49).

Due to the lack of manpower, this repository is not actively maintained, so I would appreciate it if you could contribute to this. I think @yu0A is working on this issue too.

yu0A commented 4 months ago

FigureCanvasBase and child FigureCanvas of Matplotlib cannot be reused. It is like, you should call pyplot.close() method or manually close picture viewer generated by Python if you call pyplot.show() method on Windows env. So my way is manually call pyplot.close() method to destroy old FigureCanvas (divs).

yu0A commented 4 months ago

FigureCanvasBase and child FigureCanvas of Matplotlib cannot be reused. It is like, you should call pyplot.close() method or manually close picture viewer generated by Python if you call pyplot.show() method on Windows env. So my way is manually call pyplot.close() method to destroy old FigureCanvas (divs).

When I call pyplot.close() method, due to FigureCanvas should realize destroy() method, I'm working on this method.

tomitrescak commented 4 months ago

Have you ever got to rendering plot in a custom div?

This is what I got and I always see the div attached to the end:

<!DOCTYPE html>
<html>

<head>
  <script src="https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.js"></script>
</head>

<body>
  <div id="app">
    <canvas id="target" width="400" height="400"></canvas>
  </div>

  <script src="./index.js"></script>
</body>

</html>

And js:

async function main() {
  let pyodide = await loadPyodide();
  document.pyodideMplTarget = document.getElementById("target");

  await pyodide.loadPackage("matplotlib");

  pyodide.runPython(`
import matplotlib
matplotlib.use("module://matplotlib_pyodide.html5_canvas_backend")
import matplotlib.pyplot as plt
import numpy as np
# matplotlib.use("module://matplotlib.backends.html5_canvas_backend")
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()
      `);
}
main();

The wasm backend never works

yu0A commented 4 months ago

Have you ever got to rendering plot in a custom div? The new divs really generates as children of div id="target". you can find the div id="target" in my code.

yu0A commented 4 months ago

This is what I got and I always see the div attached to the end:

That's the problem. The "module://matplotlib_pyodide.html5_canvas_backend" only generates divs in <div></div> tags. Try to change your html to <div id="target" style="width:400px; height:400px;"></div>

tomitrescak commented 4 months ago

Hello, no love for me. I did change the source as you described but still getting the result ina separate div:

image
yu0A commented 4 months ago

Hello, no love for me. I did change the source as you described but still getting the result ina separate div:

I give my development html project to you. In my project the divs can always generate under the target div, and supports manually show and close figure. This project is for you image