@@ -19,10 +19,27 @@ def __init__(self) -> None:
1919 nvl_entry_point = resource_folder / "nvl_entrypoint"
2020
2121 js_path = nvl_entry_point / "base.js"
22-
2322 with js_path .open ("r" , encoding = "utf-8" ) as file :
2423 self .library_code = file .read ()
2524
25+ styles_path = nvl_entry_point / "styles.css"
26+ with styles_path .open ("r" , encoding = "utf-8" ) as file :
27+ self .styles = file .read ()
28+
29+ icons = resource_folder / "icons"
30+
31+ zoom_in_path = icons / "zoom-in.svg"
32+ with zoom_in_path .open ("r" , encoding = "utf-8" ) as file :
33+ self .zoom_in_svg = file .read ()
34+
35+ zoom_out_path = icons / "zoom-out.svg"
36+ with zoom_out_path .open ("r" , encoding = "utf-8" ) as file :
37+ self .zoom_out_svg = file .read ()
38+
39+ screenshot_path = icons / "screenshot.svg"
40+ with screenshot_path .open ("r" , encoding = "utf-8" ) as file :
41+ self .screenshot_svg = file .read ()
42+
2643 def unsupported_field_type_error (self , e : TypeError , entity : str ) -> Exception :
2744 if "not JSON serializable" in str (e ):
2845 return ValueError (f"A field of a { entity } object is not supported: { str (e )} " )
@@ -51,13 +68,19 @@ def render(
5168
5269 if show_hover_tooltip :
5370 hover_element = f"document.getElementById('{ container_id } -tooltip')"
54- hover_div = f'<div id="{ container_id } -tooltip" style="width: 20%; min-width: 100px; max-width: 600px; max-height: 80%; position: absolute; z-index: 2147483647; right: 0; bottom: 0; background: white; display: none; border: solid; border-color: #BBBEC3; border-width: 0.5px; padding: 0.8rem; border-radius: 8px; margin-bottom: 1rem; margin-right: 0.5rem; filter: drop-shadow(0 4px 8px rgba(26,27,29,0.12)); font-family: PublicSans; color: #4D5157; font-size: 14px "></div>'
71+ hover_div = f'<div id="{ container_id } -tooltip" class="tooltip" style=" display: none;"></div>'
5572 else :
5673 hover_element = "null"
5774 hover_div = ""
5875
76+ # Using a different varname for every instance, so that a notebook
77+ # can use several instances without unwanted interactions.
78+ # The first part of the UUID should be "unique enough" in this context.
79+ nvl_varname = "graph_" + container_id .split ("-" )[0 ]
80+ download_name = nvl_varname + ".png"
81+
5982 js_code = f"""
60- var myNvl = new NVLBase.NVL(
83+ var { nvl_varname } = new NVLBase.NVL(
6184 document.getElementById('{ container_id } '),
6285 { hover_element } ,
6386 { nodes_json } ,
@@ -66,12 +89,37 @@ def render(
6689 );
6790 """
6891 full_code = self .library_code + js_code
92+
6993 html_output = f"""
94+ <style>
95+ { self .styles }
96+ </style>
7097 <div id="{ container_id } " style="width: { width } ; height: { height } ; position: relative;">
98+ <div style="position: absolute; z-index: 2147483647; right: 0; top: 0; padding: 1rem">
99+ <button type="button" title="Save as PNG" onclick="{ nvl_varname } .nvl.saveToFile({{ filename: '{ download_name } ' }})" class="icon">
100+ { self .screenshot_svg }
101+ </button>
102+ <button type="button" title="Zoom in" onclick="{ nvl_varname } .nvl.setZoom({ nvl_varname } .nvl.getScale() + 0.5)" class="icon">
103+ { self .zoom_in_svg }
104+ </button>
105+ <button type="button" title="Zoom out" onclick="{ nvl_varname } .nvl.setZoom({ nvl_varname } .nvl.getScale() - 0.5)" class="icon">
106+ { self .zoom_out_svg }
107+ </button>
108+ </div>
71109 { hover_div }
72110 </div>
111+
73112 <script>
113+ getTheme = () => {{
114+ const backgroundColorString = window.getComputedStyle(document.body, null).getPropertyValue('background-color')
115+ const colorsArray = backgroundColorString.match(/\d+/g);
116+ const brightness = Number(colorsArray[0]) * 0.2126 + Number(colorsArray[1]) * 0.7152 + Number(colorsArray[2]) * 0.0722
117+ return brightness < 128 ? "dark" : "light"
118+ }}
119+ document.documentElement.className = getTheme()
120+
74121 { full_code }
75122 </script>
76123 """
124+
77125 return HTML (html_output ) # type: ignore[no-untyped-call]
0 commit comments