An easy-to-use platform for EEG experimentation in the classroom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add plot as an svg

+34 -285
+4
.llms/learnings.md
··· 56 56 57 57 **`micropip.install()` from JS accepts a JS array directly** — as of Pyodide 0.29.x, micropip handles the `JsProxy` conversion internally. `pyodide.toPy()` is not needed. 58 58 59 + **WebAgg backend does not work in web workers** — WebAgg tries to access `js.document` to inject CSS/JS into the DOM on first import, which throws `ImportError: cannot import name 'document' from 'js'` in a worker context. Use `agg` instead. Set it via `os.environ["MPLBACKEND"] = "agg"` before any matplotlib import. `fig.savefig()` works with `agg` and is the correct way to get plot images back to the renderer. 60 + 61 + **Plot result routing pattern** — `worker.postMessage()` is fire-and-forget (returns `undefined`). Plot epics should use `tap()` to fire the worker message and `mergeMap(() => EMPTY)` to emit nothing. Results come back asynchronously on the worker `message` event. Add a `plotKey` field to each worker message; the worker echoes it back; `pyodideMessageEpic` switches on `plotKey` to dispatch `SetTopoPlot`/`SetPSDPlot`/`SetERPPlot` with a `{ 'image/png': base64string }` MIME bundle. `PyodidePlotWidget` renders this via `@nteract/transforms`. 62 + 59 63 ## Pre-existing TypeScript errors (do not treat as regressions) 60 64 61 65 - `src/renderer/epics/experimentEpics.ts` (lines 170, 205) — RxJS operator type mismatch
-205
package-lock.json
··· 14 14 "@electron-toolkit/utils": "^4.0.0", 15 15 "@fortawesome/fontawesome-free": "^5.13.0", 16 16 "@neurosity/pipes": "^5.2.1", 17 - "@nteract/transforms": "^3.2.0", 18 17 "@radix-ui/react-dialog": "^1.1.0", 19 18 "@radix-ui/react-dropdown-menu": "^2.1.0", 20 19 "@radix-ui/react-select": "^2.2.6", ··· 593 592 "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", 594 593 "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", 595 594 "license": "MIT", 596 - "engines": { 597 - "node": ">=6.9.0" 598 - } 599 - }, 600 - "node_modules/@babel/runtime-corejs2": { 601 - "version": "7.28.6", 602 - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.28.6.tgz", 603 - "integrity": "sha512-pOHfxftxpetWUeBacCB3ZOPc/OO6hiT9MLv0qd9j474khiCcduwO8uuJI3N7vX3m8GJotTT6lxlA89TS/PylGg==", 604 - "license": "MIT", 605 - "dependencies": { 606 - "core-js": "^2.6.12" 607 - }, 608 595 "engines": { 609 596 "node": ">=6.9.0" 610 597 } ··· 2579 2566 "node": ">=10" 2580 2567 } 2581 2568 }, 2582 - "node_modules/@nteract/transform-vdom": { 2583 - "version": "2.2.5", 2584 - "resolved": "https://registry.npmjs.org/@nteract/transform-vdom/-/transform-vdom-2.2.5.tgz", 2585 - "integrity": "sha512-q6FbWlrSEWUmQpDV1DBPcw5FZpUcQbKOQ2a59vY/qcQ/Qjh1KUCC+gortso+WIE4P36eHZRxKz5ptCu5i47OLg==", 2586 - "license": "BSD-3-Clause", 2587 - "dependencies": { 2588 - "@babel/runtime-corejs2": "^7.0.0", 2589 - "babel-runtime": "^6.26.0", 2590 - "lodash": "^4.17.4" 2591 - }, 2592 - "peerDependencies": { 2593 - "react": "^16.3.2" 2594 - } 2595 - }, 2596 - "node_modules/@nteract/transforms": { 2597 - "version": "3.2.0", 2598 - "resolved": "https://registry.npmjs.org/@nteract/transforms/-/transforms-3.2.0.tgz", 2599 - "integrity": "sha512-9P926e2tm0H1IHF2ER6f0+At5NPgrMgvNOPZTn+K6e9M9+EpNPbZq4q5YUX1xKG8YaK2fpiH+4XVkFBf06YOJg==", 2600 - "deprecated": "This package has been deprecated. Please access each transform through its own package.", 2601 - "license": "BSD-3-Clause", 2602 - "dependencies": { 2603 - "@nteract/transform-vdom": "^2.1.0", 2604 - "ansi-to-react": "^2.0.6", 2605 - "commonmark": "^0.28.0", 2606 - "commonmark-react-renderer": "^4.3.3", 2607 - "mathjax-electron": "^2.0.1", 2608 - "react-json-tree": "^0.11.0" 2609 - }, 2610 - "peerDependencies": { 2611 - "react": "^16.2.0" 2612 - } 2613 - }, 2614 - "node_modules/@nteract/transforms/node_modules/commonmark": { 2615 - "version": "0.28.1", 2616 - "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.28.1.tgz", 2617 - "integrity": "sha512-PklsZ9pgrfFQ5hQH9BRzoWnqI9db2LeR9MhvkNk8iz97kfaTNmhTU+IE8jKDHTEfivZZXoFqzGqzddXdk14EJw==", 2618 - "license": "BSD-2-Clause", 2619 - "dependencies": { 2620 - "entities": "~ 1.1.1", 2621 - "mdurl": "~ 1.0.1", 2622 - "minimist": "~ 1.2.0", 2623 - "string.prototype.repeat": "^0.2.0" 2624 - }, 2625 - "bin": { 2626 - "commonmark": "bin/commonmark" 2627 - }, 2628 - "engines": { 2629 - "node": "*" 2630 - } 2631 - }, 2632 2569 "node_modules/@parcel/watcher": { 2633 2570 "version": "2.5.6", 2634 2571 "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", ··· 5281 5218 "integrity": "sha512-0V/PkoculFl5+0Lp47JoxUcO0xSxhIBvm+BxHdD/OgXNmdRpRHCFnKVuUoWyS9EzQP+otSGv0m9Lb4yVkQBn2A==", 5282 5219 "license": "MIT" 5283 5220 }, 5284 - "node_modules/anser": { 5285 - "version": "1.4.10", 5286 - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", 5287 - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", 5288 - "license": "MIT" 5289 - }, 5290 5221 "node_modules/ansi-regex": { 5291 5222 "version": "5.0.1", 5292 5223 "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", ··· 5312 5243 }, 5313 5244 "funding": { 5314 5245 "url": "https://github.com/chalk/ansi-styles?sponsor=1" 5315 - } 5316 - }, 5317 - "node_modules/ansi-to-react": { 5318 - "version": "2.0.6", 5319 - "resolved": "https://registry.npmjs.org/ansi-to-react/-/ansi-to-react-2.0.6.tgz", 5320 - "integrity": "sha512-AnzmnQcMmCqbd72cRridR94RR0YQpv7Bvbm7YNSGnReTwFQmLkfaZzw4Ajg7HRfR6ZxCEa90sJWEZFhCOPcWhA==", 5321 - "license": "MPL-2.0", 5322 - "dependencies": { 5323 - "anser": "^1.4.1", 5324 - "escape-carriage": "^1.2.0" 5325 - }, 5326 - "peerDependencies": { 5327 - "react": "^16.2.0", 5328 - "react-dom": "^16.2.0" 5329 5246 } 5330 5247 }, 5331 5248 "node_modules/any-promise": { ··· 5994 5911 "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 5995 5912 "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 5996 5913 "dev": true, 5997 - "license": "MIT" 5998 - }, 5999 - "node_modules/base16": { 6000 - "version": "1.0.0", 6001 - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", 6002 - "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==", 6003 5914 "license": "MIT" 6004 5915 }, 6005 5916 "node_modules/base64-arraybuffer": { ··· 6865 6776 "node": ">=4.0.0" 6866 6777 } 6867 6778 }, 6868 - "node_modules/commonmark-react-renderer": { 6869 - "version": "4.3.5", 6870 - "resolved": "https://registry.npmjs.org/commonmark-react-renderer/-/commonmark-react-renderer-4.3.5.tgz", 6871 - "integrity": "sha512-UwUgplz8kFSMCe9+Dg/BcV75lc7R/V6mvMYJq2p29i5aaIBd0252k9HeSGa2VtEPHfg2/trS9qC7iAxnO7r6ng==", 6872 - "license": "MIT", 6873 - "dependencies": { 6874 - "lodash.assign": "^4.2.0", 6875 - "lodash.isplainobject": "^4.0.6", 6876 - "pascalcase": "^0.1.1", 6877 - "xss-filters": "^1.2.6" 6878 - }, 6879 - "peerDependencies": { 6880 - "commonmark": "^0.27.0 || ^0.26.0 || ^0.24.0", 6881 - "react": ">=0.14.0" 6882 - } 6883 - }, 6884 6779 "node_modules/compare-version": { 6885 6780 "version": "0.1.2", 6886 6781 "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", ··· 9186 9081 "once": "^1.4.0" 9187 9082 } 9188 9083 }, 9189 - "node_modules/entities": { 9190 - "version": "1.1.2", 9191 - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", 9192 - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", 9193 - "license": "BSD-2-Clause" 9194 - }, 9195 9084 "node_modules/env-paths": { 9196 9085 "version": "2.2.1", 9197 9086 "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", ··· 9527 9416 "engines": { 9528 9417 "node": ">=6" 9529 9418 } 9530 - }, 9531 - "node_modules/escape-carriage": { 9532 - "version": "1.3.1", 9533 - "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", 9534 - "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==", 9535 - "license": "MIT" 9536 9419 }, 9537 9420 "node_modules/escape-string-regexp": { 9538 9421 "version": "4.0.0", ··· 12972 12855 "version": "4.17.23", 12973 12856 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", 12974 12857 "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", 12975 - "license": "MIT" 12976 - }, 12977 - "node_modules/lodash.assign": { 12978 - "version": "4.2.0", 12979 - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", 12980 - "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", 12981 - "license": "MIT" 12982 - }, 12983 - "node_modules/lodash.curry": { 12984 - "version": "4.1.1", 12985 - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", 12986 - "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA= sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", 12987 12858 "license": "MIT" 12988 12859 }, 12989 12860 "node_modules/lodash.escaperegexp": { ··· 12992 12863 "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", 12993 12864 "license": "MIT" 12994 12865 }, 12995 - "node_modules/lodash.flow": { 12996 - "version": "3.5.0", 12997 - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", 12998 - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==", 12999 - "license": "MIT" 13000 - }, 13001 12866 "node_modules/lodash.isequal": { 13002 12867 "version": "4.5.0", 13003 12868 "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", 13004 12869 "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA= sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", 13005 12870 "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", 13006 - "license": "MIT" 13007 - }, 13008 - "node_modules/lodash.isplainobject": { 13009 - "version": "4.0.6", 13010 - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 13011 - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", 13012 12871 "license": "MIT" 13013 12872 }, 13014 12873 "node_modules/lodash.merge": { ··· 13467 13326 "node": ">=0.10.0" 13468 13327 } 13469 13328 }, 13470 - "node_modules/mathjax-electron": { 13471 - "version": "2.0.1", 13472 - "resolved": "https://registry.npmjs.org/mathjax-electron/-/mathjax-electron-2.0.1.tgz", 13473 - "integrity": "sha512-bllJaZZUccbj1ReD9i0V6qwu27dZXbd7TG/Wy3M7F10NLEjl8yN0WgFFP9uYf35s0hkody6wSPO96txr68TOqg==", 13474 - "license": "MIT" 13475 - }, 13476 13329 "node_modules/mathml-tag-names": { 13477 13330 "version": "4.0.0", 13478 13331 "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", ··· 13490 13343 "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", 13491 13344 "dev": true, 13492 13345 "license": "CC0-1.0" 13493 - }, 13494 - "node_modules/mdurl": { 13495 - "version": "1.0.1", 13496 - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", 13497 - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", 13498 - "license": "MIT" 13499 13346 }, 13500 13347 "node_modules/meow": { 13501 13348 "version": "14.0.0", ··· 14621 14468 "url": "https://github.com/fb55/entities?sponsor=1" 14622 14469 } 14623 14470 }, 14624 - "node_modules/pascalcase": { 14625 - "version": "0.1.1", 14626 - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", 14627 - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", 14628 - "license": "MIT", 14629 - "engines": { 14630 - "node": ">=0.10.0" 14631 - } 14632 - }, 14633 14471 "node_modules/path-exists": { 14634 14472 "version": "4.0.0", 14635 14473 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", ··· 15305 15143 "node": ">=6" 15306 15144 } 15307 15145 }, 15308 - "node_modules/pure-color": { 15309 - "version": "1.3.0", 15310 - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", 15311 - "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==", 15312 - "license": "MIT" 15313 - }, 15314 15146 "node_modules/pyodide": { 15315 15147 "version": "0.29.3", 15316 15148 "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.29.3.tgz", ··· 15543 15375 }, 15544 15376 "engines": { 15545 15377 "node": ">=0.10.0" 15546 - } 15547 - }, 15548 - "node_modules/react-base16-styling": { 15549 - "version": "0.5.3", 15550 - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", 15551 - "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk= sha512-EPuchwVvYPSFFIjGpH0k6wM0HQsmJ0vCk7BSl5ryxMVFIWW4hX4Kksu4PNtxfgOxDebTLkJQ8iC7zwAql0eusg==", 15552 - "license": "MIT", 15553 - "dependencies": { 15554 - "base16": "^1.0.0", 15555 - "lodash.curry": "^4.0.1", 15556 - "lodash.flow": "^3.3.0", 15557 - "pure-color": "^1.2.0" 15558 15378 } 15559 15379 }, 15560 15380 "node_modules/react-dom": { ··· 15576 15396 "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 15577 15397 "license": "MIT" 15578 15398 }, 15579 - "node_modules/react-json-tree": { 15580 - "version": "0.11.2", 15581 - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.2.tgz", 15582 - "integrity": "sha512-aYhUPj1y5jR3ZQ+G3N7aL8FbTyO03iLwnVvvEikLcNFqNTyabdljo9xDftZndUBFyyyL0aK3qGO9+8EilILHUw==", 15583 - "license": "MIT", 15584 - "dependencies": { 15585 - "babel-runtime": "^6.6.1", 15586 - "prop-types": "^15.5.8", 15587 - "react-base16-styling": "^0.5.1" 15588 - }, 15589 - "peerDependencies": { 15590 - "react": "^15.0.0 || ^16.0.0", 15591 - "react-dom": "^15.0.0 || ^16.0.0" 15592 - } 15593 - }, 15594 15399 "node_modules/react-lifecycles-compat": { 15595 15400 "version": "3.0.4", 15596 15401 "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", ··· 17132 16937 "funding": { 17133 16938 "url": "https://github.com/sponsors/ljharb" 17134 16939 } 17135 - }, 17136 - "node_modules/string.prototype.repeat": { 17137 - "version": "0.2.0", 17138 - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", 17139 - "integrity": "sha512-1BH+X+1hSthZFW+X+JaUkjkkUPwIlLEMJBLANN3hOob3RhEk5snLWNECDnYbgn/m5c5JV7Ersu1Yubaf+05cIA==" 17140 16940 }, 17141 16941 "node_modules/string.prototype.trim": { 17142 16942 "version": "1.2.10", ··· 19383 19183 "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", 19384 19184 "dev": true, 19385 19185 "license": "MIT" 19386 - }, 19387 - "node_modules/xss-filters": { 19388 - "version": "1.2.7", 19389 - "resolved": "https://registry.npmjs.org/xss-filters/-/xss-filters-1.2.7.tgz", 19390 - "integrity": "sha512-KzcmYT/f+YzcYrYRqw6mXxd25BEZCxBQnf+uXTopQDIhrmiaLwO+f+yLsIvvNlPhYvgff8g3igqrBxYh9k8NbQ==" 19391 19186 }, 19392 19187 "node_modules/xtend": { 19393 19188 "version": "4.0.2",
-1
package.json
··· 186 186 "@electron-toolkit/utils": "^4.0.0", 187 187 "@fortawesome/fontawesome-free": "^5.13.0", 188 188 "@neurosity/pipes": "^5.2.1", 189 - "@nteract/transforms": "^3.2.0", 190 189 "@radix-ui/react-dialog": "^1.1.0", 191 190 "@radix-ui/react-dropdown-menu": "^2.1.0", 192 191 "@radix-ui/react-select": "^2.2.6",
+1 -1
src/main/index.ts
··· 265 265 'fs:storePyodideImage', 266 266 (_event, title, imageTitle, rawData: ArrayBuffer) => { 267 267 const dir = path.join(getWorkspaceDir(title), 'Results', 'Images'); 268 - const filename = `${imageTitle}.png`; 268 + const filename = `${imageTitle}.svg`; 269 269 mkdirPathSync(dir); 270 270 const buffer = Buffer.from(rawData); 271 271 return new Promise<void>((resolve, reject) => {
+16 -65
src/renderer/components/PyodidePlotWidget.tsx
··· 1 1 import React, { Component } from 'react'; 2 2 import { Button } from './ui/button'; 3 - import { 4 - richestMimetype, 5 - standardDisplayOrder, 6 - standardTransforms, 7 - } from '@nteract/transforms'; 8 - import { isNil } from 'lodash'; 9 3 import { storePyodideImage } from '../utils/filesystem/storage'; 10 4 11 5 interface Props { 12 6 title: string; 13 7 imageTitle: string; 14 - plotMIMEBundle: 15 - | { 16 - [key: string]: string; 17 - } 18 - | null 19 - | undefined; 8 + plotMIMEBundle: { 'image/svg+xml': string } | null | undefined; 20 9 } 21 10 22 - interface State { 23 - rawData: string; 24 - mimeType: string; 25 - } 26 - 27 - export default class PyodidePlotWidget extends Component<Props, State> { 28 - // state: State; 11 + export default class PyodidePlotWidget extends Component<Props> { 29 12 constructor(props: Props) { 30 13 super(props); 31 - this.state = { 32 - rawData: '', 33 - mimeType: '', 34 - }; 35 14 this.handleSave = this.handleSave.bind(this); 36 15 } 37 16 38 - componentDidUpdate(prevProps: Props) { 39 - if ( 40 - this.props.plotMIMEBundle !== prevProps.plotMIMEBundle && 41 - !isNil(this.props.plotMIMEBundle) 42 - ) { 43 - const bundle = this.props.plotMIMEBundle as { [key: string]: string }; 44 - const mimeType = richestMimetype( 45 - bundle, 46 - standardDisplayOrder, 47 - standardTransforms 48 - ); 49 - if (mimeType) { 50 - this.setState({ rawData: bundle[mimeType], mimeType }); 51 - } 52 - } 53 - } 54 - 55 17 handleSave() { 56 - const buf = Buffer.from(this.state.rawData, 'base64'); 57 - storePyodideImage( 58 - this.props.title, 59 - this.props.imageTitle, 60 - buf.buffer as ArrayBuffer 61 - ); 62 - } 63 - 64 - renderResults() { 65 - if (this.state.rawData) { 66 - const Transform = standardTransforms[this.state.mimeType]; 67 - return <Transform data={this.state.rawData} />; 68 - } 69 - } 70 - 71 - renderSaveButton() { 72 - if (this.state.rawData) { 73 - return ( 74 - <Button variant="default" size="sm" onClick={this.handleSave}> 75 - Save Image 76 - </Button> 77 - ); 78 - } 18 + const svg = this.props.plotMIMEBundle?.['image/svg+xml']; 19 + if (!svg) return; 20 + const buf = Buffer.from(svg, 'utf8'); 21 + storePyodideImage(this.props.title, this.props.imageTitle, buf.buffer as ArrayBuffer); 79 22 } 80 23 81 24 render() { 25 + const svg = this.props.plotMIMEBundle?.['image/svg+xml']; 26 + if (!svg) return <div className="p-2" />; 82 27 return ( 83 28 <div className="p-2"> 84 - {this.renderResults()} 85 - {this.renderSaveButton()} 29 + <img 30 + className="w-full h-auto" 31 + src={`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`} 32 + alt={this.props.imageTitle} 33 + /> 34 + <Button variant="default" size="sm" onClick={this.handleSave}> 35 + Save Image 36 + </Button> 86 37 </div> 87 38 ); 88 39 }
+1 -1
src/renderer/epics/pyodideEpics.ts
··· 97 97 } 98 98 // Route plot results to the appropriate Redux state slot. 99 99 // results is a base64-encoded PNG string returned from Python. 100 - const mimeBundle = results ? { 'image/png': results } : null; 100 + const mimeBundle = results ? { 'image/svg+xml': results } : null; 101 101 switch (plotKey) { 102 102 case 'topo': return of(PyodideActions.SetTopoPlot(mimeBundle)); 103 103 case 'psd': return of(PyodideActions.SetPSDPlot(mimeBundle));
+12 -12
src/renderer/utils/webworker/index.ts
··· 115 115 worker.postMessage({ 116 116 plotKey: 'psd', 117 117 data: [ 118 - 'import io, base64', 118 + 'import io', 119 119 '_fig = raw.plot_psd(fmin=1, fmax=30, show=False)', 120 120 '_buf = io.BytesIO()', 121 - '_fig.savefig(_buf, format="png", bbox_inches="tight")', 121 + '_fig.savefig(_buf, format="svg", bbox_inches="tight")', 122 122 'plt.close(_fig)', 123 - 'base64.b64encode(_buf.getvalue()).decode()', 123 + '_buf.getvalue().decode()', 124 124 ].join('\n'), 125 125 }); 126 126 }; ··· 129 129 worker.postMessage({ 130 130 plotKey: 'topo', 131 131 data: [ 132 - 'import io, base64', 132 + 'import io', 133 133 '_fig = plot_topo(clean_epochs, conditions)', 134 134 '_buf = io.BytesIO()', 135 - '_fig.savefig(_buf, format="png", bbox_inches="tight")', 135 + '_fig.savefig(_buf, format="svg", bbox_inches="tight")', 136 136 'plt.close(_fig)', 137 - 'base64.b64encode(_buf.getvalue()).decode()', 137 + '_buf.getvalue().decode()', 138 138 ].join('\n'), 139 139 }); 140 140 }; ··· 144 144 worker.postMessage({ 145 145 plotKey: 'topo', 146 146 data: [ 147 - 'import io, base64', 147 + 'import io', 148 148 'import matplotlib.pyplot as plt', 149 149 '_fig, _ax = plt.subplots()', 150 150 '_ax.plot([1, 2, 3, 4], [1, 4, 2, 3])', 151 151 '_ax.set_title("Test Plot")', 152 152 '_buf = io.BytesIO()', 153 - '_fig.savefig(_buf, format="png", bbox_inches="tight")', 153 + '_fig.savefig(_buf, format="svg", bbox_inches="tight")', 154 154 'plt.close(_fig)', 155 - 'base64.b64encode(_buf.getvalue()).decode()', 155 + '_buf.getvalue().decode()', 156 156 ].join('\n'), 157 157 }); 158 158 }; ··· 161 161 worker.postMessage({ 162 162 plotKey: 'erp', 163 163 data: [ 164 - 'import io, base64', 164 + 'import io', 165 165 `_fig, _ = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, ci=97.5, n_boot=1000, title='', diff_waveform=None)`, 166 166 '_buf = io.BytesIO()', 167 - '_fig.savefig(_buf, format="png", bbox_inches="tight")', 167 + '_fig.savefig(_buf, format="svg", bbox_inches="tight")', 168 168 'plt.close(_fig)', 169 - 'base64.b64encode(_buf.getvalue()).decode()', 169 + '_buf.getvalue().decode()', 170 170 ].join('\n'), 171 171 }); 172 172 };