bokeh.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import asyncio
  2. import re
  3. from collections.abc import Sequence
  4. from pywebio.output import *
  5. from pywebio.session import get_info
  6. requirejs_config_tpl = """
  7. <script type="text/javascript">
  8. require.config({
  9. paths: {
  10. "bokeh": "https://cdn.jsdelivr.net/npm/@bokeh/bokehjs@__version__/build/js/bokeh.min",
  11. "bokeh-widgets": "https://cdn.jsdelivr.net/npm/@bokeh/bokehjs@__version__/build/js/bokeh-widgets.min",
  12. "bokeh-tables": "https://cdn.jsdelivr.net/npm/@bokeh/bokehjs@__version__/build/js/bokeh-tables.min",
  13. "bokeh-api": "https://cdn.jsdelivr.net/npm/@bokeh/bokehjs@__version__/build/js/bokeh-api.min",
  14. "bokeh-gl": "https://cdn.jsdelivr.net/npm/@bokeh/bokehjs@__version__/build/js/bokeh-gl.min",
  15. },
  16. shim: {
  17. 'bokeh': {
  18. exports: 'Bokeh'
  19. },
  20. 'bokeh-widgets': {
  21. exports: '_',
  22. deps: ['bokeh'],
  23. },
  24. 'bokeh-tables': {
  25. exports: '_',
  26. deps: ['bokeh'],
  27. },
  28. 'bokeh-api': {
  29. exports: '_',
  30. deps: ['bokeh'],
  31. },
  32. 'bokeh-gl': {
  33. exports: '_',
  34. deps: ['bokeh'],
  35. },
  36. }
  37. });
  38. </script>
  39. """
  40. requirejs_tpl = """
  41. %s
  42. <script type="text/javascript">
  43. requirejs(['bokeh', 'bokeh-widgets', 'bokeh-tables'], function(Bokeh) {
  44. %s
  45. });
  46. </script>
  47. """
  48. def load_notebook(resources=None, verbose=False, hide_banner=False, load_timeout=5000):
  49. """加载 Bokeh 资源
  50. :param resources: 目前不支持自定义静态资源的链接
  51. :param verbose: 开启 Bokeh 日志 并显示 Bokeh 加载标签
  52. :param hide_banner: 不支持
  53. :param load_timeout: 不支持
  54. :return: None
  55. """
  56. from bokeh import __version__
  57. from bokeh.util.serialization import make_id
  58. js_gists = ["console.log('Load BokehJS complete.')"]
  59. html = requirejs_config_tpl.replace('__version__', __version__)
  60. if verbose:
  61. element_id = make_id()
  62. html += """
  63. <div class="bk-root">
  64. <a href="https://bokeh.org" target="_blank" class="bk-logo bk-logo-small bk-logo-notebook"></a>
  65. <span id="{element_id}" style="font-family: Helvetica, Arial, sans-serif;font-size: 13px;">Loading BokehJS ...</span>
  66. </div>
  67. """.format(element_id=element_id)
  68. js_gists.append(
  69. "document.getElementById({element_id}).innerHTML = 'Load BokehJS complete.'".format(element_id=element_id))
  70. js_gists.append('Bokeh.set_log_level("info");')
  71. js_gists.append("console.log('Set bokeh log level to INFO because you set `output_notebook(verbose=True)`')")
  72. put_html(requirejs_tpl % (html, '\n'.join(js_gists)), sanitize=False)
  73. def show_doc(obj, state, notebook_handle):
  74. """Show a document of Bokeh
  75. :param obj:
  76. :param state:
  77. :param notebook_handle: 不支持
  78. :return:
  79. """
  80. from bokeh.embed import components
  81. script, div = components(obj, wrap_script=False)
  82. if isinstance(obj, Sequence):
  83. div = '\n'.join(div)
  84. elif isinstance(obj, dict):
  85. div = '\n'.join(div[k] for k in obj.keys())
  86. put_html(requirejs_tpl % (div, script), sanitize=False)
  87. def show_app(app, state, notebook_url, port=0, **kw):
  88. """Show Bokeh applications
  89. :param app: A Bokeh Application to embed in PyWebIO.
  90. :param state: ** Unused **
  91. :param notebook_url: ** Unused **
  92. :param port: Bokeh Server 端口
  93. :param kw: 传给 Bokeh Server 的额外参数
  94. """
  95. from bokeh.server.server import Server
  96. from bokeh.io.notebook import _origin_url, uuid4, curstate, _server_url
  97. from pywebio.platform.tornado import ioloop
  98. loop = ioloop()
  99. if loop is None:
  100. toast("Currently only supports showing bokeh application in Tornado backend",
  101. color='error', duration=0)
  102. return
  103. loop.make_current()
  104. asyncio.set_event_loop(loop.asyncio_loop)
  105. # loop = IOLoop.current()
  106. info = get_info()
  107. allow_websocket_origin = [info.server_host]
  108. if info.origin:
  109. allow_websocket_origin.append(_origin_url(info.origin))
  110. server = Server({"/": app}, io_loop=loop, port=port, allow_websocket_origin=allow_websocket_origin, **kw)
  111. server_id = uuid4().hex
  112. curstate().uuid_to_server[server_id] = server
  113. server.start()
  114. url = _server_url(info.server_host, server.port)
  115. from bokeh.embed import server_document
  116. script = server_document(url, resources=None)
  117. script = re.sub(r'<script(.*?)>([\s\S]*?)</script>', r"""
  118. <script \g<1>>
  119. requirejs(['bokeh', 'bokeh-widgets', 'bokeh-tables'], function(Bokeh) {
  120. \g<2>
  121. });
  122. </script>
  123. """, script)
  124. put_html(script, sanitize=False)
  125. def try_install_bokeh_hook():
  126. """尝试安装bokeh支持"""
  127. try:
  128. from bokeh.io import install_notebook_hook
  129. except ImportError:
  130. return False
  131. install_notebook_hook('pywebio', load_notebook, show_doc, show_app)
  132. return True