From e95c66e1c095766e667f6549d2f1399d9426520d Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 23 Jun 2022 14:20:04 -0400 Subject: [PATCH 1/4] =?UTF-8?q?patch=20matplotlib=E2=80=99s=20show?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/py.js | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/py.js b/src/py.js index 67795e4a..508642a5 100644 --- a/src/py.js +++ b/src/py.js @@ -2,17 +2,56 @@ import {pyodide as Pyodide} from "./dependencies.js"; export default async function py(require) { const pyodide = await (await require(Pyodide.resolve())).loadPyodide(); + let patch; // a promise for patching matplotlib (if needed) return async function py(strings, ...values) { - let globals = {}; + const globals = {}; const code = strings.reduce((code, string, i) => { if (!(i in values)) return code + string; const name = `_${i}`; globals[name] = values[i]; return code + string + name; }, ""); - globals = pyodide.toPy(globals); - await pyodide.loadPackagesFromImports(code); - const value = await pyodide.runPythonAsync(code, {globals}); + const imports = findImports(pyodide, code); + if (imports.includes("matplotlib") && !patch) await (patch = patchMatplotlib(pyodide)); + if (imports.length) await pyodide.loadPackagesFromImports(code); + const value = await pyodide.runPythonAsync(code, {globals: pyodide.toPy(globals)}); return pyodide.isPyProxy(value) ? value.toJs() : value; }; } + +// https://github.com/pyodide/pyodide/blob/1624e4a62445876a2d810fdbfc9ddb69a8321a8e/src/js/api.ts#L119-L125 +function findImports(pyodide, code) { + const imports = pyodide.pyodide_py.find_imports(code); + try { + return imports.toJs(); + } finally { + imports.destroy(); + } +} + +// Overrides matplotlib’s show function to return a DIV such that when used as +// the last expression in an Observable cell, the inspector will display it. +async function patchMatplotlib(pyodide) { + await pyodide.loadPackage("matplotlib"); + await pyodide.runPythonAsync(`from matplotlib import pyplot as plt + +_show = plt.show + +def show(self): + from js import document + div = None + def create_root_element(self): + nonlocal div + div = document.createElement("div") + document.body.appendChild(div) + return div + c = plt.gcf().canvas + c.create_root_element = create_root_element.__get__(c, c.__class__) + _show() + div.remove() + return div + +plt._show = _show +plt.show = show.__get__(plt, plt.__class__) +`); +} From ebeb3de5b92bb0ff02b414225c205a734448fecd Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 23 Jun 2022 15:40:50 -0400 Subject: [PATCH 2/4] font-awesome for matplotlib buttons --- src/py.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/py.js b/src/py.js index 508642a5..e0f79a4a 100644 --- a/src/py.js +++ b/src/py.js @@ -12,7 +12,7 @@ export default async function py(require) { return code + string + name; }, ""); const imports = findImports(pyodide, code); - if (imports.includes("matplotlib") && !patch) await (patch = patchMatplotlib(pyodide)); + if (imports.includes("matplotlib") && !patch) await (patch = patchMatplotlib(require, pyodide)); if (imports.length) await pyodide.loadPackagesFromImports(code); const value = await pyodide.runPythonAsync(code, {globals: pyodide.toPy(globals)}); return pyodide.isPyProxy(value) ? value.toJs() : value; @@ -31,7 +31,13 @@ function findImports(pyodide, code) { // Overrides matplotlib’s show function to return a DIV such that when used as // the last expression in an Observable cell, the inspector will display it. -async function patchMatplotlib(pyodide) { +async function patchMatplotlib(require, pyodide) { + require.resolve("font-awesome@4.7.0/css/font-awesome.min.css").then(href => { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = href; + document.head.appendChild(link); + }); await pyodide.loadPackage("matplotlib"); await pyodide.runPythonAsync(`from matplotlib import pyplot as plt From 992417bd556c73658173acb7798c783824b1b555 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 23 Jun 2022 15:49:57 -0400 Subject: [PATCH 3/4] fix for redrawing figure --- src/py.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/py.js b/src/py.js index e0f79a4a..d5d3111a 100644 --- a/src/py.js +++ b/src/py.js @@ -45,15 +45,14 @@ _show = plt.show def show(self): from js import document - div = None def create_root_element(self): - nonlocal div div = document.createElement("div") document.body.appendChild(div) return div c = plt.gcf().canvas c.create_root_element = create_root_element.__get__(c, c.__class__) _show() + div = c.get_element("") div.remove() return div From fdec02700ec68375cb8725a84d38c4cd117fe342 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 23 Jun 2022 17:00:12 -0400 Subject: [PATCH 4/4] only remove if parent is body --- src/py.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/py.js b/src/py.js index d5d3111a..e96acabc 100644 --- a/src/py.js +++ b/src/py.js @@ -40,23 +40,24 @@ async function patchMatplotlib(require, pyodide) { }); await pyodide.loadPackage("matplotlib"); await pyodide.runPythonAsync(`from matplotlib import pyplot as plt +from js import document _show = plt.show +def create_root_element(self): + div = document.createElement("div") + document.body.appendChild(div) + return div + def show(self): - from js import document - def create_root_element(self): - div = document.createElement("div") - document.body.appendChild(div) - return div c = plt.gcf().canvas c.create_root_element = create_root_element.__get__(c, c.__class__) _show() div = c.get_element("") - div.remove() + if (div.parentNode == document.body): + document.body.removeChild(div) return div -plt._show = _show plt.show = show.__get__(plt, plt.__class__) `); }