力学モデル描画ライブラリー Springy のご紹介


公式サイト:[springy](http://getspringy.com/)

# 使うと何が嬉しいのか

（グラフ理論的な意味での）グラフを自動的にそれなりに綺麗な配置で表示したいときに使います。

[デモ](http://getspringy.com/demo.html)

# どうやって実現しているのか
[力学モデル](https://ja.wikipedia.org/wiki/%E5%8A%9B%E5%AD%A6%E3%83%A2%E3%83%87%E3%83%AB_(%E3%82%B0%E3%83%A9%E3%83%95%E6%8F%8F%E7%94%BB%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0))(Force-directed graph drawing)アルゴリズムを使ってノードの位置を決定します。


# 使い方の手引き
## ファイル構成
- [springy.js](https://github.com/dhotson/springy/blob/master/springy.js) : 本体
- [springyui.js](https://github.com/dhotson/springy/blob/master/springyui.js) : Canvasを使ったUI
- [demo.html](https://github.com/dhotson/springy/blob/master/demo.html) : デモ用のhtml。簡単な使い方がわかります。

## 主要なオブジェクト
主要な三つのオブジェクト

- Springy.Graph : グラフデータを持つモデルオブジェクト
- Layout.ForceDirected : 力学レイアウトアルゴリズム
- Springy.Renderer : レイアウトの制御用インターフェース

## API

API設計には多少の混乱が見られます。

### Springy.GraphのCRUD
#### Node CREATE
##### [Nodeコンストラクタ](https://github.com/dhotson/springy/blob/master/springy.js#L57)

Springy.Node オブジェクトを作ります。

- id : 識別子。nodeSetで取得するときに使います
- data : 任意のデータ。オブジェクトを入れるのが一般的

```js
new Springy.Node(1234, {label: 'ABC'});
```

##### [addNode](https://github.com/dhotson/springy/blob/master/springy.js#L78)

Springy.Node オブジェクトをグラフに追加します。
idを指定して追加したい場合に使います。

- node : Springy.Node オブジェクト

```js
graph.addNode(new Springy.Node(1234))
```

##### [addNodes](https://github.com/dhotson/springy/blob/master/springy.js#L89)

複数のノードを一括して使いたいときに使います。
引数は（複数の）文字列です。
引数の数は任意です。

```js
graph.addNodes('mark', 'higgs', 'other', 'etc');
```

指定した文字列はidとしても使われるので、重複は許されません。
addEdgesと組み合わせて、id指定でエッジを追加する想定です。

:-1: Springy.GraphのAPIでなく、Helper関数として提供されていれば・・・

##### [newNode](https://github.com/dhotson/springy/blob/master/springy.js#L148)

idを自動採番してノードを追加します。

```js
graph.newNode({label: '1'});
```

:-1: newNodeという名前はちょっと・・・

#### Node READ
##### nodeSet

ノードはオブジェクトで管理されています。
idを指定して取得できます。

```js
var node = graph.nodeSet(1234)
```

#### Node UPDATE

ノードオブジェクトを直接編集します。
特にAPIは提供されていません。

#### Node DELETE
##### [removeNode](https://github.com/dhotson/springy/blob/master/springy.js#L208)

ノードを削除します。付随するエッジも自動的に削除します。

```js
graph.removeNode(graph.nodeSet(1234))
```

:-1: idが指定できたら嬉しいですね。

##### [filterNodes](https://github.com/dhotson/springy/blob/master/springy.js#L299)

関数にマッチしたノードを残して、それ以外を削除します。

```js
graph.filterNodes(function(node){return node.id !== 1234;})
```

:-1: 破壊的な関数にfilterHogeという名前をつけるのはちょっと・・・

#### Edge CREATE
##### [Edgeコンストラクタ](https://github.com/dhotson/springy/blob/master/springy.js#L67)


Springy.Edge オブジェクトを作ります。

- id : 識別子。
- sourge : ソースノード
- target : ターゲットノード
- data : 任意のデータ。オブジェクトを入れるのが一般的

```js
var node1 = graph.newNode({label: '1'});
var node2 = graph.newNode({label: '2'});

new Springy.Edge(123, node1, node2, {labes: '1-2'});
```

:-1: エッジに任意のidをつけたいことがあまりないので使いません。

##### [addEdge](https://github.com/dhotson/springy/blob/master/springy.js#L99)

Springy.Edge オブジェクトをグラフに追加します。

- edge : Springy.Edge オブジェクト

```js
graph.addNode(new Springy.Node(1234))
```

:-1: エッジに任意のidをつけたいことがあまりないので使いません。

##### [addEdges](https://github.com/dhotson/springy/blob/master/springy.js#L129)

複数のエッジを一括でグラフに追加します。

- 無限引数 : [ノードid1, ノードid2, data]の配列

```js
graph.addEdges([1, 2], [3, 4])
```

:+1: ノードidで指定したい場合に便利です。

##### [newEdge](https://github.com/dhotson/springy/blob/master/springy.js#L154)

idを自動採番してエッジを追加します。

- sourge : ソースノード
- target : ターゲットノード
- data : 任意のデータ。オブジェクトを入れるのが一般的

```js
var node1 = graph.newNode({label: '1'});
var node2 = graph.newNode({label: '2'});

graph.newEdge(node1, node2, {labes: '1-2'});
```

ノードとエッジを同時に追加するときに便利です。

#### Edge READ
##### [getEdges](https://github.com/dhotson/springy/blob/master/springy.js#L198)

ノードを指定してエッジを取得します。

- node1 : ノード１
- node2 : ノード２

```js
var edges = graph.getEdges(node1, node2);
```

:-1: idが指定できたら嬉しいです。

#### Edge UPDATE

エッジオブジェクトを直接編集します。
特にAPIは提供されていません。

#### Edge DELETE
##### [removeEdge](https://github.com/dhotson/springy/blob/master/springy.js#L235)

エッジを削除します。

```js
graph.removeEdge(graph.getEdges(node1, node2))
```

:-1: idが指定できたら嬉しいです。

##### [filterNodes](https://github.com/dhotson/springy/blob/master/springy.js#L308)

関数にマッチしたエッジを残して、それ以外を削除します。

```js
graph.filterNodes(function(node){return edge.source.id !== 1 || edge.target.id !== 2;})
```

:-1: 破壊的な関数にfilterHogeという名前をつけるのはちょっと・・・


### Springy.Graphのイベント
#### [addGraphListener](https://github.com/dhotson/springy/blob/master/springy.js#L318)

- obj : graphChangedという関数を持つオブジェクトを指定します。引数は何もくれません。

```js
graph.addGraphListener({
  graphChanged: () => console.log(graph.edges, graph.nodes)
})
```

:-1: 特定の名前の関数を持ったオブジェクトを指定するのはあまり見ないAPIです。
:-1: 引数で変更内容かgraphのスナップショットを通知してくれると嬉しいです。

### [Springy.Layout.ForceDirectedのコンストラクタ](https://github.com/dhotson/springy/blob/master/springy.js#L330)

- graph : グラフオブジェクト
- stiffness : ばね定数
- repulsion : 斥力
- damping : 減衰係数
- minEnergyThreshold : レンダリングを止める閾値。初期値0.01

```js
var graph = new Springy.Graph(),
  layout = new Springy.Layout.ForceDirected(graph, 400.0, 400.0, 0.5)
```

:-1: minEnergyThresholdにだけ初期値があるのはなぜ？

### [Springy.Rendererのコンストラクタ](https://github.com/dhotson/springy/blob/master/springy.js#L645)

- layout : レイアウトオブジェクト
- clear : クリア時のコールバック関数
- drawEdge : エッジ描画用コールバック関数。引数はedge, p1, p2。
- drawNode : ノード描画用コールバック関数。引数はnode, p。
- onRenderStop : Renderストップ時のコールバック。使い道は不明
- onRenderStart : Renderスタート時のコールバック。使い道は不明

:-1: イベントにしてくれればコンストラクタ以外でも指定できて便利です。

## レイアウトと描画領域のMapping方法

レイアウト中ではノードはサイズ制限の無い、無限の空間を移動しています。
`drawEdge`、`drawNode`コールバックで渡される位置は、レイアウト中の座標です。

描画する際は有限の領域に描画する必要があります。
そこで以下のようなアルゴリズムで座標を返還します。

```js
var currentBB = layout.getBoundingBox(),
  size = currentBB.topright.subtract(currentBB.bottomleft),
  x = p.subtract(currentBB.bottomleft).divide(size.x).x,
  y = p.subtract(currentBB.bottomleft).divide(size.y).y
```

`layout.getBoundingBox`でノードが存在する領域の座標を取得できます。

また、描画領域側でノード移動した場合は逆変換が必要です。

```js
var currentBB = layout.getBoundingBox(),
  size = currentBB.topright.subtract(currentBB.bottomleft),
  px = x * size.x + currentBB.bottomleft.x,
  py = y * size.y + currentBB.bottomleft.y
```

:-1: 最初から百分率でノーマライズした座標で入出力できればと思います。

## レイアウト中のノードの移動方法
ノードの位置はグラフではなくレイアウトが保持しています。
レイアウトからノードの位置を取得して、更新します。

```js
var nodePoint = layout.point(graph.nodeSet[id])

nodePoint.p.x = px
nodePoint.p.y = py
```

:-1: idとx, yを指定して更新できるAPIがあると嬉しいです。