├── ipywidgets ├── __init__.py ├── interact.py └── widgets.py ├── .gitignore ├── README.md ├── setup.py ├── LICENSE └── TODO.md /ipywidgets/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | 3 | from .interact import StaticInteract 4 | from .widgets import RadioWidget, RangeWidget, DropDownWidget 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | 39 | # emacs 40 | *~ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Static Widgets for IPython Notebooks 2 | ==================================== 3 | 4 | **Note that this package is no longer supported; instead, use IPython's built-in interactive tools: https://github.com/ipython/ipywidgets** 5 | 6 | This is currently an experimental repository containing code which enables 7 | static widgets to be used in the IPython notebook. 8 | 9 | Use at your own risk: the interface will likely change in the near future... 10 | 11 | Demonstrations 12 | -------------- 13 | You can see this in action in a few places: 14 | 15 | - My [blog post](http://jakevdp.github.io/blog/2013/12/05/static-interactive-widgets/) about starting this repository 16 | - The [example notebook](http://nbviewer.ipython.org/github/jakevdp/ipywidgets/blob/master/example.ipynb) in this repository 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | DESCRIPTION = "IPython Static Widgets" 4 | LONG_DESCRIPTION = DESCRIPTION 5 | NAME = "ipywidgets" 6 | AUTHOR = "Jake VanderPlas" 7 | AUTHOR_EMAIL = "jakevdp@cs.washington.edu" 8 | MAINTAINER = "Jake VanderPlas" 9 | MAINTAINER_EMAIL = "jakevdp@cs.washington.edu" 10 | DOWNLOAD_URL = 'http://github.com/jakevdp/ipywidgets' 11 | LICENSE = 'BSD' 12 | 13 | VERSION = '0.0.1' 14 | 15 | setup(name=NAME, 16 | version=VERSION, 17 | description=DESCRIPTION, 18 | long_description=LONG_DESCRIPTION, 19 | author=AUTHOR, 20 | author_email=AUTHOR_EMAIL, 21 | maintainer=MAINTAINER, 22 | maintainer_email=MAINTAINER_EMAIL, 23 | url=DOWNLOAD_URL, 24 | download_url=DOWNLOAD_URL, 25 | license=LICENSE, 26 | packages=['ipywidgets'], 27 | ) 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Jake Vanderplas 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # To Do 2 | There are various issues here... 3 | 4 | ## Output CSS Tags 5 | The widgets should have css attributes that allow their style to be tweaked. 6 | Also, we need better labeling options for the widgets. 7 | 8 | ## HTML renderings 9 | Currently the HTML rendering of arbitrary objects uses either the raw text 10 | format or the png format. It should be possible to use the standard 11 | IPython display tools to do this the right way. According to @minrk and 12 | @ellisonbg via the IPython dev hipchat: 13 | 14 | > if you just call OutputArea.append_mime_type(json, your_element, true), you should get back `your_element` with the rendered output attached. 15 | > Or if you instantiate OutputArea and pass it the div you want output in you can just call the top level handle output with the JSON 16 | > you could easily build that JSON from the display_formatter JSON 17 | > The message spec docs would show you how to build that 18 | > http://ipython.org/ipython-doc/dev/development/messaging.html#display-data 19 | > 20 | > Basically it is just a matter of top level content attribute with data 21 | > and metadata subattributes that contain the display JSON 22 | > 23 | > I think all you need is the publish method of displaypub: 24 | > https://github.com/ipython/ipython/blob/master/IPython/kernel/zmq/zmqshell.py#L77 25 | > (data, metadata) is what formatter.format(obj) returns 26 | > 27 | 28 | 29 | ## Working with float-values 30 | Widgets with floating-point values don't always work, because of the differences 31 | in the way Python and Javascript represent floats. 32 | 33 | ## Other widgets 34 | Currently only radio buttons and sliders are implemented. Are there other 35 | widgets that should be included? 36 | 37 | ## Animations 38 | This is a much better approach than that used in the ``JSAnimation`` library. 39 | It would be nice to re-build those animation tools in the style used here, and 40 | to include animations in this library 41 | 42 | ## Compression 43 | These things can get really big really fast. Implementing some sort of image 44 | compression into this would be really, really useful. 45 | -------------------------------------------------------------------------------- /ipywidgets/interact.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | import itertools 3 | import matplotlib.pyplot as plt 4 | from IPython import get_ipython 5 | 6 | 7 | def _get_html(obj): 8 | """Get the HTML representation of an object""" 9 | # TODO: use displaypub to make this more general 10 | ip = get_ipython() 11 | png_rep = ip.display_formatter.formatters['image/png'](obj) 12 | 13 | if png_rep is not None: 14 | if isinstance(obj, plt.Figure): 15 | plt.close(obj) # keep from displaying twice 16 | return (''.format(png_rep.encode('base64'))) 18 | else: 19 | return "

