We can use a resource called d3 to great very complex user interfaces in a Notebook.
In this example, a cluster of balls is created that moves around as the computer mouse chases the balls.
This code was poached from here:
https://github.com/skariel/IPython_d3_js_demo/blob/master/d3_js_demo.ipynb
There are three cells in this example.
In [1]:
%%writefile f1.template
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script type="text/javascript" src="https://mbostock.github.io/d3/talk/20111018/d3/d3.js"></script>
<script type="text/javascript" src="https://mbostock.github.io/d3/talk/20111018/d3/d3.geom.js"></script>
<script type="text/javascript" src="https://mbostock.github.io/d3/talk/20111018/d3/d3.layout.js"></script>
<style type="text/css">
circle {
stroke: #000;
stroke-opacity: .5;
}
</style>
<body>
<div id="body">
<script type="text/javascript">
var w = {width},
h = {height};
var nodes = d3.range({ball_count}).map(function() { return {radius: Math.random() * {rad_fac} + {rad_min}}; }),
color = d3.scale.category10();
var force = d3.layout.force()
.gravity(0.1)
.charge(function(d, i) { return i ? 0 : -2000; })
.nodes(nodes)
.size([w, h]);
var root = nodes[0];
root.radius = 0;
root.fixed = true;
force.start();
var svg = d3.select("#body").append("svg:svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("svg:circle")
.attr("r", function(d) { return d.radius - 2; })
.style("fill", function(d, i) { return color(i % {color_count}); });
force.on("tick", function(e) {
var q = d3.geom.quadtree(nodes),
i = 0,
n = nodes.length;
while (++i < n) {
q.visit(collide(nodes[i]));
}
svg.selectAll("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
svg.on("mousemove", function() {
var p1 = d3.svg.mouse(this);
root.px = p1[0];
root.py = p1[1];
force.resume();
});
function collide(node) {
var r = node.radius + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2
|| x2 < nx1
|| y1 > ny2
|| y2 < ny1;
};
}
</script>
</body>
</html>
In [2]:
from IPython.display import IFrame
import re
def replace_all(txt,d):
rep = dict((re.escape('{'+k+'}'), str(v)) for k, v in d.items())
pattern = re.compile("|".join(rep.keys()))
return pattern.sub(lambda m: rep[re.escape(m.group(0))], txt)
count=0
def serve_html(s,w,h):
import os
global count
count+=1
fn= '__tmp'+str(os.getpid())+'_'+str(count)+'.html'
with open(fn,'w') as f:
f.write(s)
return IFrame('files/'+fn,w,h)
def f1(w=500,h=400,ball_count=150,rad_min=2,rad_fac=11,color_count=3):
d={
'width' :w,
'height' :h,
'ball_count' :ball_count,
'rad_min' :rad_min,
'rad_fac' :rad_fac,
'color_count':color_count
}
with open('f1.template','r') as f:
s=f.read()
s= replace_all(s,d)
return serve_html(s,w+30,h+30)
In [3]:
f1(ball_count=50, color_count=17, rad_fac=10, rad_min=3, w=600)
Out[3]:
In [ ]: