# imports widget side # see https://github.com/ipython/ipython/blob/2.x/examples/Interactive%20Widgets/Custom%20Widgets.ipynb # and https://github.com/ipython/ipython/blob/master/examples/Interactive%20Widgets/Custom%20Widget%20-%20Hello%20World.ipynb 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 # imports render side # see http://mpld3.github.io/examples/drag_points.html import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl import mpld3 from mpld3 import plugins, utils # imports solve side # see http://stackoverflow.com/questions/8739227/how-to-solve-a-pair-of-nonlinear-equations-using-python from scipy.optimize import fsolve #def expchelon(a, b, x): # return a * (1 - np.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)) # widget sync'd python side class GraphWidget(widgets.DOMWidget): _view_name = Unicode('GraphView', sync=True) description = 'coord' value = Unicode(sync=True) %%javascript //widget javascript side require(["widgets/js/widget", "widgets/js/manager"], function(widget, manager){ // is based on the DatePickerView var GraphView = widget.DOMWidgetView.extend({ render: function() { //@ attr id : this is the id we reach to in the dragended function in the DragPlugin this.$text = $('') .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(); }, }); manager.WidgetManager.register_widget_view('GraphView', GraphView); }); # visu plugin # based on DragPlugin class DragPlugin(plugins.PluginBase): JAVASCRIPT = r""" $('#feedback_widget').hide(); 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); // feed back the new position to python, triggering 'change' on the 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)} # fit and draw class Fit(object): def __init__(self, simulate, double_seeding=False): self.simulate = simulate # i will draw initial points at random # the number of points will increase until we match arity with the function to be fit(ted?) pseudo_fit = [] while len(pseudo_fit) < 100: # just in case, I want to avoid inifite loops... try: simulate(0, pseudo_fit) print("we have %d parameters"%len(pseudo_fit)) break except IndexError: pseudo_fit.append(1) # we generate a random cloud # the dots are distributed in (>0, >0) quadrant self.p = np.random.standard_exponential((len(pseudo_fit), 2)) # first guess ! all ones. self.fit = np.array(pseudo_fit) def make_equations(self): def equations(params): return self.p[:,1] - self.simulate(self.p[:,0], params) self.equations = equations def recalc_param(self): self.make_equations() self.fit = fsolve(self.equations, np.ones(self.fit.shape), xtol=0.01) def redraw(self, coord): # we have an update ! # record the new position for given point if coord != "": i, x, y = coord.split(",") i = int(i) self.p[i][0] = float(x) self.p[i][1] = float(y) # recalculate best fit self.recalc_param() # draw things x = np.linspace(0, 10, 50) # 50 x points from 0 to 10 y = self.simulate(x, self.fit) fig, ax = plt.subplots() points = ax.plot(self.p[:,0], self.p[:,1],'or', alpha=0.5, markersize=10, markeredgewidth=1) ax.plot(x,y,'r-') ax.set_title("Click and Drag\n, we match on : %s"%np.array_str(self.fit, precision=2), fontsize=12) plugins.connect(fig, DragPlugin(points[0])) fig_h = mpld3.display() display(fig_h) # click and drag not active here, we just show how we fit def exp_ech(x, params): return params[0] * (1 - np.exp(-params[1] * x)) # we ensure we will fit nicely by setting p[0] at [0,0] # in effect adding one degree of liberty Fit(exp_ech).redraw("0,0,0") def arctan(x, params): return params[0] * np.arctan(params[1] * x + params[2]) my_fit = Fit(arctan) # not sure why, but you can't do # interact(my_fit.redraw, coord=GraphWidget()) # so we need : def f(coord): return my_fit.redraw(coord) interact(f, coord=GraphWidget())