From 5995974574efb6caf19db45ba87137b97a7d29fd Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Mon, 25 Mar 2024 15:29:16 +0100 Subject: [PATCH] Added LoadScriptOrRequireJSModuleAndRun; plotly.DisplayFig now shows up with HTML export of notebook. --- docs/CHANGELOG.md | 7 +++ examples/tutorial.ipynb | 104 +++++++++++++++++++++++----------------- gonbui/javascript.go | 102 ++++++++++++++++++++++++++++++++++++++- gonbui/plotly/plotly.go | 7 ++- 4 files changed, 172 insertions(+), 48 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6f87ca1..864eca1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,12 @@ # GoNB Changelog +## Next + +* Added `LoadScriptOrRequireJSModuleAndRun` that handles dynamicly decide is include script using `" ] }, "metadata": {}, @@ -1337,7 +1353,7 @@ { "data": { "text/html": [ - "
" + "
" ] }, "metadata": {}, @@ -1367,7 +1383,7 @@ { "data": { "text/html": [ - "  5.50 " + "  5.50 " ] }, "metadata": {}, @@ -1376,7 +1392,7 @@ { "data": { "text/html": [ - "Animated Sine" + "Animated Sine" ] }, "metadata": {}, @@ -1509,15 +1525,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "go version go1.22.0 linux/amd64\n", + "go version go1.22.1 linux/amd64\n", "/home/janpf/Projects/gonb/examples\n", - "total 324\n", - "-rw-r--r-- 1 janpf janpf 2836 Feb 18 11:43 experimental.ipynb\n", - "-rwxr-xr-x 1 janpf janpf 87160 Oct 3 06:38 google_colab_demo.ipynb\n", - "drwxr-xr-x 3 janpf janpf 4096 Jan 24 07:46 tests\n", - "-rw-r--r-- 1 janpf janpf 216361 Feb 18 11:49 tutorial.ipynb\n", - "-rw-r--r-- 1 janpf janpf 1360 Dec 17 15:20 Untitled.ipynb\n", - "-rw-r--r-- 1 janpf janpf 10090 Oct 3 11:07 wasm_demo.ipynb\n" + "total 320\n", + "-rwxr-xr-x 1 janpf janpf 87160 Dec 15 08:39 google_colab_demo.ipynb\n", + "drwxr-xr-x 3 janpf janpf 4096 Feb 18 15:31 tests\n", + "-rw-r--r-- 1 janpf janpf 217824 Feb 18 15:31 tutorial.ipynb\n", + "-rw-r--r-- 1 janpf janpf 10090 Dec 15 08:39 wasm_demo.ipynb\n" ] } ], @@ -1549,13 +1563,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "/tmp/gonb_2ea0783b\n", - "total 13720\n", - "-rw-r--r-- 1 janpf janpf 1443 Feb 18 11:50 go.mod\n", - "-rwxr-xr-x 1 janpf janpf 14018568 Feb 18 11:50 gonb_2ea0783b\n", - "srwxr-xr-x 1 janpf janpf 0 Feb 18 11:49 gopls_socket\n", - "-rw-r--r-- 1 janpf janpf 15814 Feb 18 11:50 go.sum\n", - "-rw-r--r-- 1 janpf janpf 6160 Feb 18 11:50 main.go\n" + "/tmp/gonb_baac4f0c\n", + "total 13712\n", + "-rw-r--r-- 1 janpf janpf 1443 Mar 25 15:24 go.mod\n", + "-rwxr-xr-x 1 janpf janpf 14010304 Mar 25 15:25 gonb_baac4f0c\n", + "srwxr-xr-x 1 janpf janpf 0 Mar 25 15:23 gopls_socket\n", + "-rw-r--r-- 1 janpf janpf 15734 Mar 25 15:24 go.sum\n", + "-rw-r--r-- 1 janpf janpf 6160 Mar 25 15:25 main.go\n" ] } ], @@ -1650,10 +1664,10 @@ "--- PASS: TestIncr (0.00s)\n", "goos: linux\n", "goarch: amd64\n", - "pkg: gonb_2ea0783b\n", - "cpu: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz\n", + "pkg: gonb_baac4f0c\n", + "cpu: 12th Gen Intel(R) Core(TM) i9-12900K\n", "BenchmarkIncr\n", - "BenchmarkIncr-8 \t1000000000\t 0.5308 ns/op\n", + "BenchmarkIncr-24 \t1000000000\t 0.1643 ns/op\n", "PASS\n" ] } @@ -1727,9 +1741,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "module gonb_2ea0783b\n", + "module gonb_baac4f0c\n", "\n", - "go 1.22.0\n", + "go 1.22.1\n", "\n", "replace github.com/janpfeifer/gonb => /home/janpf/Projects/gonb\n", "\n", @@ -1738,10 +1752,10 @@ "\tgithub.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b\n", "\tgithub.com/benc-uk/gofract v0.0.0-20230120162050-a6f644f92fd6\n", "\tgithub.com/erkkah/margaid v0.1.1-0.20230128143048-d60b2efd2f5a\n", - "\tgithub.com/janpfeifer/gonb v0.9.5\n", - "\tgithub.com/schollz/progressbar/v3 v3.14.1\n", + "\tgithub.com/janpfeifer/gonb v0.9.6\n", + "\tgithub.com/schollz/progressbar/v3 v3.14.2\n", "\tgithub.com/stretchr/testify v1.8.1\n", - "\tgolang.org/x/exp v0.0.0-20240213143201-ec583247a57a\n", + "\tgolang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81\n", "\tgonum.org/v1/plot v0.14.0\n", ")\n", "\n", @@ -1759,10 +1773,10 @@ "\tgithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect\n", "\tgithub.com/pkg/errors v0.9.1 // indirect\n", "\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n", - "\tgithub.com/rivo/uniseg v0.4.4 // indirect\n", + "\tgithub.com/rivo/uniseg v0.4.7 // indirect\n", "\tgolang.org/x/image v0.11.0 // indirect\n", - "\tgolang.org/x/sys v0.16.0 // indirect\n", - "\tgolang.org/x/term v0.14.0 // indirect\n", + "\tgolang.org/x/sys v0.17.0 // indirect\n", + "\tgolang.org/x/term v0.17.0 // indirect\n", "\tgolang.org/x/text v0.14.0 // indirect\n", "\tgopkg.in/yaml.v2 v2.4.0 // indirect\n", "\tgopkg.in/yaml.v3 v3.0.1 // indirect\n", @@ -2151,7 +2165,7 @@ "name": "go", "nbconvert_exporter": "", "pygments_lexer": "", - "version": "go1.21.5" + "version": "go1.22.1" } }, "nbformat": 4, diff --git a/gonbui/javascript.go b/gonbui/javascript.go index 56f7b89..c687d4a 100644 --- a/gonbui/javascript.go +++ b/gonbui/javascript.go @@ -50,6 +50,29 @@ var loadAndRunTmpl = template.Must(template.New("load_and_run").Parse(` script.onload = script.onreadystatechange = runJSFn document.head.appendChild(script); })(); +(() => { + const src="{{.Src}}"; + var runJSFn = function() { + {{.RunJS}} + } + + var currentScripts = document.head.getElementsByTagName("script"); + for (const idx in currentScripts) { + let script = currentScripts[idx]; + if (script.src == src) { + runJSFn(); + return; + } + } + + var script = document.createElement("script"); +{{range $key, $value := .Attributes}} + script.{{$key}} = "{{$value}}"; +{{end}} + script.src = src; + script.onload = script.onreadystatechange = runJSFn + document.head.appendChild(script); +})(); `)) // LoadScriptModuleAndRun loads the given script module and, `onLoad`, runs the given code. @@ -65,7 +88,7 @@ var loadAndRunTmpl = template.Must(template.New("load_and_run").Parse(` // // gonbui.LoadScriptModuleAndRun( // "https://cdn.plot.ly/plotly-2.29.1.min.js", {"charset": "utf-8"}, -// "console.log('Plotly loaded.')); +// "console.log('Plotly loaded.')"); func LoadScriptModuleAndRun(src string, attributes map[string]string, runJS string) error { var buf bytes.Buffer data := struct { @@ -84,3 +107,80 @@ func LoadScriptModuleAndRun(src string, attributes map[string]string, runJS stri ScriptJavascript(js) return nil } + +var loadOrRequireAndRunTmpl = template.Must(template.New("load_or_required_and_run").Parse(` +(() => { + const src="{{.Src}}"; + var runJSFn = function(module) { + {{.RunJS}} + } + + if (typeof requirejs === "function") { + // Use RequireJS to load module. + requirejs.config({ + paths: { + '{{.ModuleName}}': 'https://cdn.plot.ly/plotly-2.29.1.min' + } + }); + require(['{{.ModuleName}}'], function({{.ModuleName}}) { + runJSFn({{.ModuleName}}) + }); + return + } + + var currentScripts = document.head.getElementsByTagName("script"); + for (const idx in currentScripts) { + let script = currentScripts[idx]; + if (script.src == src) { + runJSFn(null); + return; + } + } + + var script = document.createElement("script"); +{{range $key, $value := .Attributes}} + script.{{$key}} = "{{$value}}"; +{{end}} + script.src = src; + script.onload = script.onreadystatechange = function () { runJSFn(null); }; + document.head.appendChild(script); +})(); +`)) + +// LoadScriptOrRequireJSModuleAndRun is similar to [LoadScriptModuleAndRun] but it will use RequireJS if loaded, +// and it uses DisplayHtml instead -- which allows it to be included if the notebook is exported. +// +// In this version `runJS` will have `module` defined as the name of the module passed by `require` is RequireJS is +// available, or have it set to `null` otherwise. +// +// Notice while Jupyter notebook uses RequireJS, it hides in its context, so for the cells' HTML content, it is as +// if RequireJS is not available. But when the notebook is exported to HTML, RequireJS is available. +// LoadScriptOrRequireJSModuleAndRun will issue javascript code that dynamically handles both situations. +// +// Args: +// - `moduleName`: is the name to be module to be used if RequireJS is installed -- it is ignored if RequireJS is not +// available. +// - `src`: URL of the library to load. Used as the script source if loading the script the usual way, or used +// as the paths configuration option for RequireJS. +// - `attributes`: Extra attributes to use in the `", "UTF-8", js) + return nil +} diff --git a/gonbui/plotly/plotly.go b/gonbui/plotly/plotly.go index f2f3717..b964eb6 100644 --- a/gonbui/plotly/plotly.go +++ b/gonbui/plotly/plotly.go @@ -36,11 +36,14 @@ func DisplayFig(fig *grob.Fig) error { // Run in plotly. runJS := fmt.Sprintf(` + if (!module) { + module = window.Plotly; + } let data = JSON.parse('%s'); - Plotly.newPlot('%s', data); + module.newPlot('%s', data); `, figBytes, divId) - err = gonbui.LoadScriptModuleAndRun(PlotlySrc, map[string]string{"charset": "utf-8"}, runJS) + err = gonbui.LoadScriptOrRequireJSModuleAndRun("plotly", PlotlySrc, map[string]string{"charset": "utf-8"}, runJS) if err != nil { return err }