vega-liteのmulti-view

# ■ はじめに
前回の記事[vega-liteの基本](https://qiita.com/contrail_triple_crown/items/b7cc0c3922657b17327c)の続きになります。
前回ご紹介したviewは一つのviewを表示するシングルviewです。
今回は同じデータで複数のview表示を行うMulti-viewついて書いていこうと思います。


## Multi-view
[Composing Layered & Multi-view Plots](https://vega.github.io/vega-lite/docs/composition.html#faceting)
vegaには以下の演算子（公式ドキュメントには演算子と記載されている）を用いて複数のviewを表示する事ができます。
- Concatenation
- Layering
- Faceting
- Repeating
- Resolution

# ■ concat
[Concatenating views](https://vega.github.io/vega-lite/docs/concat.html)
> To place views side-by-side, Vega-Lite’s view composition provides the following concatenation operators:

記載の通り複数のviewを並べて配置します。
以下の3つ種類があります。
- vconcat
  - 垂直に並べる 
- hconcat
  - 水平に並べる
- concat
  - 一般的な連結（columnsプロパティで水平に並べる数を指定する）


## 公式の例
[facet page](https://vega.github.io/vega-lite/docs/facet.html)のvconcatの例です。
```json-doc:vega-lite
{
  "data": {
    "url": "data/weather.csv"
  },
  "transform": [{
    "filter": "datum.location === 'Seattle'"
  }],
  "hconcat": [
    {
      "mark": "bar",
      "encoding": {
        "x": {
          "timeUnit": "month",
          "field": "date",
          "type": "ordinal"
        },
        "y": {
          "aggregate": "mean",
          "field": "precipitation"
        }
      }
    },
    {
      "mark": "point",
      "encoding": {
        "x": {
          "field": "temp_min",
          "bin": true
        },
        "y": {
          "field": "temp_max",
          "bin": true
        },
        "size": {"aggregate": "count"}
      }
    }
  ]
}
```
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/ed4140c0-ef6e-3e7f-0900-49c27c16330b.png)
見ての通り、水平に別々のviewを並べて表示しています。

## 入れ子にする
concatは入れ子にできます。
上記の例を少し拡張します。
```json-doc:vega-lite
{
  "data": {
    "url": "data/weather.csv"
  },

  "vconcat": [
    {
      "transform": [{
        "filter": "datum.location === 'Seattle'"
      }],
      "hconcat": [
        {
          "mark": "bar",
          "encoding": {
            "x": {
              "timeUnit": "month",
              "field": "date",
              "type": "ordinal"
            },
            "y": {
              "aggregate": "mean",
              "field": "precipitation"
            }
          }
        },
        {
          "mark": "point",
          "encoding": {
            "x": {
              "field": "temp_min",
              "bin": true
            },
            "y": {
              "field": "temp_max",
              "bin": true
            },
            "size": {"aggregate": "count"}
          }
        }
      ]
    },
    {
      "transform": [{
        "filter": "datum.location === 'New York'"
      }],
      "hconcat": [
        {
          "mark": "bar",
          "encoding": {
            "x": {
              "timeUnit": "month",
              "field": "date",
              "type": "ordinal"
            },
            "y": {
              "aggregate": "mean",
              "field": "precipitation"
            }
          }
        },
        {
          "mark": "point",
          "encoding": {
            "x": {
              "field": "temp_min",
              "bin": true
            },
            "y": {
              "field": "temp_max",
              "bin": true
            },
            "size": {"aggregate": "count"}
          }
        }
      ]
    }
  ]
}
```
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/6a3c7569-7692-7e2b-27a4-cf7765eb3dda.png)

hconcatで水平に並べたviewを縦に並べてます。
元々の例にはtransformのfilterを使って、locationをシアトルに絞って表示してましたので、加えたもう一つのviewでは同じデータにあるニューヨークでfilterしてみました。
```
vconcat[
  {
    transform: [filter]  // (locaton=シアトル)
    hconcat: [
      {view},
      {view}
    ]
  },
  {
    transform: [filter]  // (locaton=ニューヨーク)
    hconcat: [
      {view},
      {view}
    ]
  }
```

# ■ layer
[Layering views](https://vega.github.io/vega-lite/docs/layer.html)
> Sometimes, it’s useful to superimpose one chart on top of another. You can accomplish this by using the layer operator. This operator is one of Vega-Lite’s view composition operators. To define a layered chart, put multiple specifications into an array under the layer property.

チャートを別のチャートに重ねるとありますね。
1つのviewに複数のグラフを重ねるのがlayerになります。

## 公式の例
[facet page](https://vega.github.io/vega-lite/docs/facet.html)のvconcatの例です。
```json-doc:vega-lite
{
  "data": {"url": "data/stocks.csv"},
  "layer": [
    {
      "mark": "line",
      "encoding": {
        "x": {"field": "date", "type": "temporal"},
        "y": {"field": "price", "type": "quantitative"},
        "color": {"field": "symbol", "type": "nominal"}
      }
    },
    {
      "mark": "rule",
      "encoding": {
        "y": {"field": "price", "aggregate": "mean"},
        "size": {"value": 2},
        "color": {"field": "symbol"}
      }
    }
  ]
}
```
#### 1個目のview
![04-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/dcfd728e-50da-73c0-c7f5-c3bd5ff16fdc.png)
#### 2個目のview
![04-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/ca73b943-81d2-c433-1bcf-6f760a7fa61a.png)
#### 合わせて表示
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/44cece6d-9ae1-5f26-ce6f-ea8445ea7a60.png)
これは、mark:lineとmark:ruleの二つのviewを同じviewに重ねて表示しています。

## その他の例1
棒グラフと線グラフを同じviewに表示してみます。
```json-doc:vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {
    "values": [
      {
        "a": 1,
        "b": "hoge",
        "c": 100
      },
      {
        "a": 2,
        "b": "hoge",
        "c": 90
      },
      {
        "a": 3,
        "b": "hoge",
        "c": 115
      },
      {
        "a": 4,
        "b": "fuga",
        "c": 233
      },
      {
        "a": 5,
        "b": "fuga",
        "c": 68
      }
    ]
  },
  "layer": [
    {
      "mark": "bar",
      "encoding": {
        "x": {
          "field": "a"
        },
        "y": {
          "field": "c",
          "type": "quantitative"
        },
        "color": {
          "field": "c"
        }
      }
    },
    {
      "mark": "line",
      "encoding": {
        "x": {
          "field": "a"
        },
        "y": {
          "field": "c",
          "type": "quantitative"
        },
        "color": {
          "value": "#196004"
        }
      }
    }
  ]
}
```
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/efd4fc10-d36c-3b69-0cd5-ca9b916b97c4.png)



## その他の例2
ヒートマップと値を表示します。
ここでは上記の例と同じデータを使ってます。
```json-doc:vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {
     ・・・
  },
  "encoding": {
    "x": {
      "field": "a"
    },
    "y": {
      "field": "b"
    }
  },
  "layer": [
    {
      "mark": "rect",
      "encoding": {
        "color": {
          "field": "c"
        }

      },
    },
    {
      "mark": "text",
      "encoding": {
        "text": {
          "field": "c"
        }
      }
    }
  ]
}
```
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/554438d1-2414-e034-1e42-4ae329d2ab57.png)

ここではてなが一つ。
一つ目のencodingがlayerの外にありますよね、これが混乱の種なんですよ・・・
公式ドキュメントを見てもencodengやcolorやfield等が様々な個所で出てくるので慣れるまではちょっと辛かったですね。

この例に関して私なりに解釈すると、
- まずは大外のブロックが単一のviewと考えます。
- 大外のviewに対してencodingでx軸y軸を定義します。
- layerブロックの一つ目で、mark:rectを描画する（軸は外で指定したもの）
- もう一つで同じviewに数値を表示するようなイメージですかね。

この解釈や表現が正しいかわかりませんが、現状このように言えると考えてます。


# ■ Facet
[Faceting a Plot into a Trellis Plot](https://vega.github.io/vega-lite/docs/facet.html)
> A Trellis plot (or small multiple) is a series of similar plots that displays different subsets of the same data, facilitating comparison across subsets.

> トレリスプロット（または小さな倍数）は、同じデータの異なるサブセットを表示する一連の類似したプロットであり、サブセット間の比較を容易にします。

はい、なんのこっちゃですね。

## 公式の例
[facet page](https://vega.github.io/vega-lite/docs/facet.html)の一番最初の例です。
```json-doc:vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {"url": "data/cars.json"},
  "facet": {"row": {"field": "Origin"}},
  "spec": {
    "mark": "bar",
    "encoding": {
      "x": {
        "bin": {"maxbins": 15},
        "field": "Horsepower",
        "type": "quantitative"
      },
      "y": {"aggregate": "count", "type": "quantitative"}
    }
  }
}
```
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/edec58a0-fac5-1ca9-3403-7c87fb3553af.png)

使用しているデータは[/data/cars.json](https://github.com/vega/vega/blob/main/docs/data/cars.json)です。
データとソースとviewを見ていただければわかると思いますが
+ field"Horsepower"をx軸として
+ y軸が個数(count)の棒グラフを
+ filed"Origin"をkeyとして垂直に並べる

これをやってくれる機能facetとなります。

このfacetプロパティの"row"を"culmun"に変更して見て下さい。
横に並びます。
```diff:vega-lite
{
  ・・・
-  "facet": {"row": {"field": "Origin"}},
+  "facet": {"column": {"field": "Origin"}},
  "spec": {
    ・・・
  }
}
```

## 入れ子にする
はい、concatでも示したようにfacetの中でも入れ子にできます。
事例やソースは[複雑な入れ子のview](■-複雑な入れ子のview)の方に記載します。

# ■ repeat
[Repeat a View](https://vega.github.io/vega-lite/docs/repeat.html)
> The repeat operator is part of Vega-Lite’s view composition. It provides a shortcut that creates a view for each entry in an array of fields. This operator generates multiple plots like facet. However, unlike facet it allows full replication of a data set in each view.

facetのような複数プロットの表示をするが、対象のフィールドを指定して表示できるものです。
ただし、facetとは異なり、各ビューでデータセットを完全に複製できます。

## 公式の例
```json-doc:vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "",
  "data": {"url": "data/weather.csv"},
  "repeat": ["temp_max", "precipitation", "wind"],
  "spec": {
    "mark": "line",
    "encoding": {
      "x": {"field": "date", "timeUnit": "month"},
      "y": {"field": {"repeat": "repeat"}, "aggregate": "mean"},
      "color": {"field": "location"}
    }
  }
}
```
![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/574ad21a-4684-45e2-8142-1d374bcba15b.png)
### この例のデータ
ここでは[docs/data/weather.csv](https://github.com/vega/vega/blob/main/docs/data/weather.csv)を使ってます。
```csv
location,date,precipitation,temp_max,temp_min,wind,weather
Seattle,2012-01-01,0.0,12.8,5.0,4.7,drizzle
Seattle,2012-01-02,10.9,10.6,2.8,4.5,rain
・・・
New York,2012-01-01,1.8,10.0,3.3,5.1,rain
New York,2012-01-02,0.0,10.0,0.6,8.7,sun
```
これは、x軸がdateで、y軸がrepeatに設定したフィールドになります。
一つ目が最高気温、二つ目が降水量、三つめが風速で、colorに指定してるのがlocationなのでここではニューヨークとシアトルの地名が対象となってます。
データには最低気温もあるので以下のように変更するとviewが一つ増えます。
```diff:vega-lite
{
  ・・・
-    "repeat": ["temp_max", "precipitation", "wind"],
+    "repeat": ["temp_max", "precipitation", "wind", "temp_min"],
}
```

## facetでやってみた
facetを使ってに同じようにできないものかと思い試してみる。
上記と同様のデータを使いますが、facetで扱うにはデータ構造を変更する必要があります。
ちゃんとしたデータを作るのがしんどかったので、簡単にそして少しだけのデータでやってみました。

```json-doc:vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "",
  "data": {
    "values": [
      {
        "location": "Seattle",
        "data": [
          {
            "date": "2012-01-01",
            "values": [
              {
                "type": "precipitation",
                "value": 0
              },
              {
                "type": "temp_max",
                "value": 12.8
              },
              {
                "type": "temp_min",
                "value": 5
              },
              {
                "type": "wind",
                "value": 4.7
              }
            ]
          },
          {
            "date": "2012-01-02",
            "values": [
              {
                "type": "precipitation",
                "value": 10.9
              },
              {
                "type": "temp_max",
                "value": 10.6
              },
              {
                "type": "temp_min",
                "value": 2.8
              },
              {
                "type": "wind",
                "value": 4.8
              }
            ]
          },
          {
            "date": "2012-01-03",
            "values": [
              {
                "type": "precipitation",
                "value": 0.8
              },
              {
                "type": "temp_max",
                "value": 11.7
              },
              {
                "type": "temp_min",
                "value": 7.2
              },
              {
                "type": "wind",
                "value": 2.3
              }
            ]
          }
        ]
      },
      {
        "location": "New York",
        "data": [
          {
            "date": "2012-01-01",
            "values": [
              {
                "type": "precipitation",
                "value": 1.8
              },
              {
                "type": "temp_max",
                "value": 10
              },
              {
                "type": "temp_min",
                "value": 3.3
              },
              {
                "type": "wind",
                "value": 5.1
              }
            ]
          },
          {
            "date": "2012-01-02",
            "values": [
              {
                "type": "precipitation",
                "value": 0
              },
              {
                "type": "temp_max",
                "value": 10
              },
              {
                "type": "temp_min",
                "value": 0.6
              },
              {
                "type": "wind",
                "value": 8.7
              }
            ]
          },
          {
            "date": "2012-01-03",
            "values": [
              {
                "type": "precipitation",
                "value": 0.1
              },
              {
                "type": "temp_max",
                "value": 0.6
              },
              {
                "type": "temp_min",
                "value": -8.9
              },
              {
                "type": "wind",
                "value": 8.2
              }
            ]
          }

        ]
      }
    ]
  },
  "transform": [
    {
      "flatten": ["data"],
      "as": ["_data"]
    },
    {
      "flatten": ["_data.values"],
      "as": ["_values"]
    }
  ],
  "facet": {
    "column": {
      "field": "_values.type"
    }
  },
  "spec": {
    "mark": "line",
    "encoding": {
      "x": {
        "field": "_data.date"
      },
      "y": {
        "field": "_values.value",
        "type": "quantitative"
      },
      "color": {
        "field": "location"
      }
    }
  },
  "resolve": {"scale": {"color": "independent"}}
}
```
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/e86c4dd3-c924-d7dc-d151-a0e8b2406e5d.png)
とりあえず近しい形にはできました。
データが少ないので時間軸が三日分です。
### データ構造の違い
公式のサンプルデータではcsv形式だったけど、私が作ったfacetの例ですと、location毎に日付毎のlistを持ち、日付毎にtypeとvalueを持つlistを持ってます。
facetで並べるためには、keyとなるフィールドを指定する必要がなります。
ですので、データ上に対象項目(ここではtemp_max等)の名前をtypeとして持たせて、facetのfieldに指定する方法を取りました。

ただ、そのままのデータではネストされたオブジェクトですのでデータ変換を行います。
vega-liteではview上でネストされたリスト内のオブジェクトにはアクセスできないので、ここではtransformのflattenを使用してデータ構造を変換してます。
transform,flattenにに関してはここでは詳しく触れませんが、データ構造としては以下のように変更されます。

|location|data|_values.type|_data.date|_values.value|
|:-:|:-:|:-:|:-:|:-:|
|Seattle|・・・|precipitation|2012-01-01|0|
|Seattle|・・・|temp_max|2012-01-01|12.8|
|Seattle|・・・|temp_min|2012-01-01|5|
|・・・|・・・|・・・|・・・|・・・|
|New York|・・・|precipitation|2012-01-01|1.8|
|New York|・・・|temp_max|2012-01-01|10|
・・・|・・・|・・・|・・・|・・・|

そして以下のように軸を設定してます。
- facetのkeyに_data.type
- specのx軸に_data.date
- specのy軸に_values.value

最後に```  "resolve": {"scale": {"color": "independent"}}```を設定してますが、これは範囲を個別にする設定になります。
これが無ければ、facet上のy軸の範囲が全て同じになりますので、この例で言うと全valueの範囲になり何のデータかわかりにくくなります。
[Scale and Guide Resolution](https://vega.github.io/vega-lite/docs/resolve.html)

# ■ 複雑な入れ子のview
この記事で紹介した内容を使って、少し複雑なviewを作ってみました。
M1グランプリの歴代の得点データ[^1]を作ってみましたので[こちら](https://github.com/contrail-triple-crown/vega-lite/blob/master/data/m1_points.json)からデータを取得して色々いじってみて下さい。

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230674/4bb1152c-3286-a3d3-eb21-dbbc5ba4bb24.png)
全大会を表示すると画像が大きくなるのでfilterで3大会まで表示しています。


以下のような構成になっています。
詳細は[vega-liteのソース](https://github.com/contrail-triple-crown/vega-lite/blob/master/views/m1_judge_ponts.hjson)を御覧ください。

```json-doc:vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "",
  "data": {
    "url": "../data/m1_points.json"
  },
  "transform": [
  ],
  "facet": {
    "row": {}
  },
  "spec": {
    "hconcat": [
      {
        "layer": [
          {
            "mark": {},
            "encoding": {}
          },
          {
            "mark": {},
            "encoding": {}
          }
        ]
      },
      {
        "layer": [
          {
            "mark": {},
            "encoding": {}
          }
        ]
      },
      {
        "transform": [],
        "facet": {
          "row": {}
        },
        "spec": {
          "layer": [
            {
              "mark": {},
              "encoding": {}
            },
            {
              "mark": {},
              "encoding": {}
            }
          ]
        }
      }
    ]
  },
  "config": {},
  "resolve": {}
  }
}
```

# ■ あとがき
ちょっと思ってたよりもボリューミーな内容（データ作るのもちょっとしんどかった）になってしまいましたが、vega-liteのMulti-viewについて紹介させてもらいました。
multi-viewを用いて入れ子にすることで別々のviewを重ねたり、垂直や水平に並べる事ができます。
まだ詳しく紹介していないtransformの機能もたっぷり入ってしまって申し訳ないですが、今後また記事にしていきたいと思います。

# ■ 関連記事
[vega-liteの基本](https://qiita.com/contrail_triple_crown/items/b7cc0c3922657b17327c)
[vega-liteのParameter](https://qiita.com/contrail_triple_crown/items/45c173a4f04832f2490e)

[^1]:出典[wikipedia](https://ja.wikipedia.org/wiki/M-1%E3%82%B0%E3%83%A9%E3%83%B3%E3%83%97%E3%83%AA)