jQuery: Cytoscape.js お試せた6 (ノードをダイナミックに追加 + スタイルというか classes)

Cytoscape.js のドキュメントはヒドい、のだからこそ実例に価値がある、のでがむばる。

jQuery: Cytoscape.js お試せた6 (ノードをダイナミックに追加 + スタイルというか classes)

ファーストお試せた、のつもり

jquery: Tabulator 出来てみた5.5(「AJAX Data Loading」ない AJAX Data Loading)jquery: Tabulator 出来てみた5.555(Ajax making too many requests との格闘とか – AJAX Data Loadingの話の続き)で「本当にやりたいこととは違う」と言ってたその「本当にやりたいこと」。

Cytoscape.js でね、「声優関連図」を作りたいのよ。「あ、この人、このアニメもやってんのかー」てのを探しやすくしたい、という個人的動機と Cytoscape.js というオモチャへの純粋な興味とで一石なん鳥てなわけでな。

とりあえず「最初のつまづきが解決」という、ほんとのほんとに始めの一歩なのよ:


機能面でも見栄え面でも全然ほんと「はじまったばっか」なので、だせーとか言わないように。あと表を選択を続けると延々ノードを追加していくのはこれは「機能」ではなくてまだサボってるだけ。

例によって「フレームのソースを表示」ね。コードの大部分が「jquery: Tabulator 出来てみた5.555(Ajax making too many requests との格闘とか – AJAX Data Loadingの話の続き)」と共通なので、「cy」とか「cytoscape」に関係ある部分だけ読んだらいいよ。

ヒド過ぎるドキュメントを目をしょぼしょぼさせながら徘徊し、そして頭掻き毟りながらようやっと解決出来た「2つの大変だったこと」:

  1. layout.run()、さらにいえば「その layout ってどっから来るんや」問題
  2. ノードに class をつけて、それに css スタイルを対応付けるにはどーすんのよ

1つ目、いやわからんてほんと。とにかく貼り付けたコードをガン見してちょーだい。これをドキュメントから導くのはほとんど不可能。

2つ目は「class」じゃなく「classes」なのだ、というしょーもない話。「セレクタ」は jQuery のセレクタと同じなので、「classes」に「追加」しとけば、その追加したクラスを選択するセレクタを書ける(.selector("node.anime") のように)。