{0}

".format(str(obj)) 20 | 21 | rep = ip.display_formatter.formatters['text/html'](obj) 22 | 23 | if rep is not None: 24 | return rep 25 | elif hasattr(obj, '_repr_html_'): 26 | return obj._repr_html_() 27 | 28 | 29 | class StaticInteract(object): 30 | """Static Interact Object""" 31 | 32 | template = """ 33 | 66 | 67 |
68 | {outputs} 69 | {widgets} 70 |
71 | """ 72 | 73 | subdiv_template = """ 74 |
75 | {content} 76 |
77 | """ 78 | 79 | @staticmethod 80 | def _get_strrep(val): 81 | """Need to match javascript string rep""" 82 | # TODO: is there a better way to do this? 83 | if isinstance(val, str): 84 | return val 85 | elif val % 1 == 0: 86 | return str(int(val)) 87 | else: 88 | return str(val) 89 | 90 | def __init__(self, function, **kwargs): 91 | # TODO: implement *args (difficult because of the name thing) 92 | # update names 93 | for name in kwargs: 94 | kwargs[name] = kwargs[name].renamed(name) 95 | 96 | self.widgets = OrderedDict(kwargs) 97 | self.function = function 98 | 99 | def _output_html(self): 100 | names = [name for name in self.widgets] 101 | values = [widget.values() for widget in self.widgets.values()] 102 | defaults = tuple([widget.default for widget in self.widgets.values()]) 103 | 104 | #Now reorder alphabetically by names so divnames match javascript 105 | names,values,defaults = zip(*sorted(zip(names,values,defaults))) 106 | 107 | results = [self.function(**dict(zip(names, vals))) 108 | for vals in itertools.product(*values)] 109 | 110 | divnames = [''.join(['{0}{1}'.format(n, self._get_strrep(v)) 111 | for n, v in zip(names, vals)]) 112 | for vals in itertools.product(*values)] 113 | display = [vals == defaults for vals in itertools.product(*values)] 114 | 115 | tmplt = self.subdiv_template 116 | return "".join(tmplt.format(name=divname, 117 | display="block" if disp else "none", 118 | content=_get_html(result)) 119 | for divname, result, disp in zip(divnames, 120 | results, 121 | display)) 122 | 123 | 124 | def _widget_html(self): 125 | return "\n
\n".join([widget.html() 126 | for name, widget in sorted(self.widgets.iteritems())]) 127 | 128 | def html(self): 129 | return self.template.format(outputs=self._output_html(), 130 | widgets=self._widget_html()) 131 | 132 | def _repr_html_(self): 133 | return self.html() 134 | 135 | -------------------------------------------------------------------------------- /ipywidgets/widgets.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import numpy as np 3 | 4 | 5 | class StaticWidget(object): 6 | """Base Class for Static Widgets""" 7 | def __init__(self, name=None, divclass=None): 8 | self.name = name 9 | if divclass is None: 10 | self.divargs = "" 11 | else: 12 | self.divargs = 'class:"{0}"'.format(divclass) 13 | 14 | def __repr__(self): 15 | return self.html() 16 | 17 | def _repr_html_(self): 18 | return self.html() 19 | 20 | def copy(self): 21 | return copy.deepcopy(self) 22 | 23 | def renamed(self, name): 24 | if (self.name is not None) and (self.name != name): 25 | obj = self.copy() 26 | else: 27 | obj = self 28 | obj.name = name 29 | return obj 30 | 31 | 32 | class RangeWidget(StaticWidget): 33 | """Range (slider) widget""" 34 | slider_html = ('{name}: ') 39 | def __init__(self, min, max, step=1, name=None, 40 | default=None, width=350, divclass=None, 41 | show_range=False): 42 | StaticWidget.__init__(self, name, divclass) 43 | self.datarange = (min, max, step) 44 | self.width = width 45 | self.show_range = show_range 46 | if default is None: 47 | self.default = min 48 | else: 49 | self.default = default 50 | 51 | def values(self): 52 | min, max, step = self.datarange 53 | return np.arange(min, max + step, step) 54 | 55 | def html(self): 56 | style = "" 57 | 58 | if self.width is not None: 59 | style += "width:{0}px".format(self.width) 60 | 61 | output = self.slider_html.format(name=self.name, range=self.datarange, 62 | default=self.default, style=style) 63 | if self.show_range: 64 | output = "{0} {1} {2}".format(self.datarange[0], 65 | output, 66 | self.datarange[1]) 67 | return output 68 | 69 | class DropDownWidget(StaticWidget): 70 | select_html = ('{name}: ' 74 | ) 75 | option_html = ('') 77 | def __init__(self, values, name=None, 78 | labels=None, default=None, divclass=None, 79 | delimiter=" " 80 | ): 81 | StaticWidget.__init__(self, name, divclass) 82 | self._values = values 83 | self.delimiter = delimiter 84 | if labels is None: 85 | labels = map(str, values) 86 | elif len(labels) != len(values): 87 | raise ValueError("length of labels must match length of values") 88 | self.labels = labels 89 | 90 | if default is None: 91 | self.default = values[0] 92 | elif default in values: 93 | self.default = default 94 | else: 95 | raise ValueError("if specified, default must be in values") 96 | 97 | def _single_option(self,label,value): 98 | if value == self.default: 99 | selected = ' selected ' 100 | else: 101 | selected = '' 102 | return self.option_html.format(label=label, 103 | value=value, 104 | selected=selected) 105 | def values(self): 106 | return self._values 107 | def html(self): 108 | options = self.delimiter.join( 109 | [self._single_option(label,value) 110 | for (label,value) in zip(self.labels, self._values)] 111 | ) 112 | return self.select_html.format(name=self.name, 113 | options=options) 114 | 115 | class RadioWidget(StaticWidget): 116 | radio_html = ('') 119 | 120 | def __init__(self, values, name=None, 121 | labels=None, default=None, divclass=None, 122 | delimiter=" "): 123 | StaticWidget.__init__(self, name, divclass) 124 | self._values = values 125 | self.delimiter = delimiter 126 | 127 | if labels is None: 128 | labels = map(str, values) 129 | elif len(labels) != len(values): 130 | raise ValueError("length of labels must match length of values") 131 | self.labels = labels 132 | 133 | if default is None: 134 | self.default = values[0] 135 | elif default in values: 136 | self.default = default 137 | else: 138 | raise ValueError("if specified, default must be in values") 139 | 140 | def _single_radio(self, value): 141 | if value == self.default: 142 | checked = 'checked="checked"' 143 | else: 144 | checked = '' 145 | return self.radio_html.format(name=self.name, value=value, 146 | checked=checked) 147 | 148 | def values(self): 149 | return self._values 150 | 151 | def html(self): 152 | preface = '{name}: '.format(name=self.name) 153 | return preface + self.delimiter.join( 154 | ["{0}: {1}".format(label, self._single_radio(value)) 155 | for (label, value) in zip(self.labels, self._values)]) 156 | 157 | --------------------------------------------------------------------------------