UP | HOME

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:

3. Prerequisites

As for examples #1, #2, #3, #4

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

Author: Roland Conybeare

Created: 2024-09-08 Sun 18:01

Validate