Skip to content

Commit d3cd0d9

Browse files
committed
Datalore/Colab: deprecate load_lets_plot_js(), replace it with LetsPlot.setup_html()
- support simple html page output. - make LetsPlot.setup_html() mandatory.
1 parent 48f442e commit d3cd0d9

File tree

11 files changed

+243
-105
lines changed

11 files changed

+243
-105
lines changed

python-package/lets_plot/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,21 @@
1414

1515
__all__ = (plot.__all__ +
1616
frontend_context.__all__ +
17-
['LetsPlotSettings'])
17+
['LetsPlotSettings', 'LetsPlot'])
18+
19+
from .frontend_context import _configuration as cfg
20+
21+
class LetsPlot:
22+
@classmethod
23+
def setup_html(cls, isolated_frame: bool = None, offline: bool = None) -> None:
24+
"""
25+
Configure HTML frontend context
26+
:param settings:
27+
:return:
28+
"""
29+
if not (isinstance(isolated_frame, bool) or isolated_frame is None):
30+
raise ValueError("'isolated' argument is not boolean: {}".format(type(isolated_frame)))
31+
if not (isinstance(offline, bool) or offline is None):
32+
raise ValueError("'offline' argument is not boolean: {}".format(type(offline)))
33+
34+
cfg._setup_html_context(isolated_frame, offline)

python-package/lets_plot/_global_settings.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,6 @@ class LetsPlotSettings:
2222
def apply(cls, settings: Dict):
2323
_settings.update(settings)
2424

25-
# @classmethod
26-
# def init_frontend(cls, settings=None):
27-
# if settings is None:
28-
# settings = {}
29-
#
30-
# LetsPlotSettings.apply(settings)
31-
# # ToDo: inject js to the notebook
32-
# raise NotImplementedError
33-
3425

3526
def _is_production() -> bool:
3627
return 'dev' not in __version__

python-package/lets_plot/frontend_context/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
44
#
55

6-
from .frontend_context import *
6+
from ._configuration import *
77

8-
__all__ = frontend_context.__all__
8+
__all__ = _configuration.__all__
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#
2+
# Copyright (c) 2019. JetBrains s.r.o.
3+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4+
#
5+
from typing import Dict, Any
6+
7+
from ._frontend_ctx import FrontendContext
8+
from ._html_contexts import _create_html_frontend_context
9+
from .._global_settings import _get_global_bool
10+
from ..plot.core import PlotSpec
11+
from ..plot.plot import GGBunch
12+
13+
__all__ = [
14+
'load_lets_plot_js' # deprecated
15+
]
16+
17+
_frontend_contexts: Dict[str, FrontendContext] = {}
18+
_default_mimetype = "text/html" # Just HTML as yet
19+
20+
21+
def _setup_html_context(isolated_frame: bool = None, offline: bool = None) -> None:
22+
"""
23+
24+
:param isolated_frame:
25+
:param embed:
26+
:return:
27+
"""
28+
embed = offline if offline is not None else _get_global_bool('offline')
29+
ctx = _create_html_frontend_context(isolated_frame, embed)
30+
ctx.configure(verbose=True)
31+
_frontend_contexts['html'] = ctx
32+
33+
34+
def load_lets_plot_js(embed: bool = None):
35+
"""
36+
Deprecated since v.1.3: instead use LetsPlot.setup_html()
37+
38+
Loads Lets-Plot javascript library into current frontend context.
39+
40+
Parameters
41+
----------
42+
embed : bool, optional
43+
True - embed JS which is bundled with Lets-Plot PyPI package. This is useful for off-line notebooks.
44+
False - load JS from CDN.
45+
default - load JS from CDN.
46+
"""
47+
try:
48+
from IPython.display import display_html
49+
display_html("""\
50+
<div style="color:darkred;">
51+
Method `load_lets_plot_js()` is deprecated since v.1.3 and will be removed soon.<br>
52+
Try to use `LetsPlot.setup_html()` instead.
53+
</div>
54+
""", raw=True)
55+
except ImportError:
56+
pass
57+
58+
_setup_html_context(None, embed)
59+
60+
61+
def _display_plot(plot_spec: Any):
62+
"""
63+
Draw plot or `bunch` of plots in the current frontend context
64+
:param plot_spec: PlotSpec or GGBunch object
65+
"""
66+
if not (isinstance(plot_spec, PlotSpec) or isinstance(plot_spec, GGBunch)):
67+
raise ValueError("PlotSpec or GGBunch expected but was: {}".format(type(plot_spec)))
68+
69+
if _default_mimetype == "text/html":
70+
plot_html = _as_html(plot_spec.as_dict())
71+
try:
72+
from IPython.display import display_html
73+
display_html(plot_html, raw=True)
74+
return
75+
except ImportError:
76+
pass
77+
78+
# ToDo: show HTML is brawser window
79+
return
80+
81+
# fallback plain text
82+
print(plot_spec.as_dict())
83+
84+
85+
def _as_html(plot_spec: Dict) -> str:
86+
"""
87+
:param plot_spec: dict
88+
"""
89+
if 'html' not in _frontend_contexts:
90+
return """\
91+
<div style="color:darkred;">
92+
Lets-plot `html` is not configured.<br>
93+
Try to use `LetsPlot.setup_html()` before first occurrence of plot.
94+
</div>
95+
"""
96+
97+
return _frontend_contexts['html'].as_str(plot_spec)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#
2+
# Copyright (c) 2019. JetBrains s.r.o.
3+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4+
#
5+
from typing import Dict
6+
7+
8+
class FrontendContext:
9+
def configure(self, verbose: bool):
10+
pass
11+
12+
def as_str(self, plot_spec: Dict) -> str:
13+
pass
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Copyright (c) 2020. JetBrains s.r.o.
2+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
3+
4+
import os
5+
6+
from ._frontend_ctx import FrontendContext
7+
from ._static_html_page_ctx import StaticHtmlPageContext
8+
from ._jupyter_notebook_ctx import JupyterNotebookContext
9+
10+
11+
def _create_html_frontend_context(isolated_frame: bool = None, offline: bool = None) -> FrontendContext:
12+
"""
13+
14+
:param isolated_frame:
15+
:param offline:
16+
:return:
17+
"""
18+
if isolated_frame is None:
19+
# ToDo: check env var first
20+
21+
isolated_frame = _detect_isolated_frame()
22+
23+
if isolated_frame:
24+
return StaticHtmlPageContext(offline)
25+
else:
26+
return JupyterNotebookContext(offline)
27+
28+
29+
def _detect_isolated_frame() -> bool:
30+
if not _is_IPython_display():
31+
return True # isolated HTML page to show somehow
32+
33+
# Most online notebook platforms are showing cell output in iframe and require
34+
# a self-contained HTML which includes both:
35+
# - the script loading JS library and
36+
# - the script that uses this JS lib to create plot.
37+
38+
# Try to detect the platform.
39+
try:
40+
import google.colab
41+
return True # Colab -> iframe
42+
except ImportError:
43+
pass
44+
45+
if os.path.exists("/kaggle/input"):
46+
return True # Kaggle -> iframe
47+
48+
# Check if we're running in an Azure Notebook
49+
if "AZURE_NOTEBOOKS_HOST" in os.environ:
50+
return True # Azure Notebook -> iframe
51+
52+
# ToDo: other platforms: vscode, nteract, cocalc
53+
54+
try:
55+
shell = get_ipython().__class__.__name__
56+
if shell == 'ZMQInteractiveShell':
57+
return False # Jupyter notebook or qtconsole -> load JS librarty once per notebook
58+
elif shell == 'TerminalInteractiveShell':
59+
return True # Terminal running IPython -> an isolated HTML page to show somehow
60+
else:
61+
return True # Other type (?)
62+
except NameError:
63+
return True # some other env (even standard Python interpreter) -> an isolated HTML page to show somehow
64+
65+
66+
def _is_IPython_display() -> bool:
67+
try:
68+
from IPython.display import display_html
69+
return True
70+
except ImportError:
71+
return False

