jQuery: Cytoscape.js お試せた6 (ノードをダイナミックに追加 + スタイルというか classes)の「かなり」続き。
Contents
jQuery: Cytoscape.js お試せた7 (multiline label とかダブルタップとか色んなレイアウトとかとにかく色々)
前回のおさらい
jQuery: Cytoscape.js お試せた6 (ノードをダイナミックに追加 + スタイルというか classes)は結局のところの結論は、「cy.add で追加していくアプローチは諦めるがいいさ」ということ。今回のはこれに従っている。つまり、「使う人には「動的」にみえるが、実際は毎度静的データを作って都度 cytoscape インスタンスを一から構築」している。
そういうわけなので、そういう作りをするならば「(使う人からみると)動的にレイアウトを取り替える」ことが出来るというわけだ。
思ったより使いやすいもんが出来たぞ喜べ
つーか「アニメ」とか「声優」に興味がある人限定で喜べ、つーことか。
これだ:
使い方は、最初に番組名で検索して、番組を選んで、あとは「番組」もしくは「キャラクター」を「ダブルタップ」すると、ノードが追加されていく。
ノードについてのフィルターを少しいっぱい付けてるが、これは無論「そういう絞込みがしたい」ということもあるけれど、実際は「全部出そうとすると読めたものではないし、ブラウザがすぐにハングアップ状態に陥る」ので絶対に必要だった。
例によって「フレームのソースを表示で見てね」と言いたいところだが、無論そんなんデカ過ぎてそれじゃ説明にならんので、以降でポイントだけ説明する。それよりもね、こういうせせこましい状態で遊ぶのではなく、これで紹介した Open Frame を入れて、別タブでなおかつ全画面表示で遊ぶとええぞ。
コードの微解説
cytoscape.js 関係の説明の前にちょっと別の話
jquery: Tabulator 出来てみた5.5(「AJAX Data Loading」ない AJAX Data Loading)で、MAL ページの解析器を書くのに node.js を使ってる、という話をした。
これの話なんだけれど、「Object とかのぷりちぃぷりんと」でやっぱ困るんだよね、どんな言語でも付き物だよな。いつものように StackOverflow に答えがあった:
1 // ------------------------------------------
2 function MalUrlBuilder(use_proxy) {
3 /* ... */
4 }
5 // ------------------------------------------
6 const util = require('util');
7
8 var fn = process.argv[2];
9 var ty = process.argv[3];
10 var fs = require('fs'),
11 path = require('path'),
12 filePath = path.join(__dirname, fn);
13
14 fs.readFile(filePath, {encoding: 'utf-8'}, function(err, data) {
15 if (!err) {
16 var parser = new MalPageParser();
17 var result = {};
18 if (ty == 'person') {
19 result = parser.parse_person(data);
20 } else if (ty == 'chars') {
21 result = parser.parse_characters_and_staff(data);
22 } else if (ty == 'char') {
23 result = parser.parse_character(data);
24 } else if (ty == 'search_result') {
25 result = parser.parse_search_result(data);
26 }
27 console.log(util.inspect(result, false, null));
28 } else {
29 console.log(err);
30 }
31 });
32 // ------------------------------------------
どうでもいいが node.js に組み込みのこのぷりちぃぷりんと、Python でストレス感じてるとすんげーいいんだよな、読みやすい。Python にもこういうぷりちぃぷりんとが組み込みになってくれたらいいんだがなぁ。
もう一つ、同じく Cytoscape.js そのものとは関係なくて、だけれどもこの話の直接的な続きの話。「サーバに猛烈な勢いでリクエストを投げないようにする」というだけでなくて、「なおかつ諦めずにリトライする」までやってみたのよ:
1 var _cache = {};
2 function _ajax_get(url, async, fn, retries) {
3 var conf = {
4 async: async,
5 url: url,
6 type: "get",
7 dataType: "html",
8 success: fn,
9 error: function(xhr, textStatus, errorThrown) {
10 if (retries === undefined) {
11 retries = 5;
12 }
13 if (retries > 0) {
14 setTimeout(function() {
15 _ajax_get(url, async, fn, retries - 1);
16 }, 1000 + Math.random() * 10000);
17 }
18 }
19 };
20 setTimeout(function() {
21 jQuery.ajax(conf);
22 }, 250 + Math.random() * 250);
23 }
24 function get_characters(anime, fn, async) {
25 var url = urlbuilder.get_characters(anime["malid"], anime["canonical_name"]);
26 if (url in _cache) {
27 fn(_cache[url]);
28 return;
29 }
30 async = (async === undefined) ? true : async;
31 _ajax_get(url, async, function (data) {
32 var result = parser.parse_characters_and_staff(data);
33 _cache[url] = result;
34 fn(result);
35 });
36 }
非同期だからねぇ、どうしても複雑になるのは仕方がない。(キャッシュもやってるがこれは話としてはオマケ。) Math.random でリクエスト発行をバラけさせているわけなんだけれど、もっと絶妙な値を選んだ方が良さそう、このままだと結構 Too many… は頻発する。
マルチラインなラベル
アタシの例だとね、アニメのタイトルが結構すんげーのが多いんだわ、『甘城ブリリアントパーク 「わくわくミニシアター らくがきバックステージ」』とか『普通の女子校生が【ろこどる】やってみた。流川、案内してみた。』とか、もうええ加減にせえよ、て感じ。これはもう折り返さないとどうにもならん。
ドキュメントでちゃんと説明されてるようには見えないんだけれど、とにかくこの issue からリンクされていた jsbin (なんかワタシの環境だとライブで動かないんだけど)でやり方はわかった:
1 var cy = window.cy = cytoscape({
2 container: document.getElementById('cy'),
3 /* ... */
4 style: [
5 {
6 selector: '.multiline-manual',
7 style: {
8 'text-wrap': 'wrap'
9 }
10 },
11 {
12 selector: '.multiline-auto',
13 style: {
14 'text-wrap': 'wrap',
15 'text-max-width': 80
16 }
17 },
18 {
19 selector: '.autorotate',
20 style: {
21 'edge-text-rotation': 'autorotate'
22 }
23 },
24 /* ... */
25 ],
26 elements: [
27 /* ... */
28 ]
29 });
で、最大長を指定してる方は長ければ自動で折り返し、付けてる方も付けてない方も、ラベルのテキスト自身に改行コードを素直に入れれば「思った通りのことをしてくれる」。
ダブルんなタップ
まぁ要するに「タブレットとか」向けのことを考えているものなので、「ダブルんなクリック」的指向が素では提供されとらんのね。まぁ確かにあぁいうデバイスで「ダブルタップ」って、死にそうになるくらい使いづらいよね。
そうなんだけれど、今のワタシの例の場合、特に random レイアウトの場合なんだけど、「ノードを移動させる」という行為を普通にするハメになるんで、それに反応したらいかんのよね。なので是が非でも「タブルんなタップ」りたいぞと:
1 var tappedBefore;
2 var tappedTimeout;
3 function build_cy() {
4 var cy = window.cy = cytoscape({
5 container: document.getElementById('cy'),
6 /* ... */
7 };
8 cy.on('tap', function(event) {
9 // NOTE: older cytoscape's event has cyTarget rather than target.
10 var tappedNow = event.target;
11 if (tappedTimeout && tappedBefore) {
12 clearTimeout(tappedTimeout);
13 }
14 if(tappedBefore === tappedNow) {
15 tappedNow.trigger('doubleTap');
16 tappedBefore = null;
17 } else {
18 tappedTimeout = setTimeout(function(){ tappedBefore = null; }, 300);
19 tappedBefore = tappedNow;
20 }
21 });
22 cy.on('doubleTap', 'node', function(event) {
23 /* ...i can access to this.data(), this.data('actor'), ..., etc. */
24 });
25 }
これは例によって StackOverflow からパクったが、コメント入れといた通り、どうやら古いバージョンと今のバージョンで違うみたいね(event.cyTarget
ではなく event.target
)。
矢印さない
大した話ではないはずなのに迷走。万事が万事こうなのよ Cytoscape.js。迷走の原因は例えばこれだが、なんつーか単に target-arrow-shape
を「指定しないだけ」でワタシの場合はいいのね。それがデフォルトなんだから。
1 // ...
2 .selector("edge.aa")
3 .css({
4 //"target-arrow-shape": "triangle",
5 "curve-style": "bezier",
6 //"target-arrow-color": "#9dbaea",
7 "width": 3,
8 "line-color": "#ea9dba",
9 })
10 // ...
その他しょうもないことで悩んだ話
先に説明した「リトライ」にもちょっと関係するんだけれど、edge を作る際にいないノードを相手に作ろうとすると例外起こしやがるのね、これがちょっといやらしい。こういうのさ、普通ユニークな ID をベースに構築していくもんなので、大抵「存在するのがわかっているノードの ID に基くんだから、edge だけ先に作っちゃう事だって出来るのさ」なんだけどね、Cytoscape はそういうノリじゃないらしい。
まぁそれだけでなくほんとにワタシの単純ミスで迷子エッジを作っちゃってたのもあってね、なので実は「一番苦労」してしまった、ここに。
まだイケてないとこややりたいこと多数
どうしても実際に AJAX で大量にリクエストを発行する都合、まぁ「遅い」のは仕方がないとして諦めはつくけれど、だったらせめて「やってるぜ、動いてるぜ、まだ情報取得中だぜ」をどうにかわかるようにしないと。こんなん自力でなんとかするしかないヤツなんで、「技術説明としてのネタ」にはならんので、「まぁがむばる」。
もう一つが、これが一番なんとかしたいんだけど、特に random レイアウトで、ノードを選んでドラッグした場合に、繋がってるノードも(何かしらの重み規則で)一緒に動いて欲しいんだよね。読みやすい配置にしたいわけよね。
あとは cytoscape.js ネタとしては Export をやりたいかな。まぁこれは「実装」はすぐだけど、無論「実現方法への興味」の意味は全然なくて、「どういう振る舞いをするのか」を知りたいてことだ。
18日01:00追記: 番組検索結果リストの start, end
知ってたけど直し忘れてたので改めて:
しょーもない話で技術的な何かがあるわけではない。気になる人は2つを「フレームのソースの表示」して取得して diff でも取ってみてくれればいい。(まぁ「javascript の日付時刻まわりの処理」という話がないではないけれど。)