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
« 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`.
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"""
36import copy
39class StaticWidget(object):
41 """
42 Base Class for Static Widgets
43 """
45 def __init__(self, name=None, divclass=None):
46 """
47 constructor
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}"'
58 def __repr__(self):
59 """
60 operator, call method html
61 """
62 return self.html()
64 def _repr_html_(self):
65 """
66 operator, call method html
67 """
68 return self.html()
70 def html(self):
71 "abstract method"
72 raise NotImplementedError( # pragma: no cover
73 "This should overriden.")
75 def copy(self):
76 """
77 calls deepcopy
79 @return copy of self
80 """
81 return copy.deepcopy(self)
83 def renamed(self, name):
84 """
85 rename *name* if *name* is an attribute
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
97class RangeWidget(StaticWidget):
99 """
100 Range (slider) widget
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);">')
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
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)
141 def html(self):
142 """
143 HTML code
145 @return string HTML
146 """
147 style = ""
149 if self.width is not None:
150 style += f"width:{self.width}px"
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
159class DropDownWidget(StaticWidget):
161 """
162 drop down list
163 """
165 #: template 1
166 select_html = ('<b>{name}:</b> <select name="{name}" '
167 'onchange="interactUpdate(this.parentNode);"> '
168 '{options}'
169 '</select>'
170 )
172 #: template 2
173 option_html = ('<option value="{value}" '
174 '{selected}>{label}</option>')
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
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")
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)
216 def values(self):
217 """
218 return all possible values
219 """
220 return self._values
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)
234class RadioWidget(StaticWidget):
236 """
237 radio button
238 """
240 #: template 1
241 radio_html = ('<input type="radio" name="{name}" value="{value}" '
242 '{checked} '
243 'onchange="interactUpdate(this.parentNode);">')
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
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
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))
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)
286 def values(self):
287 """
288 return all the possible values
289 """
290 return self._values
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)])