python-package/lets_plot/frontend_context/jupyter_notebook.py renamed to python-package/lets_plot/frontend_context/_jupyter_notebook_ctx.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,17 @@
1111
# noinspection PyPackageRequirements
1212
from IPython.display import display_html
1313

14-
from .frontend_context import FrontendContext
15-
from .._global_settings import _get_global_str, _has_global_value, _is_production
14+
from ._frontend_ctx import FrontendContext
1615
from .. import _kbridge as kbr
16+
from .._global_settings import _get_global_str, _has_global_value, _is_production
1717
from .._version import __version__
1818

1919

2020
class JupyterNotebookContext(FrontendContext):
2121

22-
def __init__(self, connected: bool) -> None:
22+
def __init__(self, offline: bool) -> None:
2323
super().__init__()
24-
self.connected = connected
25-
26-
def as_str(self, plot_spec: Dict) -> str:
27-
return kbr._generate_dynamic_display_html(plot_spec)
28-
29-
def _undef_modules_script(self) -> str:
30-
pass
24+
self.connected = not offline
3125

3226
def configure(self, verbose: bool):
3327
if self.connected:
@@ -37,6 +31,9 @@ def configure(self, verbose: bool):
3731
# noinspection PyTypeChecker
3832
display_html(self._configure_embedded_script(verbose), raw=True)
3933

34+
def as_str(self, plot_spec: Dict) -> str:
35+
return kbr._generate_dynamic_display_html(plot_spec)
36+
4037
@staticmethod
4138
def _configure_connected_script(verbose: bool) -> str:
4239
base_url = _get_global_str("js_base_url")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#
2+
# Copyright (c) 2019. JetBrains s.r.o.
3+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4+
#
5+
from typing import Dict
6+
7+
from ._frontend_ctx import FrontendContext
8+
from .. import _kbridge as kbr
9+
from .._global_settings import _is_production
10+
from .._version import __version__
11+
12+
13+
# noinspection PyPackageRequirements
14+
15+
16+
class StaticHtmlPageContext(FrontendContext):
17+
18+
def __init__(self, offline: bool) -> None:
19+
super().__init__()
20+
self.connected = not offline
21+
22+
def configure(self, verbose: bool):
23+
# Nothing here because the entire html page is created per each cell output.
24+
if not self.connected:
25+
print("WARN: Embedding Lets-Plot JS library for offline usage is not supported.")
26+
27+
def as_str(self, plot_spec: Dict) -> str:
28+
# embedding js is not supported (yet) in this context,
29+
# replace `dev` version with the `latest`.
30+
version = __version__ if _is_production() else "latest"
31+
return kbr._generate_static_html_page(plot_spec, version, iframe=False)

python-package/lets_plot/frontend_context/frontend_context.py

Lines changed: 0 additions & 79 deletions
This file was deleted.

python-package/lets_plot/plot/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,14 @@ def _repr_html_(self):
237237
"""
238238
Special method discovered and invoked by IPython.display.display
239239
"""
240-
from ..frontend_context.frontend_context import _as_html
240+
from ..frontend_context._configuration import _as_html
241241
return _as_html(self.as_dict())
242242

243243
def show(self):
244244
"""
245245
Draw plot
246246
"""
247-
from ..frontend_context.frontend_context import _display_plot
247+
from ..frontend_context._configuration import _display_plot
248248
_display_plot(self)
249249

250250

0 commit comments

Comments
 (0)