We will see:
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)
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