Vue.jsからcytoscapeを使ってみる

Vue.jsの単一ファイルコンポーネントからCytoscapeを利用してみました。気をつける点としてはコンポーネントがマウントされるタイミングより前にCytoscapeの初期化処理を行ってはいけないぐらいですが、念のためメモしておきます。

事前作業

vue-initなどでプロジェクトを作成した後にnpm installでcytoscapeをダウンロードしておきます。

npm install –save cytoscape

単一ファイルコンポーネントスクリプトcytoscapeをrequireする

var cytoscape = require(‘cytoscape’)

初期化スクリプトの作成

cytoscape初期化のスクリプトを作成します。

methods: {
  view_init: function () {
    this.cy = cytoscape(
      {
        container: document.getElementById('cy'),
        boxSelectionEnabled: false,
        autounselectify: true,
        style: cytoscape.stylesheet()
            .selector('node')
            .css({
              'height': 80,
              'width': 80,
              'background-fit': 'cover',
              'border-color': '#000',
              'border-width': 3,
              'border-opacity': 0.5,
              'content': 'data(name)',
              'text-valign': 'center'
            })
            .selector('edge')
            .css({
              'width': 6,
              'target-arrow-shape': 'triangle',
              'line-color': '#ffaaaa',
              'target-arrow-color': '#ffaaaa',
              'curve-style': 'bezier'
            }),
        elements: {
          nodes: [
            { data: { id: 'cat' } },
            { data: { id: 'bird' } },
            { data: { id: 'ladybug' } },
            { data: { id: 'aphid' } },
            { data: { id: 'rose' } },
            { data: { id: 'grasshopper' } },
            { data: { id: 'plant' } },
            { data: { id: 'wheat' } }
          ],
          edges: [
            { data: { source: 'cat', target: 'bird' } },
            { data: { source: 'bird', target: 'ladybug' } },
            { data: { source: 'bird', target: 'grasshopper' } },
            { data: { source: 'grasshopper', target: 'plant' } },
            { data: { source: 'grasshopper', target: 'wheat' } },
            { data: { source: 'ladybug', target: 'aphid' } },
            { data: { source: 'aphid', target: 'rose' } }
          ]
        },
        layout: {
          name: 'breadthfirst',
          directed: true,
          padding: 10
        }
      }
    )
  }
},

コンポーネントマウント時にcytoscape初期化スクリプト呼び出し

mounted: function () {
  this.view_init()
}

これだけで、Vue.jsでもCytoscapeのグラフが表示さえるはずです。

ボタンクリックでノードを追加できるようにする

add_node: function (newId, targetId) {
  this.cy.add([
    { 'group': 'nodes', data: { 'id': newId }, position: { x: 300, y: 200 } },
    {'group': 'edges', data: {'id': 'edge' + this.count, 'source': newId, 'target': targetId}}
  ])
}

それとcytoscapeで動的にノードを足す場合はこんな感じでノードとエッジを追加してあげれば大丈夫です。レイアウトを初期化するのでなければノードの位置を指定しなければいけないです。 最終的にこんなコンポーネントを作成しました。

<template>
  <div id="view">
    <button v-on:click="add_node">push</button>
    <div id="cy"></div>
  </div>
</template>

<script>
var cytoscape = require('cytoscape')

export default {
  name: 'Cytoscape',
  components: {},
  created: function () {
  },
  data: function () {
    return {
      input: '',
      output: '',
      msg: 'vue to cytoscape',
      count: 0
    }
  },
  methods: {
    add_node: function () {
      console.info(this.cy)
      this.cy.add([
        { 'group': 'nodes', data: { 'id': 'node' + this.count }, position: { x: 300, y: 200 } },
        {'group': 'edges', data: {'id': 'edge' + this.count, 'source': 'node' + this.count, 'target': 'cat'}}
      ])
    },
    view_init: function () {
      this.cy = cytoscape(
        {
          container: document.getElementById('cy'),
          boxSelectionEnabled: false,
          autounselectify: true,
          style: cytoscape.stylesheet()
              .selector('node')
              .css({
                'height': 80,
                'width': 80,
                'background-fit': 'cover',
                'border-color': '#000',
                'border-width': 3,
                'border-opacity': 0.5,
                'content': 'data(name)',
                'text-valign': 'center'
              })
              .selector('edge')
              .css({
                'width': 6,
                'target-arrow-shape': 'triangle',
                'line-color': '#ffaaaa',
                'target-arrow-color': '#ffaaaa',
                'curve-style': 'bezier'
              }),
          elements: {
            nodes: [
              { data: { id: 'cat' } },
              { data: { id: 'bird' } },
              { data: { id: 'ladybug' } },
              { data: { id: 'aphid' } },
              { data: { id: 'rose' } },
              { data: { id: 'grasshopper' } },
              { data: { id: 'plant' } },
              { data: { id: 'wheat' } }
            ],
            edges: [
              { data: { source: 'cat', target: 'bird' } },
              { data: { source: 'bird', target: 'ladybug' } },
              { data: { source: 'bird', target: 'grasshopper' } },
              { data: { source: 'grasshopper', target: 'plant' } },
              { data: { source: 'grasshopper', target: 'wheat' } },
              { data: { source: 'ladybug', target: 'aphid' } },
              { data: { source: 'aphid', target: 'rose' } }
            ]
          },
          layout: {
            name: 'breadthfirst',
            directed: true,
            padding: 10
          }
        }
      )
    }
  },
  computed: {
  },
  mounted: function () {
    this.view_init()
  }
}

</script>
<style scoped>
#cy {
    width: 100%;
    height: 80%;
    position: absolute;
    top: 50px;
    left: 0px;
    text-align: left;
}

body {
  font: 14px helvetica neue, helvetica, arial, sans-serif;
}
</style>

cytoscapeが便利そうです。元々はネットワーク分析の可視化ツールですが、これがあればマインドマップとかだいぶ楽に作れそうだと思いました。その場合は、エッジ、ノードのデータを別のコンポーネントに渡せるようにした方が良いんだろうな。