Vegaでアドベントカレンダー


FUJITSU アドベントカレンダーの記事です。


## はじめに


[去年](https://qiita.com/ihgs/items/da93d7f2797a8c0d977f)は、Vega[^1] でクリスマスツリーを描きましたが、今年はVegaでアドベントカレンダーを表現してみます。
完成形は以下の様な感じです。日にちが進むとBoxが空いていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/fbc90f88-31f0-9832-dddf-a1caaa1ea94d.png)

Vega エディタで以下から動作を確認できます（２５日以降は全部空いた悲しい見た目になります）
[Online editor](https://vega.github.io/editor/#/gist/9b0226f05065365ff56897286812809e/advent.json)
[Gist](https://gist.github.com/ihgs/9b0226f05065365ff56897286812809e#file-advent-json)

[^1]: Vega はjsonでデータや描画の設定を記述することで、視覚化できるツール。[いろんなグラフ](https://vega.github.io/vega/examples/)を表現できます。

## 中身

作成方針はBoxを一つのグラフとし、25個のグラフを5x5に並べていきます。


### データ

`timeSequence` 関数を使って、12/1~12/25までの１日ごとの連続する日付データ `day` を作成します。その後 `transform/formula` で、処理しやすい様に日にちだけのでデータを作ります。


```json
  "data": [
    {
      "name": "days",
      "values":  { 
        "signal": "timeSequence('day', datetime(2021,11,1), datetime(2021,11,26))"
      },
      "transform": [
        {
          "type": "formula",
          "as": "date",
          "expr": "datum"
        },
        {
          "type": "formula",
          "as": "day",
          "expr": "date(datum)"
        }
      ]
    }
  ],
```

以下の様なデータになります。

```
[{"date": "Tue, 30 Nov 2021 15:00:00 GMT", "day":1},
{"date": "Wed, 01 Dec 2021 15:00:00 GMT", "day":2},
...
```

この一行のデータがひとつのグラフをあらわす様にします。
`layout` で、columnsで5を指定して、5列にします。
`marks` では、`day` でグルーピングすることによって、各日ごとのグラフを生成できるようにします。


```json
  "layout": {
    "padding": {"signal": "box_padding"},
    "columns": 5,
    "align": "all"
  },
  "marks": [
    {
      "type": "group",
      "from": {
        "facet": {
          "name": "facet",
          "data": "days",
          "groupby": ["day"]
        }
      },
      "marks": [...
```


### グラグ(Box)

上のグルーピングされたデータでグラフを作成していきます。
ひとつのグラフの構成要素は、

* 四角形
* 日付の数字 
* 切り取り線/切り取り後の箱
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/d10df48f-0897-abb5-fa32-bc5a6c767558.png)


#### 四角形

boxの縦横のサイズを指定するsignal `box_length` を用意しておき、
rect の {x:0,y:0, width:box_length, height: box_length}を指定することで、
縦横の長さが `box_length` の箱を描画します。

```json
      "marks": [
        {
          "type": "rect",
          "name": "box",
          "from": {
            "data": "facet"
          },
          "encode": {
            "enter": {
              "x": {
                "value": 0
              },
              "width": {
                "signal": "box_length"
              },
              "y": {
                "value": 0
              },
              "height": {
                "signal": "box_length"
              }
            }
          }
        }
  ]

```

#### 日付の数字

グラフの中心に日付の数字を描画します。

```json
{
          "type": "text",
          "from": {
            "data": "facet"
          },
          "encode": {
            "enter":{
              "text": {"field": "day"},
              "x": {"signal": "box_length*0.5"},
              "y": {"signal": "box_length*0.5"},
              "fontSize": {"signal": "box_length*0.5"},
              "align": {"value": "center"},
              "baseline": {"value": "middle"},
              "fill": {"value": "white"}
            }
          }
        }
```


####  切り取り線/切り取り後の箱

Boxの中に一回り小さい四角を作成したうえで、
現在時刻と比較し透明度(`opacity`)を変更することで、本日以前の場合は白い四角を表示することでBoxが切り取られたように見せ、以降の場合は切り取り線だけを表示するようにします。

```json
       {
          "type": "rect",
          "name": "blankbox",
          "from": {
            "data": "facet"
          },
          "encode": {
            "enter": {
              "x": {
                "value": 0, 
                "offset": {
                  "signal": "box_length*0.2"
                }
              },
              "width": {
                "signal": "box_length*0.6"
              },
              "y": {
                "value": 0,
                "offset": {
                  "signal": "box_length*0.2"
                }
              },
              "height": {
                "signal": "box_length*0.6"
              },
              "fill": {
                "value": "white"
              },
              "fillOpacity": [
                 {"test": "datum.date > now()", "value": 0},
                 {"value": 1}
              ],
              "stroke":{"value": "white"},
              "strokeWidth": {"value": 0.5},
              "strokeOpacity": [
                 {"test": "datum.date > now()", "value": 1},
                 {"value": 0}
              ],
              "strokeDash": {"value": [2,2]}
            }
          }
        }
```


以上を組み合わせることで、アドベントカレンダーの完成です。


## 参考

Vegaで普通のグラフを書く場合は以下を参照してみてください

* [Vega解説 〜離散データ〜](https://qiita.com/ihgs/items/87fda0cd39693bae44f3)
* [Vega解説 〜動的なグラフ〜](https://qiita.com/ihgs/items/7715bd7f6d2f7987f73a)
* [Vega 解説 ~時系列データを処理する~](https://qiita.com/ihgs/items/05e10b9f20faa002be18)
* [Vega解説 〜凡例〜](https://qiita.com/ihgs/items/bb222824f42969317b5d)
* [Vega解説 〜基準線をひく〜](https://qiita.com/ihgs/items/e69927315cb801cb63bc)
* [Vega解説 〜複数グラフ〜](https://qiita.com/ihgs/items/8a54c56929bf67327629)