|
117 | 117 | outputEl.innerHTML = '<span class="code-output-loading"><i class="bi bi-arrow-repeat"></i> Executing...</span>'; |
118 | 118 |
|
119 | 119 | setTimeout(function () { |
120 | | - try { |
121 | | - pyodide.runPython('sys.stdout = StringIO()\nsys.stderr = StringIO()'); |
122 | | - |
123 | | - var usesMpl = /\bimport\s+matplotlib\b|\bfrom\s+matplotlib\b|\bplt\.\b/.test(code); |
124 | | - if (usesMpl) { |
125 | | - try { |
126 | | - pyodide.runPython("import micropip"); |
127 | | - pyodide.runPython("import matplotlib\nmatplotlib.use('AGG')"); |
128 | | - } catch (e) { /* ignore */ } |
129 | | - } |
| 120 | + // Auto-detect and load ALL imported packages (numpy, pandas, scipy, matplotlib, etc.) |
| 121 | + outputEl.innerHTML = '<span class="code-output-loading"><i class="bi bi-arrow-repeat"></i> Loading packages...</span>'; |
| 122 | + pyodide.loadPackagesFromImports(code).then(function () { |
| 123 | + try { |
| 124 | + pyodide.runPython('sys.stdout = StringIO()\nsys.stderr = StringIO()'); |
130 | 125 |
|
131 | | - pyodide.runPython(code); |
132 | | - |
133 | | - var stdout = pyodide.runPython('sys.stdout.getvalue()'); |
134 | | - var stderr = pyodide.runPython('sys.stderr.getvalue()'); |
135 | | - |
136 | | - var outputHtml = ''; |
137 | | - |
138 | | - if (usesMpl) { |
139 | | - try { |
140 | | - pyodide.runPython([ |
141 | | - 'import matplotlib.pyplot as _plt', |
142 | | - 'import base64 as _b64', |
143 | | - 'from io import BytesIO as _BytesIO', |
144 | | - '_mdv_figs = []', |
145 | | - 'for _fig_num in _plt.get_fignums():', |
146 | | - ' _buf = _BytesIO()', |
147 | | - ' _plt.figure(_fig_num).savefig(_buf, format="png", dpi=100, bbox_inches="tight")', |
148 | | - ' _buf.seek(0)', |
149 | | - ' _mdv_figs.append(_b64.b64encode(_buf.read()).decode())', |
150 | | - ' _buf.close()', |
151 | | - '_plt.close("all")' |
152 | | - ].join('\n')); |
153 | | - var figs = pyodide.runPython('_mdv_figs').toJs(); |
154 | | - if (figs && figs.length > 0) { |
155 | | - figs.forEach(function (b64) { |
156 | | - outputHtml += '<div class="python-plot-output"><img src="data:image/png;base64,' + b64 + '" alt="matplotlib plot" style="max-width:100%;border-radius:6px;margin:4px 0" /></div>'; |
157 | | - }); |
158 | | - } |
159 | | - } catch (e) { /* ignore plot errors */ } |
160 | | - } |
| 126 | + // Set matplotlib backend to AGG and suppress plt.show() warning |
| 127 | + var usesMpl = /\bimport\s+matplotlib\b|\bfrom\s+matplotlib\b|\bplt\.\b/.test(code); |
| 128 | + if (usesMpl) { |
| 129 | + try { pyodide.runPython("import matplotlib\nmatplotlib.use('AGG')\nimport warnings\nwarnings.filterwarnings('ignore', '.*non-GUI backend.*')"); } catch (e) { /* ok */ } |
| 130 | + } |
| 131 | + |
| 132 | + pyodide.runPython(code); |
161 | 133 |
|
162 | | - if (stdout) outputHtml += '<span class="code-output-stdout">' + escapeHtml(stdout) + '</span>'; |
163 | | - if (stderr) outputHtml += '<span class="code-output-stderr">' + escapeHtml(stderr) + '</span>'; |
164 | | - if (!outputHtml) outputHtml = '<span class="code-output-muted">(no output)</span>'; |
165 | | - outputEl.innerHTML = outputHtml; |
166 | | - } catch (runErr) { |
167 | | - outputEl.innerHTML = '<span class="code-output-error">Error: ' + escapeHtml(runErr.message) + '</span>'; |
168 | | - } finally { |
| 134 | + var stdout = pyodide.runPython('sys.stdout.getvalue()'); |
| 135 | + var stderr = pyodide.runPython('sys.stderr.getvalue()'); |
| 136 | + var outputHtml = ''; |
| 137 | + |
| 138 | + // Capture matplotlib figures as inline images |
| 139 | + if (usesMpl) { |
| 140 | + try { |
| 141 | + pyodide.runPython([ |
| 142 | + 'import matplotlib.pyplot as _plt', |
| 143 | + 'import base64 as _b64', |
| 144 | + 'from io import BytesIO as _BytesIO', |
| 145 | + '_mdv_figs = []', |
| 146 | + 'for _fig_num in _plt.get_fignums():', |
| 147 | + ' _buf = _BytesIO()', |
| 148 | + ' _plt.figure(_fig_num).savefig(_buf, format="png", dpi=100, bbox_inches="tight")', |
| 149 | + ' _buf.seek(0)', |
| 150 | + ' _mdv_figs.append(_b64.b64encode(_buf.read()).decode())', |
| 151 | + ' _buf.close()', |
| 152 | + '_plt.close("all")' |
| 153 | + ].join('\n')); |
| 154 | + var figs = pyodide.runPython('_mdv_figs').toJs(); |
| 155 | + if (figs && figs.length > 0) { |
| 156 | + figs.forEach(function (b64) { |
| 157 | + outputHtml += '<div class="python-plot-output"><img src="data:image/png;base64,' + b64 + '" alt="matplotlib plot" style="max-width:100%;border-radius:6px;margin:4px 0" /></div>'; |
| 158 | + }); |
| 159 | + } |
| 160 | + } catch (e) { /* ignore plot errors */ } |
| 161 | + } |
| 162 | + |
| 163 | + if (stdout) outputHtml += '<span class="code-output-stdout">' + escapeHtml(stdout) + '</span>'; |
| 164 | + if (stderr) outputHtml += '<span class="code-output-stderr">' + escapeHtml(stderr) + '</span>'; |
| 165 | + if (!outputHtml) outputHtml = '<span class="code-output-muted">(no output)</span>'; |
| 166 | + outputEl.innerHTML = outputHtml; |
| 167 | + } catch (runErr) { |
| 168 | + outputEl.innerHTML = '<span class="code-output-error">Error: ' + escapeHtml(runErr.message) + '</span>'; |
| 169 | + } finally { |
| 170 | + btnRun.disabled = false; |
| 171 | + btnRun.innerHTML = '<i class="bi bi-play-fill"></i> Run'; |
| 172 | + } |
| 173 | + }).catch(function (loadErr) { |
| 174 | + outputEl.innerHTML = '<span class="code-output-error">Failed to load packages: ' + escapeHtml(loadErr.message) + '</span>'; |
169 | 175 | btnRun.disabled = false; |
170 | 176 | btnRun.innerHTML = '<i class="bi bi-play-fill"></i> Run'; |
171 | | - } |
| 177 | + }); |
172 | 178 | }, 50); |
173 | 179 | }, function (msg) { |
174 | 180 | outputEl.innerHTML = '<span class="code-output-loading"><i class="bi bi-arrow-repeat"></i> ' + escapeHtml(msg) + '</span>'; |
|
0 commit comments