Help us understand the problem. What is going on with this article?

neo4j をインストールしてから Cytoscape と組み合わせるまでの話

More than 1 year has passed since last update.

はじめに

グラフデータベースNeo4jと、図表化JavascriptライブラリCytoscapeを触ってみた時のメモ。
組み合わせた情報で、コードレベルの情報がほとんどないので、誰かの参考になれば嬉しい。
(実際組み合わせてみると、それ単独では記事化するほどの内容ではなかったけど・・・)

Neo4j

インストール

Docker

ほぼこれ

pull
$ docker pull neo4j
お試しだけなので、ホストにマウントはさせないように変更
$ docker run \
    --detach \
    --publish=7474:7474 --publish=7687:7687 \
    --ulimit=nofile=40000:40000 \
    neo4j

初期設定

初期画面アクセス時に聞かれたこと ※初期ユーザー(neo4j/neo4j)でログインすると、パスワード変更を求められる。

以降実際とは異なるID/Passwordで記載します。
ID:neo4j
Pass:password

こんな感じのUI
image.png

やっぱdockerは環境構築が楽だなぁ・・・

使ってみた

ほぼこれ

ノードの作成/参照/削除

Taroを作る

CREATE (:Person{name:"Taro", age:20});

実行結果が見れる。

1ラベル追加(Personのことか)、1ノード作成(Personラベルに1ノード追加したということだな)、2プロパティ設定(nameとageを設定したってことだろう)、528msで完了。

image.png

Codeってタブは別に面白い話無いっぽいな。

image.png

Taroを取得する

MATCH (p:Person{name:"Taro"}) RETURN p;

image.png

おぉ!Taroが出てきた!

テーブルは、想定通り非構造データ。キーとかどうなってるんだ?もっかいTaro追加するとどうなるんだろう?

image.png

もう一度Taroを作って、取得する

CREATE (:Person{name:"Taro", age:20});
MATCH (p:Person{name:"Taro"}) RETURN p;

Taroが二人!キーとかは無いのか???自動生成って理解したほうが良さそうだなー(ドキュメント読めよって言われればそれまでだけど・・・)
image.png

全てのTaroを消す

MATCH (p:Person{name:"Taro"}) DELETE p;

Taroを消した。予想通り2ノード消えている。

image.png

ちゃんと消えてるか確認する

MATCH (p:Person{name:"Taro"}) RETURN p;

消したので、誰も居ない。

image.png

リレーションの作成/参照

ひとまず、A/B/Cの3人を作る

CREATE
     (:Person{name:"Alice", age:20}),
     (:Person{name:"Bob", age:22}),
     (:Person{name:"Carol", age:23});

A/B/Cを作る。できたっぽい。

image.png

ちゃんと作れているか確認

MATCH (n) RETURN n;

全ノード取得は、こうやって書くらしい。今更だけど、このSQLっぽいやつは、Cypherと呼ぶらしい。

image.png

リレーションの作成

MATCH (p1:Person{name:"Alice"}), (p2:Person{name:"Bob"}) CREATE (p1)-[:Friend]->(p2) ;

関係性の追加はこうするらしい。Carolが寂しそう。

image.png

ちなみに、触って動かせる。二人の関係を邪魔することはできない。

neo4j_gui.gif

よく見たら、Ageとか分からんやんけ、って思った。けど、選択すると、画面下部に詳細情報が表示されるらしい。

ID採番されているやんけ。(なぜCarolは40?ちなみに、Aliceが0、Bobが20だった。20ずつ増える???)

image.png

どの属性を表示させるかは選べるらしい。(まぁそうだよね。)ただ、全部は表示できず、どれか一つだけっぽい。

image.png

Cytoscape

Cytoscapeを選んだ理由

slideshare - (2017.6.9) Neo4jの可視化ライブラリまとめ

Cytoscapeってのが良さそう。

Cytoscape.js単独での実装

Qiita - Cytoscape.jsを用いてデータを可視化する
※上記を参考にちょっと手直し(jQueryのライブラリを最新化したり、gridを文字列化したりしてる)

最終的にはNeo4jにつなげる必要があるけど、ひとまずは素の状態で触ってみる。

  • 丸いヤツとか、ドラッグアンドドロップできる。
  • マウスのスクロールで拡大・縮小できる。

See the Pen Cytoscape_test1 by nh321 (@nh321) on CodePen.

まぁ要素直書きだし、そうだろうな、って感じ。

Neo4jをjavascriptで参照する

シンプルなNeo4j接続

個人ブログ(日本語)見るとnode.jsとかpythonでサーバサイド実装してるみたいだけど、やりたいのはクライアント(ブラウザ)実装。

(DBアクセスのためのID/Passが露出するので、本来はサーバサイドに書くべきだろうけど、今回はお試ししたいだけなので、気にしない。)

よく見ると、CDN経由でライブラリ取得できるみたいなので、それをつかう。

Github - neo4j/neo4j-javascript-driver

基本は、Githubのusageを参考に作成。
そのままだと動かなかったので、stackoverflowも参照。

参考:

neo4jサーバはローカルにあるので、雑なhtmlだけ乗せておく。
<html>
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
  <script>
  $(function(){

  //https://stackoverflow.com/questions/44205354/unable-to-establish-a-neo4j-bolt-driver-connection-in-javascript
  neo4j = neo4j.v1

  //driveの設定
  var driver = neo4j.driver(
    'bolt://localhost:7687',
    neo4j.auth.basic('neo4j', 'password')
  )

  //セッションの生成
  var session = driver.session()

  // Run a Cypher statement, reading the result in a streaming manner as records arrive:
  session
    .run('MATCH (person:Person) RETURN person.name AS name')
    .subscribe({
      onNext: function(record) {
        console.log(record.get('name'))
      },
      onCompleted: function() {
        session.close()
        driver.close()
      },
      onError: function(error) {
        console.log(error)
      }
    })
  });
  </script>
