jQuery: Cytoscape.js お試せた20 (アニメーションは大変さ)

いや、正確に言うと…。

例によって実例はこれからの続き物。

「簡易的にアニメーション的なものに使える」ものが eles.flashClass で、これは css のスタイルを一時的に変更するもの。css の書き方次第、ってわけだね。一つ前で書いたので、お試しておこうかと。これだけのネタのつもりだったのだ、信じてください。

そうなんだけど非常に苦労していた。しかも flashClass とは全然関係ないところで。そりゃ無論「アタシが嬉しいように」欲張ったからだが、実際何で苦労してたかてのは、結局「座標系」問題なのよね。簡易版じゃないほうの cy.animate() は「自分が意図している通りに書けるならば」ちゃんと動くよ。いや、それが難しいのよね、「計算が大変」なのよ。「拡大しながらパン」って、一撃で書くのはむつかしいよ。つまり、Cytoscape.js のアニメーション機能って、ドキュメントで最初に受ける印象よりは低レベルなの。その大変なことをやって欲しいのだけどね。

近いもので Mapnik やら openlayers やら結構この手のは経験あるはずなんだけど、あれらよりずっとなんか書きにくい。随分前なので忘れたけど、もっと素直に書けたと思うんだよなぁ。(Mapnik だったか、C++ / Qt で書いたけど結構楽に書いてたと思う。無論別種のダルさはあるよ、C++ だし Qt だもん。)

あまりに cy.animate() の制御がうまく書けなくて、面倒くさくなって自力で書いたらすんなり書けたので…、なんてことを繰り返してるから時間かかるわけだ、とにかくこんなダサいコードを書いた:

 1 function _animated_fit(node) {
 2     function _pan(_node) {
 3         if (!_node) {
 4             return;
 5         }
 6         var pos = _node.renderedPosition();
 7         target_pos = {
 8             x: $(cy.container()).width() / 2,
 9             y: $(cy.container()).height() / 2
10         };
11         var vec = {
12             x: (target_pos.x - pos.x) / 2,
13             y: (target_pos.y - pos.y) / 2
14         };
15         cy.panBy(vec);
16     }
17     var lev = 1.0;
18     var t = setInterval(function() {
19         lev *= 1.05;
20         _pan(node);
21         cy.zoom(lev);
22         if (lev > 4) {
23             clearInterval(t);
24             cy.fit(node);
25         }
26         if (!__touring) {
27             _toggole_visible_edges([], "element");
28         }
29     }, 25);
30 }

