UP | HOME

d3/drag5 .org source

#+title: d3 draggable object example #5 -- parametric + tangent line
#
# org-publish options
# H:2   controls section numbering.
#       number top-level and second-level headings only
# ^:{}  require a_{b} before assuming that b should be subscripted.
#       without this option a_b will automatically subscript b.
#+options: ^:{}
#
# options used exclusively by emacs
#+startup: showall
#
# options used exclusively by the html exporter
#+language: en
#+infojs_opt: view:showall toc:nil ltoc:nil mouse:#ffc0c0 path:/web/ext/orginfo/org-info.js
#+html_head: <script type="text/javascript" src="/web/ext/d3/d3.v3.min.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>
#+html_head: <link rel="stylesheet" type="text/css" href="/web/css/notebook.css" />
#+html_link_home: ../../index.html
#+html_link_up: ../../index.html

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

  #+begin_export html
  <div id="frame"></div>
  <script type="text/javascript">
    window.onload = function() { ex.start(this); }
  </script>
  #+end_export

* Prerequisites

  As for examples [[file:../drag1/index.org][/#1/]], [[file://drag2/index.org][/#2/]], [[file:~/proj/org-howto/d3/drag3/index.org][/#3/]], [[file:~/proj/org-howto/d3/drag4/index.org][/#4/]]

* Procedure

  Start with example [[file:~/proj/org-howto/d3/drag4/index.org][/#4/]]: we will modify the files [[file:point.js]], [[file:fx.js]], [[file:fx_view.js]].

** Extend ~fx.js~
   Add functions:
   - ~fx.target_deriv_fn~
   - ~fx.make_target_tangent_fn~
   - ~fx.linear_inverse_fn~

   #+begin_example
     /* 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*/
   #+end_example

   #+begin_example
     /* 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*/
   #+end_example

   #+begin_example
     /* 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*/
   #+end_example

** Upgrade ~fx_view.draw~
   Extend ~fx_view.draw~ to draw tangent function:

   #+begin_example
    /* 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*/
   #+end_example

** Upgrade ~parametric-drag-example.js~
   Extend ~ex.start~ to supply new conversion to ~fx.draw~

   #+begin_example
     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);
   #+end_example

** 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:
   #+begin_example
    ,#+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>
   #+end_example

** Insert html fragment to invoke our interactive javascript code
   This also follows the same model we used in example /#3/, example /#4/.
   #+begin_example
    ,#+begin_html
    <div id="frame"></div>
    <script type="text/javascript">
      window.onload = function() { ex.start(this); }
    </script>
    #+end_html
  #+end_example

Author: Roland Conybeare

Created: 2024-09-08 Sun 18:01

Validate