jQuery: Cytoscape.js お試せた17 (layout 混合は使い物になるだろうか + nodes.shift())

意味通じてる?

jQuery: Cytoscape.js お試せた17 (layout 混合は使い物になるだろうか + cy.shift())

前置き

jQuery: Cytoscape.js お試せた6.5 (ノードを「本当に」動的に追加 – undocumented な makeLayout の話)で、automove のデモで makeLayout に気付いた、という話をしたでしょう。このコードをもう一度注意深くみてもらうとわかるんだけれど、

  1. 一部ノードだけ選んで適用可能
  2. boundingBox も指定できてしまう

のよね。(ただし後者は、「この範囲から出るな」ということになってしまうので、「この範囲にレンダリングしたまえよ」として使いたいならちょっと目的のものと違う。)

ということはつまり、ノードごとにバラバラの layout を適用出来る、のだとしたら、どんなユースケースが考えられるだろうか、ということ。というか「ワタシが今作ってるヤツでオイシイかしら」てことである。

レイアウト混合の前に、「選択したノードを一括で移動」を

マウスドラッグ操作で移動する話ではなくて、「検索で合致したノードを選択状態に(ユーザからみると自動で)出来ている」として、そうして選択状態になったものを、「ドラッグ以外の操作で」移動する、という話である。

この目的には nodes.shift() を使う、というだけの話。(これをしたくて このネタね。)

そうだけど、「気持ちのいい動き」をさせたければ、ちょっとだけ凝ったことをしないとね、てことね。順に説明するのもダルいので、書いたコードを先に貼り付ける:

 1 <!-- ... -->
 2 <i id="node-shift-nw"
 3    class="node-shift fa fa-arrow-right fa-2x"
 4    aria-hidden="true"
 5    style="transform: rotate(-135deg)"></i>
 6 <i id="node-shift-n"
 7    class="node-shift fa fa-arrow-right fa-2x"
 8    aria-hidden="true"
 9    style="transform: rotate(-90deg)"></i>
10 <i id="node-shift-ne"
11    class="node-shift fa fa-arrow-right fa-2x"
12    aria-hidden="true"
13    style="transform: rotate(-45deg)"></i><br/>
14 <i id="node-shift-w"
15    class="node-shift fa fa-arrow-right fa-2x"
16    aria-hidden="true"
17    style="transform: rotate(180deg)"></i>
18 <i id="node-shift-fit"
19    class="fa fa-expand fa-2x"
20    aria-hidden="true"
21    title="fit"></i>
22 <i id="node-shift-e"
23    class="node-shift fa fa-arrow-right fa-2x"
24    aria-hidden="true"></i><br/>
25 <i id="node-shift-sw"
26    class="node-shift fa fa-arrow-right fa-2x"
27    aria-hidden="true"
28    style="transform: rotate(135deg)"></i>
29 <i id="node-shift-s"
30    class="node-shift fa fa-arrow-right fa-2x"
31    aria-hidden="true"
32    style="transform: rotate(90deg)"></i>
33 <i id="node-shift-se"
34    class="node-shift fa fa-arrow-right fa-2x"
35    aria-hidden="true"
36    style="transform: rotate(45deg)"></i>
37 
38 <!-- ... -->
39 
40 <script>
41 // cy は初期化済みとして…
42 
43 $("#node-shift-fit").on("click", function(event) {
44     cy.fit(cy.elements("node:selected"));
45 });
46 var nodeshift_dirmap = {
47     "nw": {x: -71, y: -71},
48     "n": {x: 0, y: -100},
49     "ne": {x: 71, y: -71},
50     "w": {x: -100, y: 0},
51     "e": {x: 100, y: 0},
52     "sw": {x: -71, y: 71},
53     "s": {x: 0, y: 100},
54     "se": {x: 71, y: 71},
55     };
56 var nodeshift_factor_map = {};
57 for (var _i = 0; _i < 10; ++_i) {
58     nodeshift_factor_map[_i] = Math.pow((_i + 1) / 10., 2);
59 }
60 var nodeshift_intervals = {};
61 $(".node-shift").on("mousedown", function(event) {
62     var nid = $(this).attr("id");
63     var dir = nodeshift_dirmap[nid.split("-")[2]];
64     if (!(nid in nodeshift_intervals)) {
65         nodeshift_intervals[nid] = {
66             timer: new Interval(function(_nid) {
67                 var eles = cy.elements("node:selected");
68                 if (eles) {
69                     var c = nodeshift_intervals[nid].count;
70                     var f = nodeshift_factor_map[c] || 1.0;
71                     eles.shift({x: dir.x * f, y: dir.y * f});
72                     nodeshift_intervals[nid].count++;
73                 }
74             }, 100, nid),
75             count: 0,
76         };
77     }
78 });
79 $(".node-shift").on("mouseup", function(event) {
80     var nid = $(this).attr("id");
81     nodeshift_intervals[nid].timer.clear();
82     delete nodeshift_intervals[nid];
83 });
84 
85 </script>

