-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
elements.py
196 lines (152 loc) · 6.31 KB
/
elements.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2024, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
''' Generate various HTML elements from Bokeh render items.
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations
import logging # isort:skip
log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Standard library imports
from html import escape
from typing import TYPE_CHECKING, Any
## External imports
if TYPE_CHECKING:
from jinja2 import Template
# Bokeh imports
from ..core.json_encoder import serialize_json
from ..core.templates import (
DOC_JS,
FILE,
MACROS,
PLOT_DIV,
get_env,
)
from ..document.document import DEFAULT_TITLE
from ..settings import settings
from ..util.serialization import make_id
from .util import RenderItem
from .wrappers import wrap_in_onload, wrap_in_safely, wrap_in_script_tag
if TYPE_CHECKING:
from ..core.types import ID
from ..document.document import DocJson
from .bundle import Bundle
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------
__all__ = (
'div_for_render_item',
'html_page_for_render_items',
'script_for_render_items',
)
#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------
def div_for_render_item(item: RenderItem) -> str:
''' Render an HTML div for a Bokeh render item.
Args:
item (RenderItem):
the item to create a div for
Returns:
str
'''
return PLOT_DIV.render(doc=item, macros=MACROS)
def html_page_for_render_items(bundle: Bundle | tuple[str, str], docs_json: dict[ID, DocJson],
render_items: list[RenderItem], title: str, template: Template | str | None = None,
template_variables: dict[str, Any] = {}) -> str:
''' Render an HTML page from a template and Bokeh render items.
Args:
bundle (tuple):
a tuple containing (bokehjs, bokehcss)
docs_json (JSON-like):
Serialized Bokeh Document
render_items (RenderItems)
Specific items to render from the document and where
title (str or None)
A title for the HTML page. If None, DEFAULT_TITLE is used
template (str or Template or None, optional) :
A Template to be used for the HTML page. If None, FILE is used.
template_variables (dict, optional):
Any Additional variables to pass to the template
Returns:
str
'''
if title is None:
title = DEFAULT_TITLE
bokeh_js, bokeh_css = bundle
json_id = make_id()
json = escape(serialize_json(docs_json), quote=False)
json = wrap_in_script_tag(json, "application/json", json_id)
script = wrap_in_script_tag(script_for_render_items(json_id, render_items))
context = template_variables.copy()
context.update(dict(
title = title,
bokeh_js = bokeh_js,
bokeh_css = bokeh_css,
plot_script = json + script,
docs = render_items,
base = FILE,
macros = MACROS,
))
if len(render_items) == 1:
context["doc"] = context["docs"][0]
context["roots"] = context["doc"].roots
# XXX: backwards compatibility, remove for 1.0
context["plot_div"] = "\n".join(div_for_render_item(item) for item in render_items)
if template is None:
template = FILE
elif isinstance(template, str):
template = get_env().from_string("{% extends base %}\n" + template)
html = template.render(context)
return html
def script_for_render_items(docs_json_or_id: ID | dict[ID, DocJson], render_items: list[RenderItem],
app_path: str | None = None, absolute_url: str | None = None) -> str:
''' Render an script for Bokeh render items.
Args:
docs_json_or_id:
can be None
render_items (RenderItems) :
Specific items to render from the document and where
app_path (str, optional) :
absolute_url (Theme, optional) :
Returns:
str
'''
if isinstance(docs_json_or_id, str):
docs_json = f"document.getElementById('{docs_json_or_id}').textContent"
else:
# XXX: encodes &, <, > and ', but not ". This is because " is used a lot in JSON,
# and encoding it would significantly increase size of generated files. Doing so
# is safe, because " in strings was already encoded by JSON, and the semi-encoded
# JSON string is included in JavaScript in single quotes.
docs_json = serialize_json(docs_json_or_id, pretty=False) # JSON string
docs_json = escape(docs_json, quote=False) # make HTML-safe
docs_json = docs_json.replace("'", "'") # remove single quotes
docs_json = docs_json.replace("\\", "\\\\") # double encode escapes
docs_json = "'" + docs_json + "'" # JS string
js = DOC_JS.render(
docs_json=docs_json,
render_items=serialize_json([ item.to_json() for item in render_items ], pretty=False),
app_path=app_path,
absolute_url=absolute_url,
)
if not settings.dev:
js = wrap_in_safely(js)
return wrap_in_onload(js)
#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------