Coverage for pyquickhelper/ipythonhelper/widgets.py: 85%

93 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-03 02:21 +0200

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

35 

36import copy 

37 

38 

39class StaticWidget(object): 

40 

41 """ 

42 Base Class for Static Widgets 

43 """ 

44 

45 def __init__(self, name=None, divclass=None): 

46 """ 

47 constructor 

48 

49 @param name name 

50 @param divclass class for div section 

51 """ 

52 self.name = name 

53 if divclass is None: 

54 self.divargs = "" 

55 else: 

56 self.divargs = f'class:"{divclass}"' 

57 

58 def __repr__(self): 

59 """ 

60 operator, call method html 

61 """ 

62 return self.html() 

63 

64 def _repr_html_(self): 

65 """ 

66 operator, call method html 

67 """ 

68 return self.html() 

69 

70 def html(self): 

71 "abstract method" 

72 raise NotImplementedError( # pragma: no cover 

73 "This should overriden.") 

74 

75 def copy(self): 

76 """ 

77 calls deepcopy 

78 

79 @return copy of self 

80 """ 

81 return copy.deepcopy(self) 

82 

83 def renamed(self, name): 

84 """ 

85 rename *name* if *name* is an attribute 

86 

87 @return object 

88 """ 

89 if (self.name is not None) and (self.name != name): 

90 obj = self.copy() 

91 else: 

92 obj = self 

93 obj.name = name 

94 return obj 

95 

96 

97class RangeWidget(StaticWidget): 

98 

99 """ 

100 Range (slider) widget 

101 

102 The class overloads :meth:`html <pyquickhelper.ipythonhelper.widgets.RangeWidget.html>` 

103 and :meth:`values <pyquickhelper.ipythonhelper.widgets.RangeWidget.values>`. 

104 """ 

105 slider_html = ('<b>{name}:</b> <input type="range" name="{name}" ' 

106 'min="{range[0]}" max="{range[1]}" step="{range[2]}" ' 

107 'value="{default}" style="{style}" ' 

108 'oninput="interactUpdate(this.parentNode);" ' 

109 'onchange="interactUpdate(this.parentNode);">') 

110 

111 def __init__(self, min, max, step=1, name=None, 

112 default=None, width=350, divclass=None, 

113 show_range=False): 

114 """ 

115 @param min min value 

116 @param max max value 

117 @param step step 

118 @param name name 

119 @param default default value 

120 @param width width in pixel 

121 @param divclass class for div section 

122 @param show_range boolean 

123 """ 

124 StaticWidget.__init__(self, name, divclass) 

125 self.datarange = (min, max, step) 

126 self.width = width 

127 self.show_range = show_range 

128 if default is None: 

129 self.default = min 

130 else: 

131 self.default = default 

132 

133 def values(self): 

134 """ 

135 @return all possible values 

136 """ 

137 min, max, step = self.datarange 

138 import numpy as np 

139 return np.arange(min, max + step, step) 

140 

141 def html(self): 

142 """ 

143 HTML code 

144 

145 @return string HTML 

146 """ 

147 style = "" 

148 

149 if self.width is not None: 

150 style += f"width:{self.width}px" 

151 

152 output = self.slider_html.format(name=self.name, range=self.datarange, 

153 default=self.default, style=style) 

154 if self.show_range: 

155 output = f"{self.datarange[0]} {output} {self.datarange[1]}" 

156 return output 

157 

158 

159class DropDownWidget(StaticWidget): 

160 

161 """ 

162 drop down list 

163 """ 

164 

165 #: template 1 

166 select_html = ('<b>{name}:</b> <select name="{name}" ' 

167 'onchange="interactUpdate(this.parentNode);"> ' 

168 '{options}' 

169 '</select>' 

170 ) 

171 

172 #: template 2 

173 option_html = ('<option value="{value}" ' 

174 '{selected}>{label}</option>') 

175 

176 def __init__(self, values, name=None, 

177 labels=None, default=None, divclass=None, 

178 delimiter=" "): 

179 """ 

180 @param values values for the list 

181 @param name name of the object 

182 @param labels ? 

183 @param default default value 

184 @param divclass class for div section 

185 @param delimiter delimiter 

186 """ 

187 StaticWidget.__init__(self, name, divclass) 

188 self._values = values 

189 self.delimiter = delimiter 

190 if labels is None: 

191 labels = map(str, values) 

192 elif len(labels) != len(values): 

193 raise ValueError("length of labels must match length of values") 

194 self.labels = labels 

195 

196 if default is None: 

197 self.default = values[0] 

198 elif default in values: 

199 self.default = default 

200 else: 

201 raise ValueError( # pragma: no cover 

202 "if specified, default must be in values") 

203 

204 def _single_option(self, label, value): 

205 """ 

206 private 

207 """ 

208 if value == self.default: 

209 selected = ' selected ' 

210 else: 

211 selected = '' 

212 return self.option_html.format(label=label, 

213 value=value, 

214 selected=selected) 

215 

216 def values(self): 

217 """ 

218 return all possible values 

219 """ 

220 return self._values 

221 

222 def html(self): 

223 """ 

224 return HTML string 

225 """ 

226 options = self.delimiter.join( 

227 [self._single_option(label, value) 

228 for (label, value) in zip(self.labels, self._values)] 

229 ) 

230 return self.select_html.format(name=self.name, 

231 options=options) 

232 

233 

234class RadioWidget(StaticWidget): 

235 

236 """ 

237 radio button 

238 """ 

239 

240 #: template 1 

241 radio_html = ('<input type="radio" name="{name}" value="{value}" ' 

242 '{checked} ' 

243 'onchange="interactUpdate(this.parentNode);">') 

244 

245 def __init__(self, values, name=None, 

246 labels=None, default=None, divclass=None, 

247 delimiter=" "): 

248 """ 

249 @param values values for the list 

250 @param name name of the object 

251 @param labels ? 

252 @param default default value 

253 @param divclass class for div section 

254 @param delimiter delimiter 

255 """ 

256 StaticWidget.__init__(self, name, divclass) 

257 self._values = values 

258 self.delimiter = delimiter 

259 

260 if labels is None: 

261 labels = map(str, values) 

262 elif len(labels) != len(values): 

263 raise ValueError("length of labels must match length of values") 

264 self.labels = labels 

265 

266 if default is None: 

267 self.default = values[0] 

268 elif default in values: 

269 self.default = default 

270 else: 

271 raise ValueError( # pragma: no cover 

272 "if specified, default must be in values: default={0}, values={1}".format( 

273 default, values)) 

274 

275 def _single_radio(self, value): 

276 """ 

277 private 

278 """ 

279 if value == self.default: 

280 checked = 'checked="checked"' 

281 else: 

282 checked = '' 

283 return self.radio_html.format(name=self.name, value=value, 

284 checked=checked) 

285 

286 def values(self): 

287 """ 

288 return all the possible values 

289 """ 

290 return self._values 

291 

292 def html(self): 

293 """ 

294 return HTML string 

295 """ 

296 preface = f'<b>{self.name}:</b> ' 

297 return preface + self.delimiter.join( 

298 [f"{label}: {self._single_radio(value)}" 

299 for (label, value) in zip(self.labels, self._values)])