Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2@file
3@brief To add interactive widgets in a notebook and connect it to Python function,
4Source: https://github.com/jakevdp/ipywidgets, the module was modified for Python 3
5See notebook :ref:`havingaforminanotebookrst`.
7Copyright (c) 2013, Jake Vanderplas
8All rights reserved.
10Redistribution and use in source and binary forms, with or without modification,
11are permitted provided that the following conditions are met:
13* Redistributions of source code must retain the above copyright notice, this
14 list of conditions and the following disclaimer.
16* Redistributions in binary form must reproduce the above copyright notice, this
17 list of conditions and the following disclaimer in the documentation and/or
18 other materials provided with the distribution.
20* Neither the name of the {organization} nor the names of its
21 contributors may be used to endorse or promote products derived from
22 this software without specific prior written permission.
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
28ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
31ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34"""
35from collections import OrderedDict
36import itertools
37import base64
40def _get_html(obj):
41 """
42 Get the HTML representation of an object
43 """
44 # TODO: use displaypub to make this more general
45 from IPython import get_ipython
46 ip = get_ipython()
47 if ip is not None:
48 png_rep = ip.display_formatter.formatters['image/png'](obj)
49 else:
50 png_rep = None
52 if png_rep is not None: # pragma: no cover
53 # do not move this import to the root or
54 # you will be exposed to the issue mentioned by
55 # function fix_tkinter_issues_virtualenv
56 import matplotlib.pyplot as plt
57 if isinstance(obj, plt.Figure):
58 plt.close(obj) # keep from displaying twice
59 new_bytes = base64.b64encode(png_rep)
60 new_str = new_bytes.decode("utf8")
61 return '<img src="data:image/png;base64,{0}">'.format(new_str)
62 else:
63 return "<p> {0} </p>".format(str(obj))
65 rep = ip.display_formatter.formatters['text/html'](obj)
67 if rep is not None:
68 return rep
69 elif hasattr(obj, '_repr_html_'):
70 return obj._repr_html_()
73class StaticInteract(object):
75 """
76 Static Interact Object
78 See notebook :ref:`havingaforminanotebookrst`.
80 @warning In order to be fast in the notebook, the function is called for every possible
81 combination of values the controls can return. If it is a graph,
82 all graphs are generared.
83 """
85 template = """
86 <script type="text/javascript">
87 var mergeNodes = function(a, b) {{
88 return [].slice.call(a).concat([].slice.call(b));
89 }}; // http://stackoverflow.com/questions/914783/javascript-nodelist/17262552#17262552
90 function interactUpdate(div){{
91 var outputs = div.getElementsByTagName("div");
92 //var controls = div.getElementsByTagName("input");
93 var controls = mergeNodes(div.getElementsByTagName("input"), div.getElementsByTagName("select"));
94 function nameCompare(a,b) {{
95 return a.getAttribute("name").localeCompare(b.getAttribute("name"));
96 }}
97 controls.sort(nameCompare);
99 var value = "";
100 for(i=0; i<controls.length; i++){{
101 if((controls[i].type == "range") || controls[i].checked){{
102 value = value + controls[i].getAttribute("name") + controls[i].value;
103 }}
104 if(controls[i].type == "select-one"){{
105 value = value + controls[i].getAttribute("name") + controls[i][controls[i].selectedIndex].value;
106 }}
107 }}
109 for(i=0; i<outputs.length; i++){{
110 var name = outputs[i].getAttribute("name");
111 if(name == value){{
112 outputs[i].style.display = 'block';
113 }} else if(name != "controls"){{
114 outputs[i].style.display = 'none';
115 }}
116 }}
117 }}
118 </script>
120 <div>
121 {outputs}
122 {widgets}
123 </div>
124 """
126 subdiv_template = """
127 <div name="{name}" style="display:{display}">
128 {content}
129 </div>
130 """
132 @staticmethod
133 def _get_strrep(val):
134 """
135 Need to match javascript string rep
136 """
137 # TODO: is there a better way to do this?
138 if isinstance(val, str):
139 return val
140 elif val % 1 == 0:
141 return str(int(val))
142 else:
143 return str(val)
145 def __init__(self, function, **kwargs):
146 """
147 constructor
148 """
149 # TODO: implement *args (difficult because of the name thing)
150 # update names
151 for name in kwargs:
152 kwargs[name] = kwargs[name].renamed(name)
154 self.widgets = OrderedDict(kwargs)
155 self.function = function
157 def _output_html(self):
158 """
159 html output
161 @return string
162 """
163 names = list(self.widgets)
164 values = [widget.values() for widget in self.widgets.values()]
165 defaults = tuple([widget.default # pylint: disable=R1728
166 for widget in self.widgets.values()])
168 # Now reorder alphabetically by names so divnames match javascript
169 names, values, defaults = zip(*sorted(zip(names, values, defaults)))
171 results = [self.function(**dict(zip(names, vals)))
172 for vals in itertools.product(*values)]
174 divnames = [''.join(['{0}{1}'.format(n, self._get_strrep(v))
175 for n, v in zip(names, vals)])
176 for vals in itertools.product(*values)]
177 display = [vals == defaults for vals in itertools.product(*values)]
179 tmplt = self.subdiv_template
180 return "".join(tmplt.format(name=divname,
181 display="block" if disp else "none",
182 content=_get_html(result))
183 for divname, result, disp in zip(divnames, results, display))
185 def _widget_html(self):
186 """
187 @return string
188 """
189 return "\n<br>\n".join([widget.html()
190 for name, widget in sorted(self.widgets.items())])
192 def html(self):
193 """
194 Produce the HTML output, insert results from @see me _output_html and
195 @see me _widget_html and insert it into the template.
197 @return string
198 """
199 return self.template.format(outputs=self._output_html(),
200 widgets=self._widget_html())
202 def _repr_html_(self):
203 """
204 Synonym for :meth:`html <pyquickhelper.ipythonhelper.interact.StaticInteract.html>`.
205 """
206 return self.html()