Vega でタイマーを作る


Vega[^1] の timer イベントを使って視覚的に残り時間がわかるタイマーを作ります。


まずは、完成系のイメージです。色分けされたドーナツグラフが時間と共に欠けていきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/3d4a346b-29cc-a1c7-7ced-41b5e39e52fc.png" width="200px">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/88893ddd-5b81-74d0-c679-b32d68a1f857.png" width="200px">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/89c0df1b-fca5-9872-7d50-50e113b33e9b.png" width="200px">



### ステップ1 ドーナツグラフを表示する

今回のサンプルではタイマーの時間を30分とし、15分、25分の区切りで色を変えるようにします。

`pie` transform を利用し、360度（0~2π）を 15対10対5の割合になる角度へと変換します。

```json
    "data": [
      {
        "name": "table",
        "values": [
          {"id": 1, "value": 15},
          {"id": 2, "value": 10},
          {"id": 3, "value": 5}
        ],
        "transform": [  
          {
            "type": "pie",
            "field": "value",
            "startAngle": 0,
            "endAngle": 6.29,
            "sort": false
          }
        ]
      }
    ],
```

先ほど変換した角度が入ったデータを使ってドーナツグラフを描画します。

```json
    "marks": [
      {
        "type": "arc",
        "from": {"data": "table"},
        "encode": {
          "enter": {
            "fill": {"scale": "color", "field": "id"},
            "x": {"signal": "width / 2"},
            "y": {"signal": "height / 2"}
          },
          "update": {
            "startAngle": {"field": "startAngle"},
            "endAngle": {"field": "endAngle"},
            "padAngle": {"value": 0},
            "innerRadius": {"signal": "innerRadius"},
            "outerRadius": {"signal": "width / 2 "},
            "cornerRadius": {"value": 0}
          }
        }
      },
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/65422584-eca2-ab9a-a059-76ed922bfa53.png" width="400px">

[Onlineエディタで開く](https://vega.github.io/editor/#/gist/5f50af3d20c1f452ec647d62054b7f5c/step1.json)

### ステップ2 timerイベントを追加する

以下のsignal を追加します

* minutes: 計測するタイマーを指定します
  * `{"name": "minutes", "init": "30"}`
* startDate: 開いた時の時刻記録しておきます
  * `{"name": "startDate", "init": "now()"}`
* endDate: 終了時刻を記録しておきます
  * `{"name": "endDate", "init": "now()+minutes*60*1000"}`
* timer : 1秒ごとにイベントを発生させ、`経過時間/タイマー時間 ×　2π` することで、どこまで進んだかの角度を計算します
    ```json
          {
            "name": "timer",
            "init": "0",
            "on": [
              {
                "events": {"type": "timer", "throttle": 1000},
                "update": "(now()- startDate)/(endDate-startDate)*6.29"
              }
            ]
          }
    ```

OnlineエディタのSignal Viewer でtimerが進んでいくのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/bd1536c5-dcba-174d-e1f0-b40b6e57839e.png)


[差分](https://ihgs.github.io/online-diff/?diff=ihgs/5f50af3d20c1f452ec647d62054b7f5c/raw/step1.json...ihgs/5f50af3d20c1f452ec647d62054b7f5c/raw/step2.json)
[Online Editor で開く](https://vega.github.io/editor/#/gist/5f50af3d20c1f452ec647d62054b7f5c/step2.json)



### ステップ3 ドーナツグラフを消していく

元のドーナツグラフに対して、白いドーナツグラフを重ねることでドーナツグラフを消していきます。

白いドーナツグラフを追加し、`endAngle` を先ほどの `timer` シグナルを設定することで、時間の経過と共に白いドーナツグラフが大きくなり、元のドーナツグラフが欠けていきます


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/af777ab8-772f-e858-19a3-4af7053b54a8.png" width="400px">

[差分](https://ihgs.github.io/online-diff/?diff=ihgs/5f50af3d20c1f452ec647d62054b7f5c/raw/step2.json...ihgs/5f50af3d20c1f452ec647d62054b7f5c/raw/step3.json)
[Online Editor で開く](https://vega.github.io/editor/#/gist/5f50af3d20c1f452ec647d62054b7f5c/step3.json)


### ステップ4 文字列を表示する

最後に色の変わり目に残り時間を表示させます。

残り時間を計算するために、`window` transformを利用し積み上げデータを計算します。

```json
          {
              "type": "window",
              "ops": ["sum"],
              "fields": ["value"],
              "as": ["sum"]
          }
```

以下のようにデータ変換されます。

```
{"id": 1, "value": 15}, ==> {"id": 1, "value": 15, "sum": 15},
{"id": 2, "value": 10}, ==> {"id": 2, "value": 10, "sum": 25},
{"id": 3, "value": 5}   ==> {"id": 3, "value": 5, "sum": 30}
```

経過時間から残り時間を計算し出力します。
ついでに、円と文字が被らないように半径などの微調整します。また、時間が経過すると文字を小さくしたりします。

```json
      {
        "type": "text",
        "from": {"data": "table"},
        "encode": {
          "enter": {
            "x": {"signal": "width / 2"},
            "y": {"signal": "height / 2"},
            "radius": {"signal": "width / 2 - fontSize/2"},
            "theta": {"signal": "datum.endAngle"},
            "fill": {"value": "#000"},
            "align": {"value": "center"},
            "baseline": {"value": "middle"},
            "text": {"signal": "(minutes - datum.sum) == 0 ? minutes : (minutes - datum.sum)"}
            
            
          },
          "update": {
            "fontSize": {"signal": "timer < datum.endAngle ? fontSize: fontSize/2"}
          }
        }
      }
```



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51316/88893ddd-5b81-74d0-c679-b32d68a1f857.png" width="400px">

[差分](https://ihgs.github.io/online-diff/?diff=ihgs/5f50af3d20c1f452ec647d62054b7f5c/raw/step3.json...ihgs/5f50af3d20c1f452ec647d62054b7f5c/raw/finish.json)
[Online Editor で開く](https://vega.github.io/editor/#/gist/5f50af3d20c1f452ec647d62054b7f5c/finish.json)


### 参考

過去の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)
* [Vega解説 ~全体グラフと詳細グラフ~](https://qiita.com/ihgs/items/73a2cc6b1834d41043e7)
* [Vega解説 〜Reactアプリ上でVegaグラフを表示し、グラフのイベントを捕まえる〜](https://qiita.com/ihgs/items/ed7ac7309696184008b4)

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