import warnings
warnings.filterwarnings('ignore')
from ipywidgets import HTML
HTML("<style>.image { background-color:white }</style>")
We will see:
Illustrate the rich interaction features offered by Jupyter
In this talk, we will illustrate the rich interactive features offered by Jupyter. Indeed, beyond the traditional REPL (Read-Eval-Print loop), Jupyter offers a cross-language toolbox of interactive visual components – called widgets – from which users can build and share their own interactive applications. This toolbox has been adopted and extended by the community which has developed visualization components for various applications. A key feature of Jupyter widgets is the progressive learning curve which blurs the line between notebook readers, notebook authors, and developers.
We will start with a few “interacts” – a feature well know to Mathematica or Sage users – to build with a handful of lines of code some simple yet effective mini applications where the input of a function is chosen with visual controls (e.g. sliders). We will then illustrate the process of building applications with richer interactions from the tool box. Finally, we will demonstrate two Python/SageMath packages that we have developed based on Jupyter widgets. The first one – Sage-Combinat-widgets – is a library of widgets for the interactive edition of certain types of combinatorial objects. The second one – Sage-Explorer – is an application for interactive visual exploration of objects in Sage. Both can be combined or integrated in larger applications.
Along the way, we will reflect on our experience, trying to evaluate the expertise and development time required for each use case. We will stress at this occasion the role played by dedicated Research Software Engineers and suggest incentives for building and animating a rich user community. Beyond the usual REPL/Read-Eval-Print loop, which interactivity modes does Jupyter offer? We will show examples of so-called widgets, ie interactive visual components, from simple web form controls to complex applications.
We will explain how to build simple yet efficient interactive applications with only a handful lines of code.
To illustrate the power of Jupyter widgets, we will demonstrate a few applications we use to introspect combinatorial objects and run calculations on them.
We will explain how to integrate such widgets as building blocks to larger but still modular interactive applications.
Finally, browsing our own development process, we will try to distinguish between rather easy tasks with short learning curve, and those requiring more development time and/or expertise. We will stress the role played by dedicated Research Software Engineers and reveal incentives for building and animating a rich user community.
Suppose we want to study how Taylor series can approximate the $sinus$ function.
$$sin: x \mapsto sin(x)$$s = taylor(sin(x), x, 0, 5)
plot([x, s,sin], xmin=-5, xmax=5, ymin=-2, ymax=2, color=["gray", "blue","red"])
Now we want to explore how the approximation evolves as we change the degree. Typing in each time the new degree, and reevaluating the cell -- i.e using the Read Eval Print Loop is rather unconvenient.
@interact(degree=(1,25,2), continuous_update=False)
def f(degree=1):
s = taylor(sin(x), x, 0, degree)
display(plot([x, s,sin], xmin=-5, xmax=5, ymin=-2, ymax=2, color=["gray", "blue","red"]))
publish: binder, cocalc services
export: voila library
@interact makes up a standalone component
Let's see an example application
import random
import copy
from bqplot import Bars, LinearScale, Figure
from ipywidgets import IntSlider, Button, HTML, VBox
def mixup(l, duration):
for t in range(duration):
i = random.randint(0, len(l)-1)
j = random.randint(0, len(l)-1)
l[i], l[j] = l[j], l[i]
l = list(range(10)); l
mixup(l, 10); l
def mixup(l, duration):
history = [copy.copy(l)]
for t in range(duration):
i = random.randint(0, len(l)-1)
j = random.randint(0, len(l)-1)
l[i], l[j] = l[j], l[i]
history.append(copy.copy(l))
return history
l = list(range(10))
history = mixup(l, 100)
l = history[0]
bars = Bars(x=range(len(l)), y = l, scales={'x': LinearScale(), 'y': LinearScale()})
w = Figure(marks=[bars])
slider = IntSlider(0, 0, 99) # equiv. IntSlider(min=0, max=99)
def update(change):
w.marks[0].y = history[change["new"]]
slider.observe(update, names='value')
VBox((slider, w))
current_index = 0
bars = Bars(x=range(len(l)), y = history[current_index], scales={'x': LinearScale(), 'y': LinearScale()})
w = Figure(marks=[bars])
button = Button(description="Go!")
label = HTML()
def step():
global current_index
current_index += 1
w.marks[0].y = history[current_index]
label.value = '<p style="font-size:24px; color:tomato">Step #%d</p>' % current_index
def button_clicked(b):
step()
button.on_click(button_clicked)
VBox((button, w, label))
from ipywidgets import HTML
from traitlets import Int, observe
class Indicator(HTML):
step= Int()
@observe('step')
def step_changed(self, change):
self.value = '<p style="font-size:24px; color:tomato">Step #%d</p>' % self.step
current_index = 0
bars = Bars(x=range(len(l)), y = history[current_index], scales={'x': LinearScale(), 'y': LinearScale()})
w = Figure(marks=[bars])
button = Button(description="Go!")
label = Indicator()
def step():
global current_index
current_index += 1
w.marks[0].y = history[current_index]
label.step = int(current_index)
def button_clicked(b):
step()
button.on_click(button_clicked)
VBox((button, w, label))
Jupyter interacts were inspired by SageMath which themselves were inspired by Mathematica. Their strength is the simplicity: a little decorator and here is an application. Yet sometimes this idiom is too rigid. In such cases, we want to control ourselves how the application is built by composition of building blocks.
from ipywidgets import *
b = Button(description="Big button", layout=Layout(height="60px", width="100px"))
t = Textarea(layout=Layout(height="160px"))
s = Select(options=("1", "2", "3"))
d = Dropdown(options=("1", "2", "3"))
b.style.button_color="orange" # Some styling for our button
b.style.font_weight = "bold"
t.value="a rather high text area"
s.value="3"
HBox((b,t,s)) # Horizontal box
GridBox, Accordion, Tabs ..
List of available widgets
https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html
'ipywidgets list'
GridBox((b,b,b,d,d,d,t,t,t), layout=Layout(grid_template_columns="repeat(3, 300px)"))
A few examples
We started by building a generic widget to edit interactively combinatorial objects that have a natural representation in a grid: partitions, Young tableaux, etc, with visual feedback for forbidden operations. Let’s see this in action with a Young tableau:
from sage_combinat_widgets import GridViewWidget
t = StandardTableaux(15).random_element()
GridViewWidget(t)
from sage_explorer import explore
explore(sin)
explore(EllipticCurve([0, -1, 1, -10, -20]))
sk = SkewPartition([[7, 4, 2, 1],[2, 1, 1]])
explore(sk)
# Francy Widget is an ipywidgets integration of Francy by Manuel Martins
from francy_widget import FrancyWidget
import networkx
G1 = networkx.Graph([(1,2),(2,3),(3,4)])
FrancyWidget(G1, counter=0, title="Undirected Graph Example")
You will find more spectacular examples on Francy Widget git repository: https://github.com/zerline/francy-widget
Let me just show you one here.
Amalie Emmy Noether (23 March 1882 – 14 April 1935) was a German mathematician who made important contributions to abstract algebra and theoretical physics (https://en.wikipedia.org/wiki/Emmy_Noether).
According to the math genealogy project, Emmy Noether had 14 doctoral students, who had 76 themselves, ... so until now she has 1365 descendants.
import networkx, json
from francy_widget import FrancyWidget
G = networkx.DiGraph()
data = json.load(open("noether.json"))
nodes = data["nodes"]
#print(len(nodes))
nodes_to_keep = {k:nodes[k] for k in nodes if nodes[k][0]<4}
edges_to_keep = [e for e in data["edges"] if e[1] in nodes_to_keep]
G.add_edges_from(edges_to_keep)
def node_options(n):
options = {}
d = nodes[n]
options["layer"] = d[0]
options["title"] = "%s (%s)" % (d[2].split(",")[0], d[3])
if n in ["6967", "63779", "6982", "29850", "121808", "191816",
"54355", "98035", "44616", "57077", "21851"]:
options["type"] = 'diamond'
else:
options["type"] = 'circle'
return options
FrancyWidget(G, graphType="directed", height=800, zoomToFit=False,
node_options=node_options)
We have seen various use cases for interactive visualization.
The examples given in parts 1 and 2 are straightforward and you can start building your own.
For the more advanced ones, you might want to hire a Research Software Engineer and integrate her in your academic community!
Modules used for this presentation: * ipywidgets * bqplot * random * copy * traitlets * sage_combinat_widgets * sage_explorer * francy_widget * RISE