{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Custom widgets in a notebook\n", "\n", "The notebook explore a couple of ways to interact with the user and modifies the output based on these interactions. This is inspired from the examples from [ipwidgets](http://ipywidgets.readthedocs.io/)."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## List of widgets\n", "\n", "[Widget List](https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html)"]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": ["import ipywidgets\n", "import datetime"]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [{"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "7ead5f57e5114d78a70463666074ed05", "version_major": 2, "version_minor": 2}, "text/plain": ["DatePicker(value=datetime.datetime(2020, 1, 14, 14, 41, 54, 26999), description='Pick a Date')"]}, "metadata": {}, "output_type": "display_data"}], "source": ["obj = ipywidgets.DatePicker(\n", " description='Pick a Date',\n", " disabled=False,\n", " value=datetime.datetime.now(),\n", ")\n", "obj"]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"data": {"text/plain": ["datetime.datetime(2020, 1, 14, 14, 41, 54, 26999)"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["obj.value"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Events"]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "303dc1b8adbb44729b88c94fbf6220d3", "version_major": 2, "version_minor": 2}, "text/plain": ["Button(description='Click Me!', style=ButtonStyle())"]}, "metadata": {}, "output_type": "display_data"}], "source": ["from IPython.display import display\n", "button = ipywidgets.Button(description=\"Click Me!\")\n", "display(button)\n", "\n", "def on_button_clicked(b):\n", " print(\"Button clicked.\")\n", "\n", "button.on_click(on_button_clicked)"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "da412467a7534735a7e11873ff682a71", "version_major": 2, "version_minor": 2}, "text/plain": ["IntSlider(value=0)"]}, "metadata": {}, "output_type": "display_data"}, {"name": "stdout", "output_type": "stream", "text": ["14\n", "18\n", "22\n", "23\n", "25\n", "26\n", "27\n", "28\n", "27\n", "24\n", "22\n", "19\n", "17\n", "16\n", "15\n", "14\n", "12\n", "11\n", "10\n", "9\n", "8\n", "7\n", "6\n", "5\n", "4\n", "3\n", "2\n", "1\n", "2\n", "5\n", "6\n", "8\n", "10\n", "11\n", "12\n", "13\n", "14\n"]}], "source": ["int_range = ipywidgets.IntSlider()\n", "display(int_range)\n", "\n", "def on_value_change(change):\n", " print(change['new'])\n", "\n", "int_range.observe(on_value_change, names='value')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## matplotlib"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": ["%matplotlib inline\n", "import matplotlib.pyplot as plt"]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": ["import networkx as nx\n", "\n", "def random_lobster(n, m, k, p):\n", " return nx.random_lobster(n, p, p / m)\n", "\n", "def powerlaw_cluster(n, m, k, p):\n", " return nx.powerlaw_cluster_graph(n, m, p)\n", "\n", "def erdos_renyi(n, m, k, p):\n", " return nx.erdos_renyi_graph(n, p)\n", "\n", "def newman_watts_strogatz(n, m, k, p):\n", " return nx.newman_watts_strogatz_graph(n, k, p)\n", "\n", "def plot_random_graph(n, m, k, p, generator):\n", " g = generator(n, m, k, p)\n", " nx.draw(g)\n", " plt.show()"]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "7a0bf64696cb489ea27ecb88b0387a3e", "version_major": 2, "version_minor": 2}, "text/plain": ["interactive(children=(IntSlider(value=16, description='n', max=30, min=2), IntSlider(value=5, description='m',\u2026"]}, "metadata": {}, "output_type": "display_data"}], "source": ["ipywidgets.interact(plot_random_graph, n=(2,30), m=(1,10), k=(1,10), p=(0.0, 1.0, 0.001),\n", " generator={\n", " 'lobster': random_lobster,\n", " 'power law': powerlaw_cluster,\n", " 'Newman-Watts-Strogatz': newman_watts_strogatz,\n", " 'Erd\u0151s-R\u00e9nyi': erdos_renyi,\n", " });"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Custom widget - text\n", "\n", "[Building a Custom Widget - Hello World](http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Custom.html)."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": ["import ipywidgets as widgets\n", "from traitlets import Unicode, validate\n", "\n", "class HelloWidget(widgets.DOMWidget):\n", " _view_name = Unicode('HelloView').tag(sync=True)\n", " _view_module = Unicode('hello').tag(sync=True)\n", " _view_module_version = Unicode('0.1.0').tag(sync=True)\n", " value = Unicode('Hello World! - ').tag(sync=True)"]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"data": {"application/javascript": ["require.undef('hello');\n", "\n", "define('hello', [\"@jupyter-widgets/base\"], function(widgets) {\n", "\n", " var HelloView = widgets.DOMWidgetView.extend({\n", "\n", " render: function() {\n", " this.value_changed();\n", " this.model.on('change:value', this.value_changed, this);\n", " },\n", "\n", " value_changed: function() {\n", " this.el.textContent = this.model.get('value');\n", " },\n", " });\n", "\n", " return {\n", " HelloView : HelloView\n", " };\n", "});\n"], "text/plain": [""]}, "metadata": {}, "output_type": "display_data"}], "source": ["%%javascript\n", "require.undef('hello');\n", "\n", "define('hello', [\"@jupyter-widgets/base\"], function(widgets) {\n", "\n", " var HelloView = widgets.DOMWidgetView.extend({\n", "\n", " render: function() {\n", " this.value_changed();\n", " this.model.on('change:value', this.value_changed, this);\n", " },\n", "\n", " value_changed: function() {\n", " this.el.textContent = this.model.get('value');\n", " },\n", " });\n", "\n", " return {\n", " HelloView : HelloView\n", " };\n", "});"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "eaaf30d3b18543f5aad64220ce0bcef2", "version_major": 2, "version_minor": 2}, "text/plain": ["HelloWidget()"]}, "metadata": {}, "output_type": "display_data"}], "source": ["w = HelloWidget()\n", "w"]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": ["w.value = 'changed the value'"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Custom widget - html - svg - events\n", "\n", "See [Low Level Widget Tutorial](http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Low%20Level.html), [CircleView](https://github.com/paul-shannon/notebooks/blob/master/study/CircleView.ipynb). The following example links a custom widget and a sliding bar which defines the radius of circle to draw. See [Linking two similar widgets](http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Basics.html#Linking-two-similar-widgets). The information (circles, radius) is declared in a python class *CircleWidget* and available in the javascript code in two places: the widget (``this.model``) and the view itself (used to connect event to it). Finally, a link is added between two values: value from the first widget (sliding bar) and radius from the second widget (*CircleWidget*)."]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [{"data": {"application/javascript": ["require.config({\n", " paths: {\n", " d3: '//cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min'\n", " },\n", "});\n"], "text/plain": [""]}, "metadata": {}, "output_type": "display_data"}], "source": ["%%javascript\n", "require.config({\n", " paths: {\n", " d3: '//cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min'\n", " },\n", "});"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": ["import ipywidgets\n", "from traitlets import Int, Unicode, Tuple, CInt, Dict, validate\n", "\n", "class CircleWidget(ipywidgets.DOMWidget):\n", " _view_name = Unicode('CircleView').tag(sync=True)\n", " _view_module = Unicode('circle').tag(sync=True)\n", " radius = Int(100).tag(sync=True)\n", " circles = Tuple().tag(sync=True)\n", " width = Int().tag(sync=True)\n", " height = Int().tag(sync=True)\n", " radius = Int().tag(sync=True)\n", " def __init__(self, **kwargs):\n", " super(ipywidgets.DOMWidget, self).__init__(**kwargs)\n", " self.width = kwargs.get('width', 500)\n", " self.height = kwargs.get('height', 100)\n", " self.radius = 1\n", " def drawCircle(self, x, y, fillColor=\"white\", borderColor=\"black\"):\n", " newCircle = {\"x\": x, \"y\": y, \"radius\": self.radius * 10, \"fillColor\": fillColor, \"borderColor\": borderColor}\n", " self.circles = self.circles + (newCircle,)"]}, {"cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [{"data": {"application/javascript": ["\"use strict\";\n", "\n", "require.undef('circle');\n", "\n", "define('circle', [\"@jupyter-widgets/base\", \"d3\"], function(widgets, d3) {\n", " \n", " var CircleView = widgets.DOMWidgetView.extend({\n", "\n", " initialize: function() {\n", " console.log(\"---- initialize, this:\");\n", " console.log(this);\n", " this.circles = [];\n", " this.radius = 1;\n", " },\n", "\n", " createDiv: function(){\n", " var width = this.model.get('width'); \n", " var height = this.model.get('height'); \n", " var divstyle = $(\"
\");\n", " return(divstyle);\n", " },\n", " \n", " createCanvas: function(){\n", " var width = this.model.get('width'); \n", " var height = this.model.get('height'); \n", " var radius = this.model.get('radius'); \n", " console.log(\"--SIZE--\", width, 'x', height, \" radius\", radius);\n", " var svg = d3.select(\"#d3DemoDiv\")\n", " .append(\"svg\")\n", " .attr(\"id\", \"svg\").attr(\"width\", width).attr(\"height\", height);\n", "\n", " this.svg = svg;\n", " var circleView = this;\n", " \n", " svg.on('click', function() {\n", " var coords = d3.mouse(this);\n", " //debugger;\n", " var radius = circleView.radius;\n", " console.log('--MOUSE--', coords, \" radius:\", radius);\n", " var newCircle = {x: coords[0], y: coords[1], radius: 10 * radius,\n", " borderColor: \"black\", fillColor: \"beige\"};\n", " circleView.circles.push(newCircle);\n", " circleView.drawCircle(newCircle);\n", " //debugger;\n", " circleView.model.set(\"circles\", JSON.stringify(circleView.circles));\n", " circleView.touch();\n", " });\n", " }, \n", "\n", " drawCircle: function(obj){\n", " this.svg.append(\"circle\")\n", " .style(\"stroke\", \"gray\")\n", " .style(\"fill\", \"white\")\n", " .attr(\"r\", obj.radius)\n", " .attr(\"cx\", obj.x)\n", " .attr(\"cy\", obj.y)\n", " .on(\"mouseover\", function(){d3.select(this).style(\"fill\", \"aliceblue\");})\n", " .on(\"mouseout\", function(){d3.select(this).style(\"fill\", \"white\");});\n", " },\n", "\n", " render: function() { \n", " this.$el.append(this.createDiv());\n", " this.listenTo(this.model, 'change:circles', this._circles_changed, this);\n", " this.listenTo(this.model, 'change:radius', this._radius_changed, this);\n", " var circleView = this;\n", " function myFunc(){\n", " circleView.createCanvas()\n", " };\n", " setTimeout(myFunc, 500);\n", " },\n", "\n", " _circles_changed: function() {\n", " var circles = this.model.get(\"circles\");\n", " var newCircle = circles[circles.length-1];\n", " console.log('--DRAW--', this.circles);\n", " this.circles.push(newCircle);\n", " console.log('--LENGTH--', circles.length, \" == \", circles.length);\n", " this.drawCircle(newCircle);\n", " },\n", "\n", " _radius_changed: function() {\n", " console.log('--RADIUS--', this.radius, this.model.get('radius'));\n", " this.radius = this.model.get('radius');\n", " }\n", " });\n", " return {\n", " CircleView : CircleView\n", " };\n", "});\n"], "text/plain": [""]}, "metadata": {}, "output_type": "display_data"}], "source": ["%%javascript\n", "\"use strict\";\n", "\n", "require.undef('circle');\n", "\n", "define('circle', [\"@jupyter-widgets/base\", \"d3\"], function(widgets, d3) {\n", " \n", " var CircleView = widgets.DOMWidgetView.extend({\n", "\n", " initialize: function() {\n", " console.log(\"---- initialize, this:\");\n", " console.log(this);\n", " this.circles = [];\n", " this.radius = 1;\n", " },\n", "\n", " createDiv: function(){\n", " var width = this.model.get('width'); \n", " var height = this.model.get('height'); \n", " var divstyle = $(\"
\");\n", " return(divstyle);\n", " },\n", " \n", " createCanvas: function(){\n", " var width = this.model.get('width'); \n", " var height = this.model.get('height'); \n", " var radius = this.model.get('radius'); \n", " console.log(\"--SIZE--\", width, 'x', height, \" radius\", radius);\n", " var svg = d3.select(\"#d3DemoDiv\")\n", " .append(\"svg\")\n", " .attr(\"id\", \"svg\").attr(\"width\", width).attr(\"height\", height);\n", "\n", " this.svg = svg;\n", " var circleView = this;\n", " \n", " svg.on('click', function() {\n", " var coords = d3.mouse(this);\n", " //debugger;\n", " var radius = circleView.radius;\n", " console.log('--MOUSE--', coords, \" radius:\", radius);\n", " var newCircle = {x: coords[0], y: coords[1], radius: 10 * radius,\n", " borderColor: \"black\", fillColor: \"beige\"};\n", " circleView.circles.push(newCircle);\n", " circleView.drawCircle(newCircle);\n", " //debugger;\n", " circleView.model.set(\"circles\", JSON.stringify(circleView.circles));\n", " circleView.touch();\n", " });\n", " }, \n", "\n", " drawCircle: function(obj){\n", " this.svg.append(\"circle\")\n", " .style(\"stroke\", \"gray\")\n", " .style(\"fill\", \"white\")\n", " .attr(\"r\", obj.radius)\n", " .attr(\"cx\", obj.x)\n", " .attr(\"cy\", obj.y)\n", " .on(\"mouseover\", function(){d3.select(this).style(\"fill\", \"aliceblue\");})\n", " .on(\"mouseout\", function(){d3.select(this).style(\"fill\", \"white\");});\n", " },\n", "\n", " render: function() { \n", " this.$el.append(this.createDiv());\n", " this.listenTo(this.model, 'change:circles', this._circles_changed, this);\n", " this.listenTo(this.model, 'change:radius', this._radius_changed, this);\n", " var circleView = this;\n", " function myFunc(){\n", " circleView.createCanvas()\n", " };\n", " setTimeout(myFunc, 500);\n", " },\n", "\n", " _circles_changed: function() {\n", " var circles = this.model.get(\"circles\");\n", " var newCircle = circles[circles.length-1];\n", " console.log('--DRAW--', this.circles);\n", " this.circles.push(newCircle);\n", " console.log('--LENGTH--', circles.length, \" == \", circles.length);\n", " this.drawCircle(newCircle);\n", " },\n", "\n", " _radius_changed: function() {\n", " console.log('--RADIUS--', this.radius, this.model.get('radius'));\n", " this.radius = this.model.get('radius');\n", " }\n", " });\n", " return {\n", " CircleView : CircleView\n", " };\n", "});"]}, {"cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [{"data": {"application/vnd.jupyter.widget-view+json": {"model_id": "cecc5402ef9c4967a9b571b5050a594a", "version_major": 2, "version_minor": 2}, "text/plain": ["VBox(children=(IntSlider(value=1, max=10), CircleWidget(height=100, radius=1, width=500)))"]}, "metadata": {}, "output_type": "display_data"}], "source": ["cw = CircleWidget(width=500, height=100)\n", "scale = ipywidgets.IntSlider(1, 0, 10)\n", "box = widgets.VBox([scale, cw])\n", "mylink = ipywidgets.jslink((cw, 'radius'), (scale, 'value'))\n", "box"]}, {"cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": ["cw.drawCircle(x=30, y=30)"]}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": ["scale.value = 2"]}, {"cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": ["cw.drawCircle(x=60, y=30)"]}, {"cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2"}}, "nbformat": 4, "nbformat_minor": 2}