フローチャート描画とデータ連携

## フローチャート作成・描画をマウス操作で行い、データ連携させる
フローチャートをGUIで作図し、一覧のデータと連携させるということをやったので備忘録です。
主にライブラリに頼りますので、以下のようなものを使いました。

- vis.js
- joint.js


## vis.js
vis.jsのサンプルです。
オプションについては公式に詳細があるのでそちらを。
[vis.js](http://visjs.org/docs/network/)

```typescript
data: any;

network: Network;
nodes: any;
edges: any;

const nodeData: any[] = [];
    for (const element of ELEMENTLIST) {
      nodeData.push({id: element.elementId, label: element.elementName});
    }

    const nodes = new DataSet(nodeData);
    const edges = new DataSet([
      {from: '01', to: '02'},
      {from: '01', to: '03'},
      {from: '02', to: '04'},
      {from: '02', to: '05'},
    ]);

    // create a network
    this.data = {
      nodes: nodes,
      edges: edges
    };
    const options = {
      layout: {
        hierarchical: {
          sortMethod: 'directed'
        },
      },
      interaction: {
        dragNodes: false,
        dragView: false,
        hover: true,
      },
      manipulation: {
        enabled: false,
        initiallyActive: false,
        addEdge: function (data, callback) {
          console.log('add edge', data);
          if (data.from === data.to) {
              const r = confirm('Do you want to connect the node to itself?');
              if (r === true) {
                  callback(data);
              }
          } else {
              callback(data);
          }
          // after each adding you will be back to addEdge mode
          this.network.addEdgeMode();
        },
        controlNodeStyle: {
          // all node options are valid.
        }
      },
      edges: {
        arrows: 'to',
        smooth: false
      },
      nodes: {
        shape: 'box',
      },
      physics: {
        enabled: true,
        hierarchicalRepulsion: {
            nodeDistance: 180,
        },
        solver: 'hierarchicalRepulsion'
      }
    };
    this.network = new Network(this.visElement.nativeElement, this.data, options);
    this.network.addEdgeMode();
```

## joint.js
途中でこっちに乗り換えました。
使い勝手とカスタマイズ性がこちらの方が良さそうだったので。

``` typescript
this.graph = new joint.dia.Graph;
    const paper = new joint.dia.Paper({
      el: document.getElementById('myholder'),
      width: '200vh',
      height: '100vh',
      model: this.graph,
      background: {
        color: '#eaeaea',
      },
      gridSize: 10,
      drawGrid: {
        color: '#efefef',
        name: 'mesh'
      },
      restrictTranslate: true,
      defaultRouter: { name: 'manhattan' },
      defaultLink: new joint.dia.Link({
        attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', stroke: '#505050', fill: '#505050' }, '.connection' : { stroke: '#505050', strokeWidth: 2 } }
      }),
      linkPinning: false,
      validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
        // Prevent linking from input ports.
        if (magnetS && magnetS.getAttribute('port-group') === 'in') {
          return false;
        }
        // Prevent linking from output ports to input ports within one element.
        if (cellViewS === cellViewT) {
          return false;
        }
        // Prevent linking to input ports.

        return magnetT && magnetT.getAttribute('port-group') === 'in';
      },
      validateMagnet: function(cellView, magnet) {
          // Note that this is the default behaviour. Just showing it here for reference.
          // Disable linking interaction for magnets marked as passive (see below `.inPorts circle`).
          return magnet.getAttribute('magnet') !== 'passive';
      }
    });
    paper.scale(0.8);
```
<br>
エレメントの設定はこんな感じ。
詳細はhttp://visjs.org/docs/network/

```typescript
export const layout = {
  size: { width: 200, height: 80 },
  inPorts: ['in'],
  outPorts: ['out'],
  ports: {
      groups: {
          'in': {
              attrs: {
                  '.port-body': {
                      fill: '#16A085',
                      magnet: 'passive'
                  }
              },
              position: 'top',
          },
          'out': {
              attrs: {
                  '.port-body': {
                      fill: '#E74C3C'
                  }
              },
              position: 'bottom'
          }
      }
  },
  attrs: {
      '.label': { text: 'agra', 'ref-x': .5, 'ref-y': .2 },
      rect: { fill: '#2ECC71' }
  },
  strokeWidth: 0,
  cursor: 'pointer',
  magnet: true,
  label: {
    fill: '#333333',
    fontWeight: 'bold',
    cursor: 'pointer',
  },
  rect: {
    fill: 'red'
  },
  refPoints: '0,10 10,0 20,10 10,20',
  filter: {
    name: 'dropShadow',
    args: {
      dx: 2,
      dy: 2,
      blur: 3,
      angle: 50,
    }
  },
  defaultFill: {
    type: 'linearGradient',
    stops: [
      { offset: '0%', color: '#ffffff' },
      { offset: '100%', color: '#e2f1fc' },
    ],
    attrs: { x1: '0%', y1: '0%', x2: '0%', y2: '100%' }
  },
  executingFill: {
    type: 'linearGradient',
    stops: [
      { offset: '0%', color: '#fac695' },
      { offset: '37%', color: '#f5ab66' },
      { offset: '100%', color: '#ef8d31' },
    ],
    attrs: { x1: '0%', y1: '0%', x2: '0%', y2: '100%' }
  },
};

```

リンク追加はこんな感じです。

```typescript
new joint.dia.Link({
      source: {
        id: source,
        port: ' '
      },
      target: {
        id: target,
        port: ''
      },
      attrs: {
        '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', stroke: '#505050', fill: '#505050' }, '.connection' : { stroke: '#505050', strokeWidth: 2 }
      }
    });
```

## データ連携
作りたいエレメントとリンクの数だけ回せばデータができる。
エレメントの追加や動的なリンク情報取得は同様にaddしたり、portのリンク情報取得するだけ。
グラフのイベント発火を待ってデータ取得する。

```typescript
this.paper.on('link:connect', (linkView, evt, cellView, magnet, arrowhead) => {
  this.graph.getConnectedLinks(
    this.graph.attributes.cells._byId[linkView.model.attributes.source.id],
    { outbound: true}
  )
}
```

## 総括
ライブラリの使い方という感じになってしまいましたが、どのライブラリも情報少ないし使いづらい。