import sys, time
from IPython.utils.traitlets import HasTraits, Integer, Unicode, Bool, Instance, Float
from IPython.display import *
from IPython.kernel.comm import Comm
%%javascript
var TestWidget = function (comm) {
this.comm = comm;
this.comm.on_msg($.proxy(this.handler, this));
// get the cell that was probably executed
// msg_id:cell mapping will make this possible without guessing
this.cell = IPython.notebook.get_cell(IPython.notebook.get_selected_index()-1);
this.callbacks = {
iopub : {
output : $.proxy(this.cell.output_area.handle_output, this.cell.output_area)
}
};
};
TestWidget.prototype.handler = function(msg) {
console.log('handle', this, msg, this.cell.output_area);
this.comm.send({ b:10 }, this.callbacks);
};
IPython.notebook.kernel.comm_manager.register_target('test', IPython.utils.always_new(TestWidget));
comm = Comm(target_name='test')
{'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'A122E95D77AF4CBB8A7217FB545CE6D9', 'content': {u'data': {u'b': 10}, u'comm_id': u'fe2aadee4edd447eab4e70db35329167'}, 'header': {u'username': u'username', u'msg_id': u'A122E95D77AF4CBB8A7217FB545CE6D9', u'msg_type': u'comm_msg', u'session': u'10E950937F064BF189265D59D6AD2262'}, 'buffers': [], 'metadata': {}}
comm.send(dict(a=5))
def handler(msg):
print >> sys.__stderr__, msg
print msg
comm.on_msg(handler)
comm.send(dict(a=10))
import uuid
class Widget(HasTraits):
target_name = Unicode("widget")
id = Unicode()
def _id_default(self):
return 'widget-%s' % uuid.uuid4().hex
comm = Instance(Comm)
def _comm_default(self):
data = self.data
data['id'] = self.id
return Comm(data, target_name=self.target_name)
@property
def data(self):
return {}
class RangeWidget(Widget):
target_name = Unicode("range")
min = Float(0)
max = Float(100)
value = Float(50)
step = Float(1)
_keys = ['min', 'max', 'value', 'step']
_in_handler = Bool(False)
def _validate_and_update(self, name, old, new):
if self.value > self.max:
raise ValueError("value %d exceeds max %d" % (self.value, self.max))
if new < self.min:
raise ValueError("value %d below min %d" % (self.value, self.min))
if not self._in_handler:
self.update()
def __init__(self, **kwargs):
super(RangeWidget, self).__init__(**kwargs)
self.comm.on_msg(self.handle_msg)
# register on_trait_change after init
# otherwise default values can't be specified
self.on_trait_change(self._validate_and_update, self._keys)
@property
def data(self):
return {key:getattr(self, key) for key in self._keys}
def update(self):
self.comm.send(self.data)
def __repr__(self):
return "<Range (%.1f) %.1f:%.1f:%.1f>" % (self.value, self.min, self.max, self.step)
def handle_msg(self, msg):
self._in_handler = True
data = msg['content']['data']
try:
for key in self._keys:
if key in data:
setattr(self, key, data[key])
finally:
self._in_handler = False
sys.stdout.write('\r%s' % self)
sys.stdout.flush()
def _repr_html_(self):
return """<div class="%s"></div>""" % self.id
def display(self):
display(self)
self.update()
The Javascript side
%%javascript
var RangeWidget = function (comm, msg) {
this.comm = comm;
comm.on_msg($.proxy(this.handle_msg, this));
// get the cell that was probably executed
// msg_id:cell mapping will make this possible without guessing
this.cell = IPython.notebook.get_cell(IPython.notebook.get_selected_index()-1);
this.callbacks = {
iopub : {
output : $.proxy(this.cell.output_area.handle_output, this.cell.output_area)
}
};
this.keys = ['min', 'max', 'step'];
this.data = msg.content.data;
this.id = this.data.id;
this.get_div();
};
RangeWidget.prototype.get_div = function () {
var div = $("div." + this.id);
if (div.length === 0) {
return null;
}
if (div.find("input").length === 0) {
this.create_range(div);
}
return div;
};
RangeWidget.prototype.create_range = function (thediv) {
var range = $('<input></input>').attr('type', 'range');
var data = this.data;
for (var i=0; i < this.keys.length; i++) {
var key = this.keys[i];
range.attr(key, data[key]);
}
range.val(data.value);
thediv.append(
$("<span/>").addClass("min").text(data.min)
).append(
range
).append(
$("<span/>").addClass("max").text(data.max)
).append(
$("<br/>")
).append(
$("<span/>").addClass("value").text(data.value)
);
range.on("change", $.proxy(this.value_changed, this));
}
RangeWidget.prototype.value_changed = function (evt) {
var now = new Date();
if (now - this.last_changed < 100) {
// only update every 0.1s, max
// return;
}
var div = this.get_div();
var range = div.find("input");
var data = {};
for (var i=0; i < this.keys.length; i++) {
var key = this.keys[i];
data[key] = parseFloat(range.attr(key));
}
data.value = parseFloat(range.val());
div.find("span.min").text(data.min);
div.find("span.max").text(data.max);
div.find("span.value").text(data.value);
this.comm.send(data, this.callbacks);
this.last_changed = now;
};
RangeWidget.prototype.handle_msg = function (msg) {
var data = msg.content.data;
this.data = data;
var div = this.get_div();
if (!div) {
return;
}
var range = div.find("input");
for (var i=0; i < this.keys.length; i++) {
var key = this.keys[i];
range.attr(key, data[key]);
}
div.find("span.min").text(data.min);
div.find("span.max").text(data.max);
div.find("span.value").text(data.value);
range.val(data.value);
};
IPython.notebook.kernel.comm_manager.register_target('range', IPython.utils.always_new(RangeWidget));
Create the Python object, and register it with the WidgetManager. Registration triggers creation of the javascript-side counterpart.
r = RangeWidget()
r.display()
<Range (54.0) 0.0:100.0:1.0>
r.value = 10
print r
<Range (10.0) 0.0:100.0:1.0>
import math, time
import numpy as np
x = np.linspace(0,10,1000)
y = 50 + (np.sin(x) * 50)
for n in y:
r.value = (n)
time.sleep(0.005)
from IPython.utils.traitlets import List
class FlotWidget(Widget):
lines = List()
target_name = Unicode('flotplot')
def show(self):
self.comm.send(dict(lines=self.lines))
def plot(self, x, y):
self.lines.append(list(zip(x,y)))
self.show()
def clear(self):
self.lines = []
self.show()
def display(self):
display(self)
self.clear()
def _repr_html_(self):
return '<div id="%s" class="flotwidget"></div>' % self.id
%%javascript
$.getScript('//cdnjs.cloudflare.com/ajax/libs/flot/0.8.1/jquery.flot.min.js');
$("<style type='text/css'> .flotwidget{ width: 100%; height: 300px;}</style>").appendTo("head");
var FlotWidget = function (comm, msg) {
this.comm = comm;
this.id = msg.content.data.id;
comm.on_msg($.proxy(this.handle_msg, this));
};
FlotWidget.prototype.handle_msg = function (msg) {
console.log(msg)
$.plot($('#' + this.id), msg.content.data.lines);
}
IPython.notebook.kernel.comm_manager.register_target('flotplot', IPython.utils.always_new(FlotWidget));
flt = FlotWidget()
flt.display()
x = np.linspace(0,5,200)
y = np.sin(x)
flt.plot(x,np.sin(x))
flt.plot(x,np.cos(x))
flt.plot(x, np.sin(2*x))
flt.plot(x, np.sin(3*x))
flt.clear()
from IPython.utils.traitlets import HasTraits, Instance, Any
class SineOmegaT(HasTraits):
omega = Instance(RangeWidget, kw=dict(min=0, max=10, step=0.1, value=1), allow_none=False)
flot = Instance(FlotWidget, args=())
# can't use traitlets with numpy arrays
t = None
y = None
_in_omega_changed = False
def _omega_value_changed(self, name, old, new):
self.y = np.sin(new * self.t)
self.show()
def __init__(self):
super(SineOmegaT, self).__init__()
self.omega.on_trait_change(self._omega_value_changed, 'value')
self.t = np.linspace(0,10,200)
self.y = np.sin(self.omega.value * self.t)
# add HTML to the dom
display(self)
# ensure omega slider is drawn
self.omega.update()
self.last_draw = 0
self.show()
def show(self):
now = time.time()
if False and (now - self.last_draw) < 0.1:
return
self.last_draw = now
self.flot.lines = []
self.flot.plot(self.t, self.y)
def display(self):
display(self)
self.show()
def _repr_html_(self):
return u"<div style='text-align: center; font-size: 200%%;'>$sin(\omega t)$</div><br>%s<br>$\omega$: %s" % (self.flot._repr_html_(), self.omega._repr_html_())
sino = SineOmegaT()
<Range (21.0) 0.0:30.0:1.0>
sino.omega.max = 30
sino.omega.step = 1
sino.t = np.linspace(0,10,10)
sino.show()
class RPCWidget(object):
def __init__(self, comm, msg):
self.comm = comm
self.comm.on_msg(self.handle_msg)
@property
def shell(self):
return get_ipython()
def add(self, a, b):
return a + b
def mul(self, x, y):
return x * y
def get_execution_count(self):
return self.shell.execution_count
def handle_msg(self, msg):
data = msg['content']['data']
method = getattr(self, data['method'])
args = data.get('args', ())
kwargs = data.get('kwargs', {})
result = method(*args, **kwargs)
self.comm.send(dict(result=result, method=data['method']))
get_ipython().comm_manager.register_target('rpc', RPCWidget)
%%javascript
var RPCWidget = function (comm) {
this.comm = comm;
// get the cell that was probably executed
// msg_id:cell mapping will make this possible without guessing
this.comm.on_msg($.proxy(this.handle_msg, this));
this.cell = IPython.notebook.get_cell(IPython.notebook.get_selected_index()-1);
this.callbacks = {
iopub : {
output : $.proxy(this.cell.output_area.handle_output, this.cell.output_area)
}
};
this.target = 'rpc';
};
RPCWidget.prototype.handle_msg = function (msg) {
this.cell.output_area.handle_output({
header : {
msg_type : 'stream',
msg_id : '',
},
parent_header : msg.parent_header,
content : {
name: 'stdout',
data: JSON.stringify(msg.content.data) + "\n"
}
});
}
RPCWidget.prototype.add = function (a,b) {
this.comm.send({method : 'add', args : [a,b]}, this.callbacks);
}
RPCWidget.prototype.mul = function (a,b) {
this.comm.send({method : 'mul', args : [a,b]}, this.callbacks);
}
RPCWidget.prototype.get_ec = function () {
this.comm.send({method : 'get_execution_count'}, this.callbacks);
}
var comm = new IPython.Comm(IPython.utils.uuid(), 'rpc');
IPython.notebook.kernel.comm_manager.register_comm(comm);
comm.open();
var rpc = new RPCWidget(comm);
rpc.add(5,3);
rpc.get_ec();
rpc.mul(6,6);
window.rpc = rpc;
{"method":"add","result":8} {"method":"get_execution_count","result":44} {"method":"mul","result":36} {"method":"mul","result":5000}
%%javascript
window.rpc.mul(10,500);