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_fnfx.make_target_tangent_fnfx.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