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

Document some pyodide matplotlib suggestions for node and deno

Open rajsite opened this issue 2 years ago • 4 comments

The Node and Deno contexts can't use the matplotlib_pyodide backends but work fine with Agg.

Takes a second to piece together but one pattern I'm finding useful is to save the output as a data: url.

import base64
import io 
import numpy as np
import matplotlib
from matplotlib import pyplot as plt

matplotlib.use('Agg')

x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)

pic_IObytes = io.BytesIO()
plt.savefig(pic_IObytes, format='png')
pic_IObytes.seek(0)
pic_hash = base64.b64encode(pic_IObytes.read()).decode('utf-8')
dataurl = f'data:image/png;base64,{pic_hash}'
dataurl

Which seems to work! :D

Couldn't think of the right place for it but I think an example like that could be handy (even if just in this issue). Also interested in easier / more efficient patterns for sharing matplotlib figures between the python and JS contexts.

rajsite avatar May 05 '23 01:05 rajsite

Ah lol it was right there the whole time

https://github.com/pyodide/matplotlib-pyodide/blob/6a6d6fb56889adade87771d5216cfa173a38867e/matplotlib_pyodide/wasm_backend.py#L71-L82

rajsite avatar May 05 '23 01:05 rajsite

Well I wouldn't necessarily recommend this if you can avoid it. I think the technique here: https://github.com/pyodide/matplotlib-pyodide/blob/6a6d6fb56889adade87771d5216cfa173a38867e/matplotlib_pyodide/wasm_backend.py#L50 Is more efficient than base64 encoding it (though creating a bytes object may still be an unnecessary copy).

It depends a bit on what you want to do with the image. Do you want to store it to the file system?

hoodmane avatar May 05 '23 01:05 hoodmane

Ahh. Yea, just trying to get the byte array into the js context. Looks like the following is how to access the buffer with FigureCanvasAgg:

import matplotlib
from matplotlib import pyplot as plt
import numpy as np

matplotlib.use('Agg')

x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)

fig.canvas.draw()
buff = fig.canvas.get_renderer().buffer_rgba()

# For illustration
import js
js.window.buff = buff

# data is typed array view on wasm memory
# let data = window.buff.getBuffer('u8').data;

Seems much better than the data URL (assuming the raw buffer is useful)! Would it be zero copies up to this point? If so that's pretty fancy

rajsite avatar May 05 '23 03:05 rajsite

You'll need to use pyodide.ffi.create_proxy but yes this should be zero copy.

hoodmane avatar May 05 '23 04:05 hoodmane