1414from typing import Tuple , List
1515
1616import uuid
17+ import base64
1718import dash
1819import plotly .graph_objects as go
1920from dash import Dash
2728from .utils import is_figure , is_fr
2829
2930
30- class JupyterDashCustomOutput (JupyterDash ):
31+ class JupyterDashPersistentInlineOutput (JupyterDash ):
32+ """ Extension of the JupyterDash class to support the custom inline output.
33+
34+ Specifically we embed a div in the notebook to display the figure inline.
35+ - In this div the figure is shown as an iframe when the server (of the dash app)
36+ is alive.
37+ - In this div the figure is shown as an image when the server (of the dash app)
38+ is dead.
39+
40+ As the HTML & javascript code is embedded in the notebook output, which is loaded
41+ each time you open the notebook, the figure is always displayed (either as iframe
42+ or just an image).
43+ Hence, this extension enables to maintain always an output in the notebook.
44+
45+ Note: this subclass is only used when the mode is set to `"inline_persistent"` in
46+ the `FigureResampler.show_dash` method. However, the mode should be passed as
47+ `"inline"` since this subclass overwrites the inline behavior.
48+ """
3149
3250 def __init__ (self , * args , ** kwargs ):
3351 super ().__init__ (* args , ** kwargs )
@@ -41,26 +59,31 @@ def broadcast_alive():
4159 return "Alive"
4260
4361 def _display_inline_output (self , dashboard_url , width , height ):
44- # TODO: width en height gebruiken
45- # TODO: text displayen in de output :check:
62+ """Display the dash app persistent inline in the notebook.
63+
64+ The figure is displayed as an iframe in the notebook if the server is reachable,
65+ otherwise as an image.
66+ """
4667 # TODO: check if in case of crash wel error gelogged wordt
47- import base64
68+ # TODO: add option to opt out of this
4869 from IPython .display import display
4970
5071 # Get the image from the dashboard and encode it as base64
51- fig = self .layout .children [0 ].figure
72+ fig = self .layout .children [0 ].figure # is stored there in the show_dash method
5273 fig_base64 = base64 .b64encode (fig .to_image ("png" )).decode ("utf8" )
5374
75+ # The unique id of this app
76+ # This id is used to couple the output in the notebook with this app
77+ # A fetch request is performed to the _is_alive_{uid} endpoint to check if the
78+ # app is still alive.
5479 uid = self ._uid
5580
56- print ("width: " + str (width ))
57- print ("height: " + str (height ))
58-
81+ # The html (& javascript) code to display the app / figure
5982 display (
6083 {
6184 "text/html" :
6285 f"""
63- <div id='PR_div__{ uid } ' class='container' ></div>
86+ <div id='PR_div__{ uid } '></div>
6487 <script type='text/javascript'>
6588 """ +
6689 """
@@ -81,7 +104,7 @@ def _display_inline_output(self, dashboard_url, width, height):
81104 const controller = new AbortController();
82105 const signal = controller.signal;
83106
84- return fetch(url + is_alive_suffix, {signal: signal})
107+ return fetch(url + is_alive_suffix, {method: 'GET', signal: signal})
85108 .then(response => response.text())
86109 .then(data =>
87110 {
@@ -93,36 +116,42 @@ def _display_inline_output(self, dashboard_url, width, height):
93116 console.log("Server is dead");
94117 imageOutput(pr_div, pr_img_src);
95118 }
96- }
119+ }
97120 )
98121 .catch(error => {
99122 console.log("Server is unreachable");
100123 imageOutput(pr_div, pr_img_src);
101124 })
102125 }
103126
104- setOutput(500 );
127+ setOutput(350 );
105128
106129 function imageOutput(element, pr_img_src) {
107130 console.log('Setting image');
108-
109- var pr_text = document.createElement("p");
110- pr_text.setAttribute("style", 'color:red');
111- pr_text.innerHTML = 'Server unreachable - using image instead';
112- element.appendChild(pr_text);
113-
114131 var pr_img = document.createElement("img");
115132 pr_img.setAttribute("src", pr_img_src)
133+ pr_img.setAttribute("alt", 'Server unreachable - using image instead');
134+ """ +
135+ f"""
136+ pr_img.setAttribute("width", '{ width } ');
137+ pr_img.setAttribute("height", '{ height } ');
138+ """ +
139+ """
116140 element.appendChild(pr_img);
117141 }
118142
119143 function iframeOutput(element, url) {
120144 console.log('Setting iframe');
121145 var pr_iframe = document.createElement("iframe");
122146 pr_iframe.setAttribute("src", url);
123- pr_iframe.setAttribute("width", '100%');
124- pr_iframe.setAttribute("height", '468px');
125147 pr_iframe.setAttribute("frameborder", '0');
148+ pr_iframe.setAttribute("allowfullscreen", '');
149+ """ +
150+ f"""
151+ pr_iframe.setAttribute("width", '{ width } ');
152+ pr_iframe.setAttribute("height", '{ height } ');
153+ """ +
154+ """
126155 element.appendChild(pr_iframe);
127156 }
128157 </script>
@@ -131,6 +160,9 @@ def _display_inline_output(self, dashboard_url, width, height):
131160 )
132161
133162 def _display_in_jupyter (self , dashboard_url , port , mode , width , height ):
163+ """Override the display method to display to retain some output when displaying
164+ inline in jupyter.
165+ """
134166 if mode == "inline" :
135167 self ._display_inline_output (dashboard_url , width , height )
136168 else :
@@ -296,6 +328,11 @@ def show_dash(
296328 web browser.
297329 * ``"inline"``: The app will be displayed inline in the notebook output
298330 cell in an iframe.
331+ * ``"inline_persistent"``: The app will be displayed inline in the
332+ notebook output cell in an iframe, if the app is not reachable a static
333+ image of the figure is shown. Hence this is a persistent version of the
334+ ``"inline"`` mode, allowing users to see a static figure in other
335+ environments, browsers, etc.
299336 * ``"jupyterlab"``: The app will be displayed in a dedicated tab in the
300337 JupyterLab interface. Requires JupyterLab and the ``jupyterlab-dash``
301338 extension.
@@ -321,7 +358,14 @@ def show_dash(
321358 graph_properties = {} if graph_properties is None else graph_properties
322359 assert "config" not in graph_properties .keys () # There is a param for config
323360 # 1. Construct the Dash app layout
324- app = JupyterDashCustomOutput ("local_app" )
361+ if mode is "inline_persistent" :
362+ # Inline persistent mode: we display a static image of the figure when the
363+ # app is not reachable
364+ # Note: this is the "inline" behavior of JupyterDashInlinePersistentOutput
365+ mode = "inline"
366+ app = JupyterDashPersistentInlineOutput ("local_app" )
367+ else :
368+ app = JupyterDash ("local_app" )
325369 app .layout = dash .html .Div (
326370 [
327371 dash .dcc .Graph (
0 commit comments