Interval は これね。

東西南北と移動量の関係の基本はすぐに読める? まず第一に、座標系が「(0, 0) は (left, top)」であることに注意。北に移動したければ、「減算」する。あと「斜め」と「真横」の移動距離で差が出ないように長さを揃えてるとこも…、わかる…よね?

で、その「基礎」以外の「凝った」というポイントは2つ。一つが、「マウスクリックに反応するのではなく」てこと。押し続けたら動き続けて欲しいじゃん。なので mousedown でタイマーかけて mouseup でタイマー殺す、てこと。のために Interval を活用している、てことね。もう一つが「加速装置」をつけてるってこと。最初はゆっくり、段々移動速度が上がる、という風にすると、操作性がいいわけよね。微調整のしやすさと大胆移動のしやすさの両方を満足出来る、というわけだ。加速はシンプルに 0.1~1.0 の範囲での x2 を採用した。いい感じだよ、気持ちいい。

というわけでレイアウト混合を組み込んでみる

「どう実装すればいいのか」で説明するような内容は何もない。jQuery: Cytoscape.js お試せた6.5 (ノードを「本当に」動的に追加 – undocumented な makeLayout の話)で書いてるコードスニペットの通りだし、既にここでやってることを、「選択状態のノード」に限定して使うだけの話。

使ってる動画を貼り付けてみる:

動画だけだと今ひとつ伝わりにくいかもしれないが、後から適用する方の layout が「ステキな場所に」配置してはくれないので、「上でやっといた移動手段がないとめっさストレス」なのですわ。

動画の方のコメントにも書いておいたんだけれど、「layout 任せにしないでほかの「ドラッグ以外の移動手段を用意しておく」」ことでようやく「使いやすいものになりうる」ということかなぁと思った。

実際のヤツを貼り付けるので、ご自分でも動かしてみたらいいと思う:


boundingBox 指定するパターンについて

automove のデモで「可動範囲も限定してしまう」振る舞いであることは確認済みなので、「このままでは(ワタシのニーズ的には)使い物にはならない」のよ。だから「今回はやってみない」。

ただ、最初やろうとしたのは、「可視範囲」(extent)を4つくらいの象限に分割して、それをユーザに選ばせて、そこに収めて配置する、ということをやれば案外いいかもしれんな、と思ったのよ。でも「可動範囲を制限する」ことは望みではないから…、本当にこれをやりたいとなれば、多分「望みのレイアウト⇒これが配置した位置全部記憶⇒preset レイアウトにして記憶しておいた position に配置」ということをしないといけないんじゃないかと思う。

なんせ makeLayout には「ドキュメントがない」のでね、本当は「ステキなインターフェイス」があるのかもしれないんだけれど、automove のデモから読み取れた範囲からの理解だけだと「そうやって実現するしかなかろう」ということなので…、まぁのんびりやる、こっちは。(やれば使いやすいと思う、とても。)



Related Posts