JointJSのフローチャートから各要素の属性を抽出する

# はじめに
[JointJS](https://www.jointjs.com/opensource)を用いるとJavaScript側でフローチャート等の図を簡単に描くことができます。

JointJSの基本的な使用方法は以下が参考になります。
また、本記事で紹介する内容も以下記事のサンプルをほぼそのまま利用させていただいています。
参考記事：[インタラクティブに図が書けるJointJsを使ってみた](https://qiita.com/moriyaman/items/bbf2988eed6f7d1e61e2)

これを使って、例えば裏で動いているデータ分析やデバイス連携の処理をフローチャートで簡単に設定できるようにする、
といったWebサービスを開発することができます。（例えばNode-REDのような）

そのためにはインタラクティブなフローチャートを描画するだけでなく、
フローチャートの各要素（ブロックやリンク）の属性を抽出し、
POSTしたりajaxしたりでデータをサーバに送信する、という処理が必要になってきます。

そこで本記事では[JointJSのAPI](https://resources.jointjs.com/docs/jointjs/v3.0/joint.html)の中から、要素の属性抽出に関連するAPIを紹介していきます。

# 動作確認環境
- JointJS: Development version 3.0.2
- jQuery: 3.4.1
- Lodash: 4.17.11
- Backbone: 1.4.0

すべて[こちら](https://www.jointjs.com/opensource#Download-JointJS)からダウンロードできます。

# フローチャートのサンプル
以下の3つのブロック（element）と2本のリンク（link）をもつフローチャートを例に説明していきます。

<img width="500" alt="flowchart.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225840/96792e9c-cf3d-bee3-b7f7-b4d1aa5a0de6.png">


各ブロックにはdivNameに"mycell1"、"mycell2"、"mycell3"が設定されています。

以降、JavaScriptにて
`graph = new joint.dia.Graph;`
と定義されているものとしてgraphに対してAPIを使用していきます。

# APIの紹介
多数あるAPIの中でも[dia.Graph.prototype.getBBox](https://resources.jointjs.com/docs/jointjs/v3.0/joint.html#dia.Graph.prototype.getBBox)から始まるdia.Graph.prototype.get～というAPI群がありますが数が多いので一部だけ紹介します。

## dia.Graph.prototype.getCells
graph内のすべてのelementとlinkを配列で取得します。

```Javascript
var cells = graph.getCells();
cells.forEach(function(cell){
    console.log(JSON.stringify(cell));
});
```

結果

```
{"type":"org.Arrow","source":{"selector":".card","id":"0148eff1-ea1b-40b2-afc9-2bc031e586ce"},"target":{"selector":".card","id":"2e8732f3-8b98-4793-a6ea-406dd60ddba2"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"276c7e3c-ca4c-4e4c-8e32-0c20b88b3f98","attrs":{}}

{"type":"org.Arrow","source":{"selector":".card","id":"2e8732f3-8b98-4793-a6ea-406dd60ddba2"},"target":{"selector":".card","id":"1d8a053e-98ac-49a9-bab5-758d66759881"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"89e965e8-e1c2-4200-ba27-ed3d73fa82aa","attrs":{}}

{"type":"html.Element","position":{"x":80,"y":80},"size":{"width":170,"height":80},"angle":0,"divName":"mycell1","isCompany":true,"id":"0148eff1-ea1b-40b2-afc9-2bc031e586ce","z":1,"attrs":{}}

{"type":"html.Element","position":{"x":490,"y":90},"size":{"width":170,"height":80},"angle":0,"divName":"mycell2","id":"2e8732f3-8b98-4793-a6ea-406dd60ddba2","z":2,"attrs":{}}

{"type":"html.Element","position":{"x":558,"y":344},"size":{"width":170,"height":80},"angle":0,"divName":"mycell3","id":"1d8a053e-98ac-49a9-bab5-758d66759881","z":3,"attrs":{}}
```

- "type"が"org.Arrow"になっているものがブロックを接続する線です。"source"から"target"に接続されており、それぞれの"id"でどのブロックであるか特定できます。
- "type"がhtml.Element"になっているものがブロックです。描画されている座標("position")やサイズ("size")が取得できます。設定した"diveName"も反映されていることがわかります。こちらの"id"と"org.Arrow"の"source"や"target"の"id"を比較して操作することもできそうです。

## dia.Graph.prototype.getElements
getCellsではelementとlinkの全てを取得しましたが、getElementsでは全てのelementのみ取得します。

## dia.Graph.prototype.getLinks
同様にgetLinksでは全てのlinkのみ取得します。

## dia.Graph.prototype.getConnectedLinks
あるelementに接続されている全てのlinkを取得します。

```Javascript
var elements = graph.getElements();
console.log(JSON.stringify(elements[0]));
var links = graph.getConnectedLinks(elements[0]);
links.forEach(function(link){
    console.log(JSON.stringify(link));
});
```

結果

```
{"type":"html.Element","position":{"x":80,"y":80},"size":{"width":170,"height":80},"angle":0,"divName":"mycell1","isCompany":true,"id":"1d0d270e-642a-403e-808e-0d63d60dd589","z":1,"attrs":{}}

{"type":"org.Arrow","source":{"selector":".card","id":"1d0d270e-642a-403e-808e-0d63d60dd589"},"target":{"selector":".card","id":"f904240e-92fb-4af0-aef1-97f0422a047a"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"f3bd7d00-4697-4f29-acc5-331e857e69ba","attrs":{}}
```

- getElementsで取得したelementから最初のelementを取得してgetConnectedLinksに入れています。
- element[0]に接続されているlinkのみ取得されていることが"id"からわかります。
- オプションで「外に出ていくlink」や「内に入ってくるlink」を指定することもできます。

## dia.Graph.prototype.toJSON
getCellsとほぼ同じ情報が得られるようです。
toJSONが返却するのはJSON文字列ではない点にお気を付けください。

```Javascript
console.log(JSON.stringify(graph.toJSON())); 
```

結果

```
{"cells":[{"type":"org.Arrow","source":{"selector":".card","id":"26578cbf-aa01-40ab-8752-05c874a64fa9"},"target":{"selector":".card","id":"de7626c7-6cab-48e8-a5c2-8298e6a11483"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"cf9a1a1a-234f-43ec-83d6-019e8dc80010","attrs":{}},{"type":"org.Arrow","source":{"selector":".card","id":"de7626c7-6cab-48e8-a5c2-8298e6a11483"},"target":{"selector":".card","id":"b0a28724-1a33-4deb-8877-02c1378f7465"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"f5d76d9d-a256-483d-abc0-f1ba228e90a9","attrs":{}},{"type":"html.Element","position":{"x":80,"y":80},"size":{"width":170,"height":80},"angle":0,"divName":"mycell1","isCompany":true,"id":"26578cbf-aa01-40ab-8752-05c874a64fa9","z":1,"attrs":{}},{"type":"html.Element","position":{"x":190,"y":277},"size":{"width":170,"height":80},"angle":0,"divName":"mycell2","id":"de7626c7-6cab-48e8-a5c2-8298e6a11483","z":2,"attrs":{}},{"type":"html.Element","position":{"x":297,"y":483},"size":{"width":170,"height":80},"angle":0,"divName":"mycell3","id":"b0a28724-1a33-4deb-8877-02c1378f7465","z":3,"attrs":{}}]}
```

# おわりに
JointJSはあまり日本語の情報がないため、本家のAPI一覧を見ていくのが基本になります。
一方でAPIの数が多く探しにくいこともあるため、この記事が参考になれば幸いです。