Custom widgets in a notebook

Links: notebook, html, PDF, python, slides, slides(2), GitHub

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.

from jyquickhelper import add_notebook_menu
add_notebook_menu()

List of widgets

Widget List

import ipywidgets
import datetime
obj = ipywidgets.DatePicker(
    description='Pick a Date',
    disabled=False,
    value=datetime.datetime.now(),
)
obj

Failed to display Jupyter Widget of type DatePicker.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

obj.value
datetime.datetime(2018, 3, 17, 15, 28, 49, 605121)

Events

from IPython.display import display
button = ipywidgets.Button(description="Click Me!")
display(button)

def on_button_clicked(b):
    print("Button clicked.")

button.on_click(on_button_clicked)

Failed to display Jupyter Widget of type Button.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

int_range = ipywidgets.IntSlider()
display(int_range)

def on_value_change(change):
    print(change['new'])

int_range.observe(on_value_change, names='value')

Failed to display Jupyter Widget of type IntSlider.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

matplotlib

%matplotlib inline
import matplotlib.pyplot as plt
import networkx as nx

def random_lobster(n, m, k, p):
    return nx.random_lobster(n, p, p / m)

def powerlaw_cluster(n, m, k, p):
    return nx.powerlaw_cluster_graph(n, m, p)

def erdos_renyi(n, m, k, p):
    return nx.erdos_renyi_graph(n, p)

def newman_watts_strogatz(n, m, k, p):
    return nx.newman_watts_strogatz_graph(n, k, p)

def plot_random_graph(n, m, k, p, generator):
    g = generator(n, m, k, p)
    nx.draw(g)
    plt.show()
ipywidgets.interact(plot_random_graph, n=(2,30), m=(1,10), k=(1,10), p=(0.0, 1.0, 0.001),
         generator={
             'lobster': random_lobster,
             'power law': powerlaw_cluster,
             'Newman-Watts-Strogatz': newman_watts_strogatz,
             'Erdős-Rényi': erdos_renyi,
         });

Failed to display Jupyter Widget of type interactive.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

Custom widget - text

Building a Custom Widget - Hello World.

import ipywidgets as widgets
from traitlets import Unicode, validate

class HelloWidget(widgets.DOMWidget):
    _view_name = Unicode('HelloView').tag(sync=True)
    _view_module = Unicode('hello').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)
    value = Unicode('Hello World! - ').tag(sync=True)
%%javascript
require.undef('hello');

define('hello', ["@jupyter-widgets/base"], function(widgets) {

    var HelloView = widgets.DOMWidgetView.extend({

        render: function() {
            this.value_changed();
            this.model.on('change:value', this.value_changed, this);
        },

        value_changed: function() {
            this.el.textContent = this.model.get('value');
        },
    });

    return {
        HelloView : HelloView
    };
});
<IPython.core.display.Javascript object>
w = HelloWidget()
w

Failed to display Jupyter Widget of type HelloWidget.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

w.value = 'changed the value'

Custom widget - html - svg - events

See Low Level Widget Tutorial, CircleView. The following example links a custom widget and a sliding bar which defines the radius of circle to draw. See 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).

%%javascript
require.config({
    paths: {
        d3: '//cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min'
    },
});
<IPython.core.display.Javascript object>
import ipywidgets
from traitlets import Int, Unicode, Tuple, CInt, Dict, validate

class CircleWidget(ipywidgets.DOMWidget):
    _view_name = Unicode('CircleView').tag(sync=True)
    _view_module = Unicode('circle').tag(sync=True)
    radius = Int(100).tag(sync=True)
    circles = Tuple().tag(sync=True)
    width = Int().tag(sync=True)
    height = Int().tag(sync=True)
    radius = Int().tag(sync=True)
    def __init__(self, **kwargs):
        super(ipywidgets.DOMWidget, self).__init__(**kwargs)
        self.width = kwargs.get('width', 500)
        self.height = kwargs.get('height', 100)
        self.radius = 1
    def drawCircle(self, x, y, fillColor="white", borderColor="black"):
        newCircle = {"x": x,  "y": y, "radius": self.radius * 10, "fillColor": fillColor, "borderColor": borderColor}
        self.circles = self.circles + (newCircle,)
%%javascript
"use strict";

require.undef('circle');

define('circle', ["@jupyter-widgets/base", "d3"], function(widgets, d3) {

    var CircleView = widgets.DOMWidgetView.extend({

        initialize: function() {
            console.log("---- initialize, this:");
            console.log(this);
            this.circles = [];
            this.radius = 1;
            },

        createDiv: function(){
            var width = this.model.get('width');
            var height = this.model.get('height');
            var divstyle = $("<div id='d3DemoDiv' style='border:1px solid red; height: " +
                             height + "px; width: " + width + "px'>");
            return(divstyle);
            },

        createCanvas: function(){
            var width = this.model.get('width');
            var height = this.model.get('height');
            var radius = this.model.get('radius');
            console.log("--SIZE--", width, 'x', height, " radius", radius);
            var svg = d3.select("#d3DemoDiv")
                        .append("svg")
                        .attr("id", "svg").attr("width", width).attr("height", height);

            this.svg = svg;
            var circleView = this;

            svg.on('click', function() {
                var coords = d3.mouse(this);
                //debugger;
                var radius = circleView.radius;
                console.log('--MOUSE--', coords, " radius:", radius);
                var newCircle = {x: coords[0], y: coords[1], radius: 10 * radius,
                                 borderColor: "black", fillColor: "beige"};
                circleView.circles.push(newCircle);
                circleView.drawCircle(newCircle);
                //debugger;
                circleView.model.set("circles", JSON.stringify(circleView.circles));
                circleView.touch();
                });
           },

        drawCircle: function(obj){
           this.svg.append("circle")
              .style("stroke", "gray")
              .style("fill", "white")
              .attr("r", obj.radius)
              .attr("cx", obj.x)
              .attr("cy", obj.y)
              .on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
              .on("mouseout",  function(){d3.select(this).style("fill", "white");});
            },

        render: function() {
            this.$el.append(this.createDiv());
            this.listenTo(this.model, 'change:circles', this._circles_changed, this);
            this.listenTo(this.model, 'change:radius', this._radius_changed, this);
            var circleView = this;
            function myFunc(){
               circleView.createCanvas()
               };
            setTimeout(myFunc, 500);
            },

        _circles_changed: function() {
           var circles = this.model.get("circles");
           var newCircle = circles[circles.length-1];
           console.log('--DRAW--', this.circles);
           this.circles.push(newCircle);
           console.log('--LENGTH--', circles.length, " == ", circles.length);
           this.drawCircle(newCircle);
           },

        _radius_changed: function() {
           console.log('--RADIUS--', this.radius, this.model.get('radius'));
           this.radius = this.model.get('radius');
           }
    });
    return {
        CircleView : CircleView
    };
});
<IPython.core.display.Javascript object>
cw = CircleWidget(width=500, height=100)
scale = ipywidgets.IntSlider(1, 0, 10)
box = widgets.VBox([scale, cw])
mylink = ipywidgets.jslink((cw, 'radius'), (scale, 'value'))
box

Failed to display Jupyter Widget of type VBox.

If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.

If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.

cw.drawCircle(x=30, y=30)
scale.value = 2
cw.drawCircle(x=60, y=30)