Hide keyboard shortcuts

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`. 

6 

7Copyright (c) 2013, Jake Vanderplas 

8All rights reserved. 

9 

10Redistribution and use in source and binary forms, with or without modification, 

11are permitted provided that the following conditions are met: 

12 

13* Redistributions of source code must retain the above copyright notice, this 

14 list of conditions and the following disclaimer. 

15 

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. 

19 

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. 

23 

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 

38 

39 

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 

51 

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)) 

64 

65 rep = ip.display_formatter.formatters['text/html'](obj) 

66 

67 if rep is not None: 

68 return rep 

69 elif hasattr(obj, '_repr_html_'): 

70 return obj._repr_html_() 

71 

72 

73class StaticInteract(object): 

74 

75 """ 

76 Static Interact Object 

77 

78 See notebook :ref:`havingaforminanotebookrst`. 

79 

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 """ 

84 

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); 

98 

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 }} 

108 

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> 

119 

120 <div> 

121 {outputs} 

122 {widgets} 

123 </div> 

124 """ 

125 

126 subdiv_template = """ 

127 <div name="{name}" style="display:{display}"> 

128 {content} 

129 </div> 

130 """ 

131 

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) 

144 

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) 

153 

154 self.widgets = OrderedDict(kwargs) 

155 self.function = function 

156 

157 def _output_html(self): 

158 """ 

159 html output 

160 

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()]) 

167 

168 # Now reorder alphabetically by names so divnames match javascript 

169 names, values, defaults = zip(*sorted(zip(names, values, defaults))) 

170 

171 results = [self.function(**dict(zip(names, vals))) 

172 for vals in itertools.product(*values)] 

173 

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)] 

178 

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)) 

184 

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())]) 

191 

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. 

196 

197 @return string 

198 """ 

199 return self.template.format(outputs=self._output_html(), 

200 widgets=self._widget_html()) 

201 

202 def _repr_html_(self): 

203 """ 

204 Synonym for :meth:`html <pyquickhelper.ipythonhelper.interact.StaticInteract.html>`. 

205 """ 

206 return self.html()