Vega 学習 その2

前回は以下

[Vega 学習 その1](https://qiita.com/ihgs/items/18e155da0f1e04314268)


## 散布図

https://vega.github.io/vega/examples/scatter-plot/

このExample の場合、dataは外部から取得しています(`"url": "data/cars.json"`)。
dataの中身は、[ここ](https://github.com/vega/vega/blob/master/docs/data/cars.json)で確認できます。

### データ投入

Elasticsearch へのデータ投入は、今回は[Machine Learning]-[Data Visualizer]-[Import Data]を利用しました。
ただし、Importするには、Json ではなくNewline-delimited JSONである必要があるので、以下のスクリプトで変換。
https://gist.github.com/ihgs/caf192943889607afe3c4175fd776917

```
cat cars.json | python json2ndjson.py > cars.ndjson
```

### Vega HJSON

```
{
  $schema: https://vega.github.io/schema/vega/v3.3.1.json
  width: 200
  height: 200
  padding: 5
  data: [
    {
      name: source
      url: {  // imoprt したデータのindexを指定
        index: vega_example_cars
        body: {size: 1000}
      }
      format: {property: "hits.hits"}
      transform: [
        {
          type: filter
          expr: datum['_source'].Horsepower != null && datum['_source'].Miles_per_Gallon != null && datum['_source'].Acceleration != null
        }
      ]
    }
  ]
  scales: [
    {
      name: x
      type: linear
      round: true
      nice: true
      zero: true
      domain: {data: "source", field: "_source.Horsepower"}
      range: width
    }
    {
      name: y
      type: linear
      round: true
      nice: true
      zero: true
      domain: {data: "source", field: "_source.Miles_per_Gallon"}
      range: height
    }
    {
      name: size
      type: linear
      round: true
      nice: false
      zero: true
      domain: {data: "source", field: "_source.Acceleration"}
      range: [4, 361]
    }
  ]
  axes: [
    {
      scale: x
      grid: true
      domain: false
      orient: bottom
      tickCount: 5
      title: Horsepower
    }
    {
      scale: y
      grid: true
      domain: false
      orient: left
      titlePadding: 5
      title: Miles_per_Gallon
    }
  ]
  legends: [
    {
      size: size
      title: Acceleration
      format: s
      symbolStrokeColor: "#4682b4"
      symbolStrokeWidth: 2
      symbolOpacity: 0.5
      symbolType: circle
    }
  ]
  marks: [
    {
      name: marks
      type: symbol
      from: {data: "source"}
      encode: {
        update: {
          x: {scale: "x", field: "_source.Horsepower"}
          y: {scale: "y", field: "_source.Miles_per_Gallon"}
          size: {scale: "size", field: "_source.Acceleration"}
          shape: {value: "circle"}
          strokeWidth: {value: 2}
          opacity: {value: 0.5}
          stroke: {value: "#4682b4"}
          fill: {value: "transparent"}
        }
      }
    }
  ]
}
```

### メモ

* transform: filterを使い欠損データを除外
* marks: type にsymbolを指定。

## ネットワーク図

https://vega.github.io/vega/examples/force-directed-layout/

### データ投入

上と同様に、[Machine Learning]-[Data Visualizer]-[Import Data]を利用。
今回の[データ](https://github.com/vega/vega/blob/master/docs/data/miserables.json)は、`{"nodes": ..., "links": ...}`といった構造になっており、vegaで描画する際は、nodes, links を別々に扱っているので、それぞれindexをわけてImport してみます。
投入データは同様にスクリプトで変換しておきます。


```
cat miserables.json | python json2ndjson.py --key nodes > nodes.ndjson
cat miserables.json | python json2ndjson.py --key links > links.ndjson
```


### Vega HJSON


```
{
  $schema: https://vega.github.io/schema/vega/v3.3.1.json
  width: 700
  height: 500
  padding: 0
  autosize: none
  signals: [
    {name: "cx", update: "width / 2"}
    {name: "cy", update: "height / 2"}
    {
      name: nodeRadius
      value: 8
      bind: {input: "range", min: 1, max: 50, step: 1}
    }
    {
      name: nodeCharge
      value: -30
      bind: {input: "range", min: -100, max: 10, step: 1}
    }
    {
      name: linkDistance
      value: 30
      bind: {input: "range", min: 5, max: 100, step: 1}
    }
    {
      name: static
      value: true
      bind: {input: "checkbox"}
    }
    {
      description: State variable for active node fix status.
      name: fix
      value: false
      on: [
        {
          events: symbol:mouseout[!event.buttons], window:mouseup
          update: "false"
        }
        {events: "symbol:mouseover", update: "fix || true"}
        {
          events: "[symbol:mousedown, window:mouseup] > window:mousemove!"
          update: xy()
          force: true
        }
      ]
    }
    {
      description: Graph node most recently interacted with.
      name: node
      value: null
      on: [
        {
          events: symbol:mouseover
          update: fix === true ? item() : node
        }
      ]
    }
    {
      description: Flag to restart Force simulation upon data changes.
      name: restart
      value: false
      on: [
        {
          events: {signal: "fix"}
          update: fix && fix.length
        }
      ]
    }
  ]
  data: [
    //修正
    {
      name: node-data
      url: {index: "test_vega_tree_nodes", size: 1000}
      format: {type: "json", property: "hits.hits"}
    }
    {
      name: link-data
      url: {index: "test_vega_tree_links", size: 1000}
      format: {type: "json", property: "hits.hits"}
      transform: [
        {
          type: formula
          expr: datum['_source'].source
          as: source
        }
        {
          type: formula
          expr: datum['_source'].target
          as: target
        }
        {type: "formula", expr: "datum['_source'].value", as: "value"}
      ]
    }
  ]
  scales: [
    {
      name: color
      type: ordinal
      domain: {data: "node-data", field: "_source.group"}
      range: {scheme: "category20c"}
    }
  ]
  marks: [
    {
      name: nodes
      type: symbol
      zindex: 1
      from: {data: "node-data"}
      on: [
        {
          trigger: fix
          modify: node
          values: fix === true ? {fx: node.x, fy:node.y} : {fx: fix[0], fy: fix[1]}
        }
        {
          trigger: !fix
          modify: node
          values: "{fx: null, fy: null}"
        }
      ]
      encode: {
        enter: {
          fill: {scale: "color", field: "_source.group"}
          stroke: {value: "white"}
        }
        update: {
          size: {signal: "2 * nodeRadius * nodeRadius"}
          cursor: {value: "pointer"}
        }
      }
      transform: [
        {
          type: force
          iterations: 300
          restart: {signal: "restart"}
          static: {signal: "static"}
          signal: force
          forces: [
            {
              force: center
              x: {signal: "cx"}
              y: {signal: "cy"}
            }
            {
              force: collide
              radius: {signal: "nodeRadius"}
            }
            {
              force: nbody
              strength: {signal: "nodeCharge"}
            }
            {
              force: link
              links: link-data
              distance: {signal: "linkDistance"}
            }
          ]
        }
      ]
    }
    {
      type: path
      from: {data: "link-data"}
      interactive: false
      encode: {
        update: {
          stroke: {value: "#ccc"}
          strokeWidth: {value: 0.5}
        }
      }
      transform: [
        {
          type: linkpath
          require: {signal: "force"}
          shape: line
          sourceX: datum.source.x
          sourceY: datum.source.y
          targetX: datum.target.x
          targetY: datum.target.y
        }
      ]
    }
  ]
}
```

### メモ

* data: node,linkをそれぞれで取得。
* marks内のtransform のtype:force, force:link linkのデータをきちんと値を渡せるようにするため、source とtarget を設定するように変換を実施。なお、一番下のtype:linkpath で使っているdatum.source.xとかは、force:link で変換されたものを利用。


## まとめ

ファイルからデータを投入できる[Machine Learning]-[Data Visualizer]-[Import Data]は便利。
次回は、~~kibanaとより連携する部分を調べていこうと思います。~~ vega そのものについて解説していく記事を優先することにしました。