org-howto/d3/drag5/index.org

6 KiB
Raw Permalink Blame History

d3 draggable object example #5 parametric + tangent line

#

#

#

Introduction

This page extends example #4 (file:../drag4/index.org), 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: file:index-src.org

Demo

The div element #frame will appear below this line:

Prerequisites

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

Procedure

Start with example #4: we will modify the files /git/roland/org-howto/src/branch/master/d3/drag5/point.js, /git/roland/org-howto/src/branch/master/d3/drag5/fx.js, /git/roland/org-howto/src/branch/master/d3/drag5/fx_view.js.

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*/

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*/

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);

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>

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