d3 draggable object example #5 – parametric + tangent line
Table of Contents
1. Introduction
This page extends example #4 (../drag4/index.html), modifying the algorithm for associating mouse coordinates with a point on the function so that we get smooth dragging behavior
- source
- org-mode source for this page is here: index-src.html
2. Demo
The div element #frame
will appear below this line:
4. Procedure
Start with example #4: we will modify the files point.js, fx.js, fx_view.js.
4.1. Extend fx.js
Add functions:
fx.target_deriv_fn
fx.make_target_tangent_fn
fx.linear_inverse_fn
/* derivative f'(x) of fx.target_fn * x :: number * returns :: number */ fx.target_deriv_fn = function(x) { return x * (3 * x - 1.2); } /*target_deriv_fn*/
/* the (linear) function of (x) that is * tangent to fx.target_fn at x0 */ fx.make_target_tangent_fn = function(x0) { var fx0 = fx.target_fn(x0); /*f(x0)*/ var dfx0 = fx.target_deriv_fn(x0); /*f'(x0)*/ return function(x) { return fx0 + (x - x0) * dfx0; } } /*make_target_tangent_fn*/
/* inverse of fx.linear_fn: * given scale k, {offset_x, offset_y} * return function * f({x,y}) = {(x - offset_x)/scale, (y - offset_y)/scale} */ fx.linear_inverse_fn = function(offset_pt, scale) { /* p :: Point */ return function(p) { return pt.scale_pt(1.0 / scale, pt.sub_pt(p, offset_pt)); } } /*linear_inverse_fn*/
4.2. Upgrade fx_view.draw
Extend fx_view.draw
to draw tangent function:
/* parent_id :: string. pass this to d3.select() to get selection for parent * at which to attach svg box * box_pt :: Point. size of svg bounding box * fx2scr_fn :: Point -> Point * scr2fx_fn :: Point -> Point */ fx_view.draw = function(parent_id, box_pt, target_pt_v, fx2scr_fn, scr2fx_fn) { /* create an svg bounding box, to contain interactive drawing area */ fx_view.box = (d3.select(parent_id) .append("svg") .attr("class", "box") .attr("width", box_pt.x) .attr("height", box_pt.y)); /* border, so bounding box is visible */ fx_view.border = (fx_view.box.append("svg:rect") .attr("class", "border") .attr("x", 1) .attr("y", 1) .attr("width", box_pt.x - 2) .attr("height", box_pt.y - 2) .attr("stroke", "navy") .attr("stroke-width", 3) .style("fill", "none")); /* create path representing our target function f(x) */ fx_view.fx_path = (fx_view.box.append("path") .attr("d", fx_view.svg_line_fn(target_pt_v)) .attr("stroke", "navy") .attr("stroke-width", 2) .attr("fill", "none") ); fx_view.fx_update_tangent_fn(scr2fx_fn(pt.scale_pt(0.5, box_pt)).x, box_pt, fx2scr_fn, scr2fx_fn); fx_view.fx_update_select_circle(pt.find_closest(pt.scale_pt(0.5, box_pt), target_pt_v)); } /*draw*/
4.3. Upgrade parametric-drag-example.js
Extend ex.start
to supply new conversion to fx.draw
ex.screen2pt = fx.linear_inverse_fn(pt.sub_pt(pt.scale_pt(0.5, ex.box_pt), fx.eval_fn(0.0) /*ctr_fx*/), 200.0 /*scale_factor*/); ... fx_view.draw("#frame", ex.box_pt, ex.target_pt_v, ex.pt2screen, ex.screen2pt);
4.4. Load ~.js_ files in html header
This step is identical to the similar step in example #3, example #4
At the top of the .org
file:
#+html_head: <script type="text/javascript" src="/ext/d3/d3.js"></script> #+html_head: <script type="text/javascript" src="point.js"></script> #+html_head: <script type="text/javascript" src="fx.js"></script> #+html_head: <script type="text/javascript" src="fx_view.js"></script> #+html_head: <script type="text/javascript" src="parametric-drag-example.js"></script>
4.5. Insert html fragment to invoke our interactive javascript code
This also follows the same model we used in example #3, example #4.
#+begin_html <div id="frame"></div> <script type="text/javascript"> window.onload = function() { ex.start(this); } </script> #+end_html