|
| 1 | +from __future__ import annotations |
| 2 | + |
1 | 3 | import json
|
2 | 4 | import uuid
|
3 |
| -from dataclasses import dataclass |
4 |
| -from typing import Any, Dict, Optional |
| 5 | +from typing import Any, Dict |
5 | 6 |
|
6 | 7 | import jinja2
|
7 | 8 |
|
8 | 9 | from gosling.plugin_registry import PluginRegistry
|
9 | 10 | from gosling.schema import SCHEMA_VERSION, THEMES
|
10 | 11 |
|
11 |
| -# TODO: This is kind of a mess. Gosling.js can be very finky with its |
12 |
| -# peer dependencies in various Jupyter-like environments. This is a |
13 |
| -# hacky way to get things working in JupyterLab, Jupyter Notebook, |
14 |
| -# VSCode, and Colab consistently. |
| 12 | +# TODO: Ideally we could use a single import but this seems to work ok. |
15 | 13 | HTML_TEMPLATE = jinja2.Template(
|
16 | 14 | """
|
17 | 15 | <!DOCTYPE html>
|
18 | 16 | <html>
|
19 | 17 | <head>
|
20 | 18 | <style>.error { color: red; }</style>
|
21 |
| - <link rel="stylesheet" href="{{ higlass_css_url }}"> |
22 | 19 | </head>
|
23 | 20 | <body>
|
24 | 21 | <div id="{{ output_div }}"></div>
|
25 | 22 | <script type="module">
|
26 |
| - async function loadScript(src) { |
27 |
| - return new Promise(resolve => { |
28 |
| - const script = document.createElement('script'); |
29 |
| - script.onload = resolve; |
30 |
| - script.src = src; |
31 |
| - script.async = false; |
32 |
| - document.head.appendChild(script); |
33 |
| - }); |
34 |
| - } |
35 |
| - async function loadGosling() { |
36 |
| - // Manually load scripts from window namespace since requirejs might not be |
37 |
| - // available in all browser environments. |
38 |
| - // https://github.com/DanielHreben/requirejs-toggle |
39 |
| - if (!window.gosling) { |
40 |
| - // https://github.com/DanielHreben/requirejs-toggle |
41 |
| - window.__requirejsToggleBackup = { |
42 |
| - define: window.define, |
43 |
| - require: window.require, |
44 |
| - requirejs: window.requirejs, |
45 |
| - }; |
46 |
| - for (const field of Object.keys(window.__requirejsToggleBackup)) { |
47 |
| - window[field] = undefined; |
48 |
| - } |
49 |
| - // load dependencies sequentially |
50 |
| - const sources = [ |
51 |
| - "{{ react_url }}", |
52 |
| - "{{ react_dom_url }}", |
53 |
| - "{{ pixijs_url }}", |
54 |
| - "{{ higlass_js_url }}", |
55 |
| - "{{ gosling_url }}", |
56 |
| - ]; |
57 |
| - for (const src of sources) await loadScript(src); |
58 |
| - // restore requirejs after scripts have loaded |
59 |
| - Object.assign(window, window.__requirejsToggleBackup); |
60 |
| - delete window.__requirejsToggleBackup; |
61 |
| - } |
62 |
| - return window.gosling; |
63 |
| - }; |
64 |
| - var el = document.getElementById('{{ output_div }}'); |
65 |
| - var spec = {{ spec }}; |
66 |
| - var opt = {{ embed_options }}; |
67 |
| - loadGosling() |
68 |
| - .then(gosling => gosling.embed(el, spec, opt)) |
69 |
| - .catch(err => { |
70 |
| - el.innerHTML = `\ |
71 |
| -<div class="error"> |
72 |
| - <p>JavaScript Error: ${error.message}</p> |
73 |
| - <p>This usually means there's a typo in your Gosling specification. See the javascript console for the full traceback.</p> |
74 |
| -</div>`; |
75 |
| - throw error; |
76 |
| - }); |
| 23 | + import { importWithMap } from "https://unpkg.com/dynamic-importmap@0.1.0"; |
| 24 | + let importMap = {{ import_map }}; |
| 25 | + let gosling = await importWithMap("gosling.js", importMap); |
| 26 | + let el = document.getElementById('{{ output_div }}'); |
| 27 | + let spec = {{ spec }}; |
| 28 | + let opt = {{ embed_options }}; |
| 29 | + gosling.embed(el, spec, opt).catch((err) => { |
| 30 | + el.innerHTML = `\ |
| 31 | + <div class="error"> |
| 32 | + <p>JavaScript Error: ${error.message}</p> |
| 33 | + <p>This usually means there's a typo in your Gosling specification. See the javascript console for the full traceback.</p> |
| 34 | + </div>`; |
| 35 | + throw error; |
| 36 | + }); |
77 | 37 | </script>
|
78 | 38 | </body>
|
79 | 39 | </html>
|
|
83 | 43 | GoslingSpec = Dict[str, Any]
|
84 | 44 |
|
85 | 45 |
|
86 |
| -@dataclass |
87 |
| -class GoslingBundle: |
88 |
| - react: str |
89 |
| - react_dom: str |
90 |
| - pixijs: str |
91 |
| - higlass_js: str |
92 |
| - higlass_css: str |
93 |
| - gosling: str |
94 |
| - |
95 |
| - |
96 |
| -def get_display_dependencies( |
| 46 | +def get_gosling_import_map( |
97 | 47 | gosling_version: str = SCHEMA_VERSION.lstrip("v"),
|
98 |
| - higlass_version: str = "~1.12", |
99 |
| - react_version: str = "17", |
| 48 | + higlass_version: str = "1.13", |
| 49 | + react_version: str = "18", |
100 | 50 | pixijs_version: str = "6",
|
101 |
| - base_url: str = "https://unpkg.com", |
102 |
| -) -> GoslingBundle: |
103 |
| - return GoslingBundle( |
104 |
| - react=f"{ base_url }/react@{ react_version }/umd/react.production.min.js", |
105 |
| - react_dom=f"{ base_url }/react-dom@{ react_version }/umd/react-dom.production.min.js", |
106 |
| - pixijs=f"{ base_url }/pixi.js@{ pixijs_version }/dist/browser/pixi.min.js", |
107 |
| - higlass_js=f"{ base_url }/higlass@{ higlass_version }/dist/hglib.js", |
108 |
| - higlass_css=f"{ base_url }/higlass@{ higlass_version }/dist/hglib.css", |
109 |
| - gosling=f"{ base_url }/gosling.js@{ gosling_version }/dist/gosling.js", |
110 |
| - ) |
| 51 | +) -> dict: |
| 52 | + return { |
| 53 | + "imports": { |
| 54 | + "react": f"https://esm.sh/react@{react_version}", |
| 55 | + "react-dom": f"https://esm.sh/react-dom@{react_version}", |
| 56 | + "pixi": f"https://esm.sh/pixi.js@{pixijs_version}", |
| 57 | + "higlass": f"https://esm.sh/higlass@{higlass_version}?external=react,react-dom,pixi", |
| 58 | + "gosling.js": f"https://esm.sh/gosling.js@{gosling_version}?external=react,react-dom,pixi,higlass", |
| 59 | + } |
| 60 | + } |
111 | 61 |
|
112 | 62 |
|
113 | 63 | def spec_to_html(
|
114 | 64 | spec: GoslingSpec,
|
115 | 65 | output_div: str = "vis",
|
116 |
| - embed_options: Optional[Dict[str, Any]] = None, |
| 66 | + embed_options: Dict[str, Any] | None = None, |
117 | 67 | **kwargs,
|
118 | 68 | ):
|
119 | 69 | embed_options = embed_options or dict(padding=0, theme=themes.get())
|
120 |
| - deps = get_display_dependencies(**kwargs) |
| 70 | + deps = get_gosling_import_map(**kwargs) |
121 | 71 |
|
122 | 72 | return HTML_TEMPLATE.render(
|
123 | 73 | spec=json.dumps(spec),
|
124 | 74 | embed_options=json.dumps(embed_options),
|
125 | 75 | output_div=output_div,
|
126 |
| - react_url=deps.react, |
127 |
| - react_dom_url=deps.react_dom, |
128 |
| - pixijs_url=deps.pixijs, |
129 |
| - higlass_js_url=deps.higlass_js, |
130 |
| - higlass_css_url=deps.higlass_css, |
131 |
| - gosling_url=deps.gosling, |
| 76 | + import_map=json.dumps(deps), |
132 | 77 | )
|
133 | 78 |
|
| 79 | + |
134 | 80 | class Renderer:
|
135 | 81 | def __init__(self, output_div: str = "jupyter-gosling-{}", **kwargs: Any):
|
136 | 82 | self._output_div = output_div
|
|
0 commit comments