意味通じてる?
Contents
jQuery: Cytoscape.js お試せた17 (layout 混合は使い物になるだろうか + cy.shift())
前置き
jQuery: Cytoscape.js お試せた6.5 (ノードを「本当に」動的に追加 – undocumented な makeLayout の話)で、automove のデモで makeLayout に気付いた、という話をしたでしょう。このコードをもう一度注意深くみてもらうとわかるんだけれど、
- 一部ノードだけ選んで適用可能
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 のデモから読み取れた範囲からの理解だけだと「そうやって実現するしかなかろう」ということなので…、まぁのんびりやる、こっちは。(やれば使いやすいと思う、とても。)