Custom widgets in a notebook

Links: notebook, html, PDF, python, slides, 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
DatePicker(value=datetime.datetime(2020, 1, 14, 14, 41, 54, 26999), description='Pick a Date')
obj.value
datetime.datetime(2020, 1, 14, 14, 41, 54, 26999)

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)
Button(description='Click Me!', style=ButtonStyle())
int_range = ipywidgets.IntSlider()
display(int_range)

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

int_range.observe(on_value_change, names='value')
IntSlider(value=0)
14
18
22
23
25
26
27
28
27
24
22
19
17
16
15
14
12
11
10
9
8
7
6
5
4
3
2
1
2
5
6
8
10
11
12
13
14

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,
         });
interactive(children=(IntSlider(value=16, description='n', max=30, min=2), IntSlider(value=5, description='m',…

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
HelloWidget()
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
VBox(children=(IntSlider(value=1, max=10), CircleWidget(height=100, radius=1, width=500)))
cw.drawCircle(x=30, y=30)
scale.value = 2
cw.drawCircle(x=60, y=30)