Kibana でローソク足チャートを出してみる

# 追記

2018.02.07追記。

Kibana6.2.0より、VegaがプラグインインストールなしでKibana本体だけで使えるようになっています。
Vegaの記法そのものについては、この記事は参考になる部分はありますが、その旨ご承知おきください。

あわせて、こちらもどうぞ[Kibana 6.2.0リリース記念　Vegaで遊ぼう](https://qiita.com/tsgkdt/items/45ec86bada9a7bea2997)

追記終わり。

# 概要

Kibanaでは、棒グラフや線グラフ、円グラフを始めとした基本的なグラフがVisualizationとして用意されていますが、
それ以外もプラグインを入れることで実現できたりします。

公式に紹介されているプラグインは、こちらで見ることができます。
https://www.elastic.co/guide/en/kibana/current/known-plugins.html

今回は、ローソク足チャートを出すために、ここにある[Vega-based visualizations](https://github.com/nyurik/kibana-vega-vis)を入れてやってみます。


# どんなグラフが描けますか

https://vega.github.io/vega/

詳しくはこちらのサイト、Examplesから見ることができます。

![A Visualization Grammar   Vega.png](https://qiita-image-store.s3.amazonaws.com/0/111676/1fbff9e6-14a9-6395-5b09-79e29bc4ea80.png)

で、今回はVega Liteの方にある「Candlestick Chart」を使ってみます。

![Example Gallery   Vega Lite.png](https://qiita-image-store.s3.amazonaws.com/0/111676/f082d8fe-8a2e-6bfc-aca5-40d1be0876c8.png)


# 先に結論

こんな画面を作ることができました。

## ローソク足

![ro soku   Kibana.png](https://qiita-image-store.s3.amazonaws.com/0/111676/f861743f-2482-1d7e-7cfd-c0bb66a823e7.png)


## 移動平均線を追加

![Kibana (2).png](https://qiita-image-store.s3.amazonaws.com/0/111676/527e77e5-0ee6-aafe-c80d-48d200436c38.png)


# 環境構築

## kibanaにプラグインのインストール

現時点での最新版の6.1.0では互換性がないと言われたので、kibanaは6.0.1を使用します。
12/19日現在、プラグインは0.5.1が最新でした。

```
kibana-plugin install \
    https://github.com/nyurik/kibana-vega-vis/releases/download/v0.5.1/vega_vis-0.5.1--for-Kibana-6.0.1.zip
```

環境にもよるのでしょうが、インストールには少し時間がかかりました。


## テストデータの用意

今回は日経平均株価でローソク足チャートを書いています。
以前にやった[「Elasticsearchを使って日経平均株価データでMachine Learningを体験する」](https://qiita.com/tsgkdt/items/bc3dfe27535cfd7fcf88) を使いまわし（再利用し）ます

データとしては、このような形として入ります。

```json
{
  "_index": "stock",
  "_type": "nikkei225",
  "_id": "105",
  "_score": 1,
  "_source": {
    "start_value": 15130.74,
    "timestamp": "2014-06-10T00:00:00",
    "high_value": 15184.77,
    "end_value": 14994.8,
    "low_value": 14966.8
   }
}
```

# Visualizationの作成

## 新規作成

Visualizationを選択し、画面下部の「Other」の部分に現れる「Vega」をダブルニープレス（選択）します。

![d1.png](https://qiita-image-store.s3.amazonaws.com/0/111676/20070bad-466d-a1a8-7d33-96213312c37e.png)

## 雛形の表示

標準で提供されているVisualizationであれば、選択後にはIndexやSaved Searchを選択する画面に遷移しますが、
Vegaの場合は、indexなどを指定する画面は表示されず、いきなりこのような画面となります。

初期表示時に、Dev Toolsのような画面が現れ、いきなり赤い文字でエラーのように見えると萎えます。
ざっと中身を眺めたあと、上部にある「JSON」ボタンを押すと、コメントがなくなりエラーは消えます。

![d2.png](https://qiita-image-store.s3.amazonaws.com/0/111676/89feccc8-a6e6-7b4e-0e16-ab5285587c13.png)

## jsonの記述

ここからは、https://vega.github.io/editor/#/ を開きつつ行うと良いでしょう。
こちらのサイトでは、データを直接渡したり、外部のjsonファイルから取得するようなサンプルとなっています。

このデータを受け渡す部分を、Elasticsearchのクエリと検索結果にする、という作業になります。

メインは、aggs以下なのですが、それ以外の項目もあるので整理します。

### 全体の構造

大まかな構造はこのようになっています。

```json
{
  "$schema": "https://vega.github.io/schema/vega-lite/v2.json",
  "title": "日経平均株価の推移",
  "width": 1024,
  "data": {
    "url": {
      "index": "stock",
      "%context_query%": "timestamp",
      "body": {}
      },
      "size": 0
    },
    "format": {"property": "aggregations.time_buckets.buckets"}
  },
  "transform": [],
  "layer": []
}
```

| 項目 | 説明 |
|---|----|
| $schema | vega側のスキーマ指定。通常は変更せずそのままで良いと思う|
| title | visualizationの上部に表示されるタイトル名 |
| width | 横幅。Dashboardに貼り付けた際に領域の大きさに自動でフィットするわけではなかった |
| data  | 肝心のグラフに描画するデータ設定部分。|
| data.url.index | index名を指定する。logstash-* などのイメージ |
| data.url.%context_query% | kibanaの上部にある時間の範囲指定部分のクエリーの対象フィールドを指定する <br>今回はtimestampにdatetimeがあるので、"timestamp"としている。|
| data.url.body | aggregationの指定をここで行う。|
| transform | データ加工のフェーズ |
| layer | グラフ表示部分。ローソク足の場合は、縦の細い棒のグラフと矩形のグラフの２つが重なって表現されている。これをレイヤーで表現している|


### data.url.body

aggs以下は、通常のElasticsearchでaggregationを取得する際のクエリと変わりません。
注意が必要なのは、通常kibanaで生成されるリクエストでは aggs以下に1や2といった名前となっていますが、
Vegaの場合、後からlayerの部分で明示的にどの項目をy軸の値とするか、といった指定をします。
そのため、わかりやすい名称にしています。

- date histgramを1日間隔で取得
- 高値、安値、始値、終値を1日ごとに取得
- 終値の25日移動平均を取得

```json
 "url": {
      "index": "stock",
      "%context_query%": "timestamp",
      "body": {
        "aggs": {
          "time_buckets": {
            "date_histogram": {
              "field": "timestamp",
              "interval": "1d",
              "extended_bounds": {
                "min": {"%timefilter%": "min"},
                "max": {"%timefilter%": "max"}
              },
              "min_doc_count": 0
            },
            "aggs": {
              "high_value": {"avg": {"field": "high_value"}},
              "low_value": {"avg": {"field": "low_value"}},
              "end_value": {"avg": {"field": "end_value"}},
              "start_value": {"avg": {"field": "start_value"}},
              "moving": {
                "moving_avg": {
                  "buckets_path": "moving_end",
                  "model": "simple",
                  "window": 25,
                  "settings": {}
                }
              },
              "moving_end": {"avg": {"field": "end_value"}}
            }
          }
        }
      }
```

auto intervalとする場合は、interval指定を、このように書けば良いです。

```json
"interval": {"%autointerval%": true}
```
### layer ローソク足部分

markとしてrule（縦線）, bar（矩形部分）の２つがあります。
x軸はdate histgramなので共通ですが、y軸の値の指定が違います。

typeに指定されているtemporalは時系列の時間、quantitativeは数値データということぐらいの認識で良いです。
大事なのは、fieldに指定するところです。
ここに、{aggregation名}.value で値をセットすることができます。


```json
"layer": [
    {
      "mark": "rule",
      "encoding": {
        "x": {
          "field": "key",
          "type": "temporal",
          "timeUnit": "yearmonthdate",
          "axis": {"format": "%m/%d", "title": "月/日"}
        },
        "y": {
          "field": "low_value.value",
          "type": "quantitative",
          "scale": {"zero": false},
          "axis": {"title": ""}
        },
        "y2": {
          "field": "high_value.value",
          "type": "quantitative"
        },
        "color": {
          "field": "isIncrease",
          "type": "nominal",
          "scale": {"range": ["#ae1325", "#06982d"]},
          "legend": null
        }
      }
    },
    {
      "mark": "bar",
      "encoding": {
        "x": {
          "field": "key",
          "type": "temporal",
          "timeUnit": "yearmonthdate"
        },
        "y": {
          "field": "start_value.value",
          "type": "quantitative"
        },
        "y2": {
          "field": "end_value.value",
          "type": "quantitative"
        },
        "size": {"value": 5},
        "color": {
          "field": "isIncrease",
          "type": "nominal",
          "legend": null
        }
      }
    },
```

| graph | item | value | remarks |
|-------|------|-------|---------|
| rule  | y.field    | low_value.value | 安値 |
| rule  | y2.field    | high_value.value | 高値 |
| bar   | y.field    | start_value.value | 始値 |
| bar   | y2.field    | end_value.value | 終値 |


### 移動平均部分

Lineチャートなので、typeをlineとし、なめらかになるように interpolateをcardinalにしています。
先と同じように yのfieldに値をセットします。

```json
{
      "mark": {
          "type":"line",
          "interpolate": "cardinal"
      },
      "encoding": {
        "x": {
          "field": "key",
          "timeUnit": "yearmonthdate",
          "axis": {"format": "%m/%d", "title": "月/日"}
        },
        "y": {
          "field": "moving_end.value",
          "type": "quantitative"
        }
      }
    }
```

### おまけ

始値が終値よりも低い場合は、「増えた」とみなす、というようなデータ処理をtransformで行っています。

```json
  "transform": [
    {
      "calculate": "datum.start_value.value < datum.end_value.value",
      "as": "isIncrease"
    }
  ],
```

isIncreaseの内容によって色を変えている例です。

```json
 "color": {
          "field": "isIncrease",
          "type": "nominal",
          "scale": {"range": ["#ae1325", "#06982d"]},
          "legend": null
        }
```


# 参考資料

今回試したものの内容は、以下にあげてあります。

- https://gist.github.com/tsgkdt/bf19b6f24e993aad8be0ef0930b506ca


# まとめと感想

1. Vegaの記法、内容は知っておく必要がある。
2. 基本はグラフにデータを渡す部分が、ElasticsaerchのAggregaionになっただけと考えれば、さほど構えることはない。
3. https://vega.github.io/editor/#/ とにらめっこしながらやれば、何とかなる。
4. プラグインがKibanaのメンテナンス速度についていってくれると、使う側はとてもうれしい。

自分の望むVisualizationを、プラグインを書くことで実現することもできるけれど、そこまで技術力高くないマン、Reactとかよく分からない非フロントエンドのエンジニアには、プラグインの拡充はうれしいですね！！！