「フレームのソースを表示」だけだとほんと読みずらいので、今回の本題に関係あるとこだけ引用しとく:

  1 <style>
  2 #outer {
  3   border-style: none;
  4   border-width: 3em;
  5 }
  6 #cy {
  7   width: 100%;  /* MUST */
  8   height: 70%;  /* MUST */
  9   /**
 10   position: absolute;
 11   left: 0;
 12   top: 0px;
 13 
 14   z-index: 999;
 15   */
 16 }
 17 </style>
 18 <!-- ... -->
 19 <div id="cy"></div>
 20 <script>
 21 // ------------------------------------------
 22 function get_characters(anime_id, anime_canonical_name, fn, async) {
 23     var url = PROXY + BASEURL + "/anime/";
 24     url += anime_id;
 25     url += "/" + anime_canonical_name;
 26     url += "/characters";
 27     jQuery.ajax({
 28         async: async,
 29         url: url,
 30         type: "get",
 31         dataType: "html",
 32         success: fn,
 33     });
 34 }
 35 function construct_cy_first_tree(an_anime) {
 36     var anime = an_anime;
 37     anime["id"] = "anime" + anime["anime_id"];
 38     anime["name"] = anime["anime_name"];
 39     cy.add([
 40         {group: "nodes", data: anime, classes: "anime"},
 41     ]);
 42     get_characters(
 43         an_anime["anime_id"],
 44         an_anime["anime_canonical_name"],
 45         function(data) {
 46             var result = parser.parse_characters_and_staff(data);
 47             result["characters"].forEach(function(e, i) {
 48                 e["id"] = "cv" + e["voice actor person id"];
 49                 e["name"] = e["voice actor"];
 50                 cy.add([
 51                     {group: "nodes", data: e, classes: "actor"},
 52                     {group: "edges", data: {source: anime["id"], target: e["id"]}}
 53                     ]);
 54                 });
 55             var layout = cy.layout({
 56                 name: 'random',
 57                 //name: 'breadthfirst',
 58                 fit: true,
 59                 avoidOverlap: true,
 60                 //name: 'dagre'
 61                 //name: 'circle'
 62             });
 63             layout.run();
 64         }, false);
 65 }
 66 // ...
 67 var cy = window.cy = cytoscape({
 68   container: document.getElementById('cy'),
 69 
 70   boxSelectionEnabled: false,
 71   autounselectify: true,
 72 
 73   style: cytoscape.stylesheet()
 74     .selector("node")
 75       .css({
 76         "text-valign": "center",
 77         "text-halign": "center",
 78       })
 79     .selector("node.anime")
 80       .css({
 81         "content": "data(name)",
 82         "color": "black",
 83         "text-outline-width": 2,
 84         "text-outline-color": "white",
 85         /**
 86         "text-opacity": 0.5,
 87         */
 88         "background-color": "#479e11",
 89         "background-opacity": 0.5,
 90 
 91         "shape": "square",
 92         "width": 200,
 93       })
 94     .selector("node.actor")
 95       .css({
 96         "content": "data(name)",
 97         "color": "black",
 98         "text-outline-width": 2,
 99         "text-outline-color": "white",
100         /**
101         "text-opacity": 0.5,
102         */
103         "background-color": "#9e4711",
104         "background-opacity": 0.5,
105 
106         "shape": "square",
107         "width": 180,
108       })
109     .selector("edge")
110       .css({
111         "target-arrow-shape": "triangle",
112         "curve-style": "bezier",
113         "target-arrow-color": "#9dbaea",
114         "width": 4,
115         "line-color": "#9dbaea",
116       }),
117 });
118 </script>

2018-01-16 15:00 追記: いや、ダメだこれは…

AJAX アクセスをもう一段階増やしたら途端にダメになってしまった。ググるさまでもなんにも出てこない、これは解決になってないけど「出来ない」ということだけは言ってそうな気がする。けどさっきのはなんでうまく動いたの?

結論としてはどうも cy.add で動的にノードを追加していく場合は、「レイアウトマネージャ」の機能のほとんど全てを「諦める」必要があるらしい。(「自動的にいいあんばいで配置」のことね、つまり position を自分で設定していかなければならない。)うぞーん、これじゃ layout: {name: "random"} してる意味ないよ…。

ともあれ、「諦めたバージョン」:


さっきより一段階データ取得部分が深くなってるので読みにくくなってると思うけど、今回のポイントとなる部分は

  1. cy.fit だけはキャーすてき
  2. position: {...} 指定せざるを得ない
  3. のであればもう「’random’ レイアウト」なんて意味ないので「’preset’ レイアウト」でええやん

「’random’」についてだけ言ってるけれど、本当に機能的に致命傷なのはそれ以外の「’dagre’」などを使いたい場合だろう。こういう高機能な配置を、まぁ「自力で簡単に計算出来る」わきゃない(というかそういう賢さに期待して使おうとするんだから、落胆だろ)。

うーん、「あとから cy.add」という発想そのものを出来るだけ避けるようにした方がいいのかもしれんなぁ。

2018-01-16 15:50 追記: もっと諦めれば不満はあれどハッピー

不満でハッピーってなんだ。

「毎度一から全部構築する」つもりなら組み込みのレイアウトマネージャを諦めなくていい。当たり前。かくして:


もはや cy.add してない。動きは美しくないね、random だと。circle だとこれでもいい感じなんだけどねぇ。

2018-01-21 16:45 追記: 諦めないで

やっとやり方わかったよ。6.5 参照。



Related Posts