Coverage for src/jyquickhelper/jspy/render_nb_js_vis.py: 95%
60 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-02 00:05 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-02 00:05 +0200
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Renders a network in Javascript.
5"""
6import os
7from .render_nb_js import RenderJS, JavascriptScriptError
10class JavascriptVisError(JavascriptScriptError):
11 """
12 Raised when the code does not contain what the class expects to find.
13 """
14 pass
17class RenderJsVis(RenderJS):
18 """
19 Renders a network in a :epkg:`notebook`
20 with :epkg:`vis.js`.
21 """
23 def __init__(self, js=None, local=False, width="100%", height="100%", divid=None,
24 style=None, only_html=True, div_class=None, check_urls=True,
25 class_vis='Network', dot=None, layout=None, direction='UD'):
26 """
27 @param js (str) javascript
28 @param local (bool) use local path to javascript dependencies
29 @param script (str) script
30 @param width (str) width
31 @param height (str) height
32 @param style (str) style (added in ``<style>...</style>``)
33 @param divid (str|None) id of the div
34 @param only_html (bool) use only function
35 `display_html <http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html?
36 highlight=display_html#IPython.display.display_html>`_
37 and not `display_javascript
38 <http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html?
39 highlight=display_html#IPython.display.display_javascript>`_ to add
40 javascript to the page.
41 @param div_class (str) class of the section ``div`` which will host the results
42 of the javascript
43 @param check_urls (bool) by default, check url exists
44 @param class_vis (str) visualization class (*Network*, *Timeline*, ...)
45 @param dot (str) either *js* or *dot* must be specified.
46 @param layout (str) layout see `layout <http://visjs.org/docs/network/layout.html>`_
47 @param direction (str) if ``layout=='hiearchical'``, a string among
48 `'UD'`, `'DU'`, `'LR'`, `'RL'`.
50 The script must defined variables *options* and *data* if
51 ``class_vis=='Network'``.
52 """
53 script = RenderJsVis._build_script(
54 js, dot, layout=layout, direction=direction)
55 libs, css = RenderJsVis._get_libs_css(local, class_vis)
56 RenderJS.__init__(self, script, width=width, height=height, divid=divid,
57 only_html=only_html, div_class=div_class, check_urls=True,
58 libs=libs, css=css, local=local)
60 @staticmethod
61 def _get_libs_css(local, class_vis):
62 """
63 Returns the dependencies.
65 @param local use local file (True) or remote urls (False)
66 @param lite use lite version
67 @return tuple *(libs, css)*
68 """
69 if class_vis == 'Network':
70 libs = [ # 'vis-timeline-graph2d.min.js',
71 # 'vis-network.min.js',
72 # 'vis-graph3d.min.js',
73 'vis.min.js'
74 ]
75 css = [ # 'vis-timeline-graph2d.min.css',
76 'vis-network.min.css',
77 'vis.min.css'
78 ]
79 else:
80 raise NotImplementedError(
81 f"Unable to generate a script for class '{class_vis}'")
83 if local:
84 this = os.path.dirname(__file__)
85 libs = [dict(path=os.path.join(this, '..', 'js', 'visjs', j),
86 name=j.split('.')[0]) for j in libs] # pylint: disable=C0207
87 css = [os.path.join(this, '..', 'js', 'visjs', j) for j in css]
88 else:
89 libs = [dict(path='http://www.xavierdupre.fr/js/visjs/' + j,
90 name=j.split('.')[0]) for j in libs] # pylint: disable=C0207
91 css = ['http://www.xavierdupre.fr/js/visjs/' + j for j in css]
92 return libs, css
94 @staticmethod
95 def _build_script(js, dot, **options):
96 """
97 Builds the javascript script.
99 @param js javascript
100 @param dot dot scripts
101 @param options graph options
102 @return javascript
103 """
104 if js is None:
105 if dot is None:
106 raise ValueError("js or dot must be specified")
107 else:
108 dot = dot.replace("'", "\\'").replace("\n", "\\n")
109 jsadd = """
110 var DOTstring = '%s';
111 var parsedData = vis.network.convertDot(DOTstring);
112 var data = { nodes: parsedData.nodes, edges: parsedData.edges };
113 var options = parsedData.options;
114 """.replace(" ", "") % dot
115 else:
116 if dot is not None:
117 raise ValueError("js or dot must be specified not both")
118 jsadd = js
120 if options or 'var options =' not in jsadd:
121 opts = {}
122 if 'layout' in options and options['layout'] is not None:
123 opts['layout'] = {options['layout']: {'direction': options.get('direction', 'UD'),
124 'sortMethod': "directed"}}
125 else:
126 opts = {k: v for k, v in options.items() if v is not None}
127 st = f'var options = {RenderJsVis._options2js(opts)};'
128 jsadd += "\n" + st + "\n"
130 checks = ['var data =', 'var options =']
131 for ch in checks:
132 if ch not in jsadd:
133 raise JavascriptVisError(
134 f"String '{ch}' was not found in\n{js}")
135 script = jsadd + "\nvar container = document.getElementById('__ID__');" + \
136 "\nvar network = new vis.Network(container, data, options);\n"
137 return script
139 @staticmethod
140 def _options2js(data):
141 """
142 Converts *data* into a string.
143 """
144 rows = ['{']
145 for k, v in data.items():
146 if k is None:
147 raise ValueError("k cannot be None")
148 rows.append(k)
149 rows.append(':')
150 if isinstance(v, dict):
151 rows.append(RenderJsVis._options2js(v))
152 else:
153 rows.append(f'"{v}"')
154 rows.append(', ')
155 rows.append('}')
156 return "".join(rows)