In [25]:
# imports widget side
from __future__ import print_function # For py 2.7 compat
from IPython.html import widgets # Widget definitions
from IPython.display import display # Used to display widgets in the notebook
from IPython.utils.traitlets import Unicode # Used to declare attributes of our widget
from IPython.html.widgets import interact, interactive, fixed
In [26]:
# imports render side
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import mpld3
from mpld3 import plugins, utils
In [27]:
# imports solve side
from scipy.optimize import fsolve
import math
#http://stackoverflow.com/questions/8739227/how-to-solve-a-pair-of-nonlinear-equations-using-python
#def expchelon(a, b, x):
# return a * (1 - exp(-b * x))
#def fun(p1, p2):
# x1, y1 = p1
# x2, y2 = p2
# def equations(p):
# a, b = p
# return (y1 - expchelon(a, b, x1), y2 - expchelon(a, b, x2))
# return equations
#equations = fun((1,1), (2,4))
#a, b = fsolve(equations, (1, 1))
#print((a, b), expchelon(a, b, 1), expchelon(a, b, 2))
In [28]:
# widget sync'd python side
class GraphWidget(widgets.DOMWidget):
_view_name = Unicode('GraphView', sync=True)
description = 'coord'
value = Unicode(sync=True)
In [29]:
%%javascript
//widget javascript side
require(["widgets/js/widget"], function(WidgetManager){
// is based on the DatePickerView
var GraphView = IPython.DOMWidgetView.extend({
render: function() {
//@ attr id : this is the id we reach to in the dragended function in the DragPlugin
this.$text = $('<input />')
.attr('type', 'text')
.attr('id', 'feedback_widget')
.appendTo(this.$el);
},
update: function() {
this.$text.val(this.model.get('value'));
return GraphView.__super__.update.apply(this);
},
events: {"change": "handle_change"},
handle_change: function(event) {
this.model.set('value', this.$text.val());
this.touch();
},
});
WidgetManager.register_widget_view('GraphView', GraphView);
});
In [30]:
# visu plugin
class DragPlugin(plugins.PluginBase):
JAVASCRIPT = r"""
mpld3.register_plugin("drag", DragPlugin);
DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);
DragPlugin.prototype.constructor = DragPlugin;
DragPlugin.prototype.requiredProps = ["id"];
DragPlugin.prototype.defaultProps = {}
function DragPlugin(fig, props){
mpld3.Plugin.call(this, fig, props);
mpld3.insert_css("#" + fig.figid + " path.dragging",
{"fill-opacity": "1.0 !important",
"stroke-opacity": "1.0 !important"});
};$
DragPlugin.prototype.draw = function(){
var obj = mpld3.get_element(this.props.id);
var drag = d3.behavior.drag()
.origin(function(d) { return {x:obj.ax.x(d[0]),
y:obj.ax.y(d[1])}; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
obj.elements()
.data(obj.offsets)
.style("cursor", "default")
.call(drag);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d, i) {
d[0] = obj.ax.x.invert(d3.event.x);
d[1] = obj.ax.y.invert(d3.event.y);
d3.select(this)
.attr("transform", "translate(" + [d3.event.x,d3.event.y] + ")");
}
function dragended(d,i) {
d3.select(this).classed("dragging", false);
//console.log(this);
// feed back the new position to python, calling the feedback widget
$('#feedback_widget').val("" + i + "," + d[0] + "," + d[1]).trigger("change");
}
}"""
def __init__(self, points):
if isinstance(points, mpl.lines.Line2D):
suffix = "pts"
else:
suffix = None
self.dict_ = {"type": "drag",
"id": utils.get_id(points, suffix)}
In [31]:
# fit and draw
def expchelon(a, b, x):
return a * (1 - exp(-b * x))
def make_equations(p1, p2):
x1, y1 = p1
x2, y2 = p2
def equations(p):
a, b = p
return (y1 - expchelon(a, b, x1), y2 - expchelon(a, b, x2))
return equations
class Fit(object):
def __init__(self, p):
self.p = p
def recalc_param(self):
x1 = self.p[0][0]
y1 = self.p[0][1]
x2 = self.p[1][0]
y2 = self.p[1][1]
#a = (y2 - y1) / (x2 - x1)
#b = y2 - a * x2
_equations = make_equations(*self.p)
a, b = fsolve(_equations, (1, 1))
return a, b
def redraw(self, coord):
#asymptote = 1
#speed = 2
if coord != "":
i, x, y = coord.split(",")
i = int(i)
self.p[i][0] = float(x)
self.p[i][1] = float(y)
#print(i,x,y)
asymptote, speed = self.recalc_param()
x = np.linspace(0,10,50) # 50 x points from 0 to 10
y = asymptote * (1.0 - np.exp(- speed * x))
#y = a * x + b
#print(coord)
fig, ax = plt.subplots()
points = ax.plot([xy[0] for xy in self.p],
[xy[1] for xy in self.p],
'or', alpha=0.5,
markersize=10, markeredgewidth=1)
ax.plot(x,y,'r-')
ax.set_title("Click and Drag, we match on asym : %2.2f speed : %2.2f"%(asymptote, speed), fontsize=18)
plugins.connect(fig, DragPlugin(points[0]))
fig_h = mpld3.display()
display(fig_h)
In [32]:
# click and drag not active here, we just show how we fit
Fit([[1, 1.5], [6, 4]]).redraw("")
In [38]:
my_fit = Fit([[2, 6], [6, 7]])
def f(coord):
return my_fit.redraw(coord)
interact(f, coord=GraphWidget()) # you can hide the hugly details somewhere in code with what's in next cell
In [39]:
%%javascript
$('#feedback_widget').hide();