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