</html>

そうすると、以下のようにF12でconsoleにログ出力されていることを確認。ちゃんと動いている。良かった。

image.png

グラフ情報として取得するのはどうやるんだ・・・???

Neo4jからリレーションデータを取得する

誰かのサンプルを見つけたので使ってみる。

Cypherの結論

MATCH (m)-[r]-(n) RETURN m.name as m,type(r) as type,startnode(r).name as start,n.name as n;

Javascripte側はたぶん以下で行けるはず。(sample読んだ雰囲気で理解)

record.get('m');
record.get('type');
record.get('start');
record.get('n');

以下、うごいたhtml/javascript。

<html>
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
  <script>
    $(function(){

    //https://stackoverflow.com/questions/44205354/unable-to-establish-a-neo4j-bolt-driver-connection-in-javascript
    neo4j = neo4j.v1

    //driveの設定
    var driver = neo4j.driver(
      'bolt://localhost:7687',
      neo4j.auth.basic('neo4j', 'password')
    )

    //セッションの生成
    var session = driver.session()

    // Run a Cypher statement, reading the result in a streaming manner as records arrive:
    session
      .run('MATCH (m)-[r]-(n) RETURN m.name as m,type(r) as type,startnode(r).name as start,n.name as n')
      .subscribe({
        onNext: function(record) {
          console.log("---");
          console.log(record.get('m'));
          console.log(record.get('type'));
          console.log(record.get('start'));
          console.log(record.get('n'));
        },
        onCompleted: function() {
          session.close()
          driver.close()
        },
        onError: function(error) {
          console.log(error)
        }
      })
    });
  </script>
</html>

Neo4jとCytoscape.jsと組み合わせる

わりとできた。(Cypherは微修正してる)

ソース動いたの確認してから、仲良し3人組に変更してみたけど、ソース修正しなくてもちゃんと動く。いい感じ。

<html>
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
    <script src="http://cdn.bootcss.com/cytoscape/2.3.16/cytoscape.min.js"></script>
    <script>
      $(function(){

        //https://stackoverflow.com/questions/44205354/unable-to-establish-a-neo4j-bolt-driver-connection-in-javascript
        neo4j = neo4j.v1

        //driveの設定
        var driver = neo4j.driver(
          'bolt://localhost:7687',
          neo4j.auth.basic('neo4j', 'password')
        )

        //セッションの生成
        var session = driver.session()
        var style = [];
        var elements = {
          nodes:[],
          edges:[]
        };

        // Run a Cypher statement, reading the result in a streaming manner as records arrive:
        session
          .run('MATCH (m:Person)-[r]->(n:Person) RETURN m.name as mname, ID(m) as mid, type(r) as type, n.name as nname, ID(n) as nid')
          .subscribe({
          onNext: function(record) {
            console.log("---");
            console.log(record.get('mname'));
            console.log(record.get('mid').toString());
            console.log(record.get('type'));
            console.log(record.get('nname'));
            console.log(record.get('nid').toString());


            //内容要素はJSONオブジェクトである、サーバ側加工しフロントに渡すもの
            elements.nodes.push({
              data: {id: record.get('mid').toString(), name: record.get('mname'), label: 'Person'}
            })
            elements.nodes.push({
              data: {id: record.get('nid').toString(), name: record.get('nname'), label: 'Person'}
            })
            elements.edges.push({
              data: {source: record.get('mid').toString(), target: record.get('nid').toString(), relationship: record.get('type')}
            })

            //内容要素を表現するCSS
            var style = [
              //セレクターで拾いた内容要素が 指定したCSSを適用する
              //ノードの中で、label属性は「Peson」のノードが青色で表示し、文字はname属性を表示する
              { selector: 'node[label = "Person"]', 
               css: {'background-color': '#6FB1FC', 'content': 'data(name)'}
              },
              //エッジ全体で、文字はrelationship属性を表示する、終了点での矢印は三角形にする
              { selector: 'edge', 
               css: {'content': 'data(relationship)', 'target-arrow-shape': 'triangle'}
              } 
            ]

            //レイアウト設定
            var layout = {
              //グリッドレイアウトを適用する
              name : "grid"
            }

            // Cytoscapeオブジェクト初期化。
            var cy2 = cytoscape({ 
              // containerがHTML内の「cy2」DOM要素に指定
              container: document.getElementById('cy2'),
              elements: elements,
              style: style,
              layout: layout,
            });
          },
          onCompleted: function() {
            session.close()
            driver.close()
          },
          onError: function(error) {
            console.log(error)
          }
        })
      });
    </script>

  <style type="text/css">
    <!--
    /* cytoscape graph */
    #cy2 {
      height: 300px;
      width: 400px;
      background-color: #f9f9f9;
    }
    -->
  </style>

  <div id="cy2"></div>
</html>

最終的にこんな感じになりました。(以下は画像)

image.png

さいごに

グラフィカルなツールは触ってて楽しいですねー。
これを使ってジョブの先行後続とか見やすくしたら面白いかなーとか考えます。

以上。

nh321
せやかて工藤、このアカウントが発信するんは全て個人的な意見で、現在所属する会社の公式見解では無い、ゆーとるやろが。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away