最後の if (!__touring) { は後で出てくる。コードは基本的には「拡大率をちょっとずつ上げながら、ターゲットノードを少しずつ中心に来るようにパン」という単純な繰り返しによる実装。

これを書いて何をしたかったかと言えば、

  1. 興味のあるキャラクターノード群が選択されているとして
  2. 「ツアー」ボタンを押すと
  3. 最初に「そのキャラの出てるアニメとそのキャラ」を「光らせるなりなんなり目立たせて(eles.flashClass の出番のつもり)
  4. そしてキャラにパンしつつズーム(上のダサい _animated_fit の出番のつもり)
  5. これをキャラごとに繰り返す

実際の動きはあとで本物貼り付けるので動かしてみればいい。

ここまでのことで何が言いたいかと言うと、「本題の flashClass に至るまでの方が時間がかかってしまった」ということ。ワタシ的にはこの flassClass 前の諸事情は本題のつもりではなかったんだよね。

そしてその「本題の「簡易アニメーション目的にも使える flassClass」」の話。結論から言えば、「ワタシはそれを適用するのに一番ふさわしくない例を選んでしまった」てこと。こりゃぁ向かないパターンがあるぞ。

flashClass は「class を一時的に書き換える」ものだから、スタイルを予め設定しておく必要があるわけだけどさ、そこなのよ、「ワタシの例では向いてない」まさにその理由は:

 1 // initialize cy
 2 var cy = window.cy = cytoscape({
 3     container: document.getElementById('cy'),
 4     // ...    
 5     style: cytoscape.stylesheet()
 6         // ...
 7         .selector("node.tour_anime_start")  // flassClass で一時的につけたい class のスタイル
 8         .css({
 9             "font-size": 450,  // うーん、固定値しか書けないよなぁ
10             "color": "black",
11             "text-outline-width": 5,
12             "text-outline-color": "white",
13             //"text-opacity": 1,
14             //"background-opacity": 0.5,
15         }),

layout によっていいあんばいで綺麗に配置されて fit されてると、凄まじい縮小率なわけね。0.1 とか。でも grid レイアウトとかだとそこまでならない。となれば 450 というサイズは「大き過ぎる、または小さ過ぎる」てことになる。うげぇ。

「アイディア次第」と取るか「苦肉の策」と取るかは自由だが、もうこうなったら「予めズームアウトしちゃえ」とすることにした。flassClass だけで頑張るつもりならもうそれしか解がないので。さすが「簡易版」だけありますわ。なのでもしこれを何かに使おうと思うなら、「ズームレベルに依存しないナニカ」で考えてね。ワタシは間違えた。

結局こういうすさまじい実装になってしまった:

 1 var __touring = 0;
 2 var __tour_nodes = [];
 3 var __tour_idx = 0;
 4 function _animated_fit(node) {
 5     function _pan(_node) {
 6         if (!_node) {
 7             return;
 8         }
 9         var pos = _node.renderedPosition();
10         target_pos = {
11             x: $(cy.container()).width() / 2,
12             y: $(cy.container()).height() / 2
13         };
14         var vec = {
15             x: (target_pos.x - pos.x) / 2,
16             y: (target_pos.y - pos.y) / 2
17         };
18         cy.panBy(vec);
19     }
20     var lev = 1.0;
21     var t = setInterval(function() {
22         lev *= 1.05;
23         _pan(node);
24         cy.zoom(lev);
25         if (lev > 4) {
26             clearInterval(t);
27             cy.fit(node);
28         }
29         if (!__touring) {
30             _toggole_visible_edges([], "element");
31         }
32     }, 25);
33 }
34 $("#tour-selected").on("click", function(event) {
35     if (!__touring) {
36         __tour_idx = 0;
37         var cnodes = cy.elements("node.v:selected");
38         cnodes.forEach(function(c, _) {
39             __tour_nodes.push(c);
40         });
41         if (__tour_nodes.length) {
42             var fn = function() {
43                 _toggole_visible_edges([], "element");
44                 cy.fit();
45                 if (cy.zoom() > 0.2) {
46                     cy.zoom(0.2);
47                     cy.center();
48                 }
49                 cy.$id("a" + __tour_nodes[__tour_idx].data("a")["#"]).flashClass(
50                     "tour_anime_start", 1500);
51                 setTimeout(function() {
52                     __tour_nodes[__tour_idx].flashClass("tour_anime_start", 1500);
53                 }, 1500);
54                 setTimeout(function() {
55                     _animated_fit(__tour_nodes[__tour_idx]);
56                     __tour_idx = (__tour_idx + 1) % __tour_nodes.length;
57                     _toggole_visible_edges([], "none");
58                 }, 3000);
59             };
60             fn();
61             __touring = setInterval(function() {
62                 fn();
63             }, 10000);
64             $("#tour-selected-icon").attr(
65                 "class", "fa fa-stop fa-1x");
66             $("#tour-selected-icon").attr(
67                 "title", "Stop tour");
68         }
69     } else {
70         _toggole_visible_edges([], "element");
71         clearInterval(__touring);
72         __touring = 0;
73         __tour_nodes = [];
74         __tour_idx = 0;
75         $("#tour-selected-icon").attr(
76             "class", "fa fa-play fa-1x");
77         $("#tour-selected-icon").attr(
78             "title", "Start tour to selected nodes");
79     }
80 });

(_toggole_visible_edges は 前回の参照。)

絶対どこかで cy.animate() 活用できるはずなんだよなぁ。こんなコード書いてたら全然柔軟性ないもん、動き変えたくなってもこれじゃすぐに直せん。うーん、あとで頑張ろう。

まぁ「本題と関係ないこと」で苦労し過ぎたけど「flashClass は使い道が思ったより限定されます」てことでよろしう。

というわけでいつものように「実際に動くヤツ」:


遊び方はここの末尾に書いてある。その時点から「Tour」が付いただけ。選択状態になったものを「アニメーションする」(ツアーする)。

個人的にはまぁまぁ気持ちい動きさせれたかなぁ、と思ってはいる。というか「声優関連図作り子ちゃんとしてのツアー機能」としてみるなら、存外使いやすい。実装がどんなであれ、使いやすければ「ひとまずはそれでいい」、今は。まぁそのうち整理するわい。



Related Posts