SVGとCanvasの特性の違いをチャートの実装を使って比較する

# はじめに

チャートを使ったアプリを作りたいなと思っていて、自分が作りたいチャートを実装するにあたってSVGとCanvasのどちらがベターなのかを知るために、TypeScriptを使って、ほぼ同じロジックでローソク足のチャートを表示するツールを実装しました。

[DEMO](https://takeshy.github.io/svg_canvas_chart/dist/)
[GitHub](https://github.com/takeshy/svg_canvas_chart)

# ツールについて
<img width="414" alt="Image-1.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/10645/e862a13b-7722-cb6e-cd38-18f91a77bc0c.jpeg">


Demoを見ていただければわかる通り、チャートの高さ、幅、表示データ数、スケール(Canvasのみで使用)が設定できるようになっています。
スケールとは、iPhoneのRetinaのような高精細ディスプレイの場合、Canvasをそのままで表示すると表示がぼやけてしまうため、devicePixelRatioで取得した値と同じだけCanvasを拡大させ、表示時に縮小させることでぼやけないようにするための設定です。100x100の場合、スケールが2だと200x200になるため4倍の広さになります。そのためスケールを大きくすると表示が遅くなります。
チャート表示が完了すると、チャートの表示にかかった時間が表示されます。
チャート上でdragすることによりスクロールができるので表示のなめらかさを確認できます。
pinch in pinch outにより、チャート内における表示データ数を手軽に変更できます。
ダミーデータとして米ドル/円の1分足を１日分用意しました。前の終値と次の初値が一致していないことが多いですが、拾ったデータがそうなってました。

# パフォーマンス

## MacBook Air Mid 2012(devicePixelRatio 1)

### 1440x820
|quantity|svg|canvas|
|--:|--:|-----:|
|96 |10~20ms|1~10ms|
|300|25~40ms|1~10ms|

### 1900x1200
|quantity|svg|canvas|
|--:|--:|-----:|
|96 |10~20ms|1~10ms|
|300|25~40ms|2~10ms|

### 4096x2160 
|quantity|svg|canvas|
|--:|--:|-----:|
|96 |10~20ms|2~10ms|
|300|25~40ms|4~10ms|

## MacBook Pro (devicePixelRatio 2)

### 1440x800
|quantity|svg|canvas|
|--:|--:|-----:|
|96 |5~8ms|1~2ms|
|500|20~40ms|1~3ms|

### 3610x2280
|quantity|svg|canvas|
|--:|--:|-----:|
|96 |5~8ms|2~6ms|
|500|20~40ms|2~18ms|

### 3648x2304
|quantity|svg|canvas|
|--:|--:|-----:|
|96 |5~8ms|120~220ms|
|500|20~40ms|120~220ms|

## iPhone 6S Plus(devicePixelRatio 3)
### 414x622 
|quantity|svg|canvas|
|--:|--:|-----:|
|27 |4~10ms|4~6ms|
|120|10~15ms|5~20ms|

### 800x1000
|quantity|svg|canvas|
|--:|--:|-----:|
|27 |4~10ms|8~30ms|
|120|10~15ms|10~30ms|

### 1900x1200
|quantity|svg|canvas|
|--:|--:|-----:|
|27 |4~10ms|N/A|
|120|10~15ms|N/A|

よく言われているようなCanvasはオブジェクト数に強く、SVGは画面の広さに強いという結果になりました。
Dragで動的にチャートをスクロールさせても、canvasの一部サイズを除いてはどれも快適で問題がないレベル。
scaleの影響のため、canvasがMacbookProだと3640x2300を越えると急に何十倍も遅くなり、iPhoneだと1900x1200が表示できませんでした。
ただこのサイズの画面がdevicePixelRatioが1以上になるとは考えにくいので、気にしなくていいと思います。
devicePixelRatioが2のMacbook Proに外部Displayにつなげると、外部Display上ではdevicePixelRatioが1になるので、4Kのモニターにつないだとしてもcanvasでも影響があるレベルではないと考えてよさそう。
PCの場合はCanvasの方が早いケースが多いですが、iPhoneの場合はほぼ遜色がないです。

# その他違い

- SVG
 - 表示結果がnodeとして残るため、テストやDebugができる
 - 仮想DOMを使ったり、Data bindingができる
 - 個々の図形に対してEventHandlingできる
 - CSSが使える
 - devicePixelRatioを意識しなくていい。

- Canvas
 - PNGやJPEG画像に変換できる(ただしSVGもCanvasにとりこめば同じことができる)
 - オブジェクトを細かくしても色情報が失われない
 - スクロールのような画面全体に対するイベントハンドリングが簡単。

今回チャート上でdragするとスクロールするようにしましたが、SVGの場合にその処理をするためにはちょっと変わった対応が必要でした。
canvasは再描画する際には同じcanvas上で再度renderするだけなので、Canvasもしくはその親のNodeに対するEventをhandlingするだけですみますが、SVGの場合は再描画のためには配下のNodeをすべて消す必要があり、サイズを固定したSVGの親に対してEventハンドリングをしていても、SVG内の削除生成がはげしすぎるためか、Eventがなかなか拾えない状態になってしまいました。
そのためその対応として、position:absoluteかつz-indexを2以上でbackground-color:transparentのチャートと同じサイズのDIVを作成することで、チャート上に透明なDIVを被せて、そのDIVに対するDragのEventを取得することで、チャート上のDragに対応しました。

# 個人的感想

SVGのほうが利点がいろいろあることには気づいていましたが、どうしてもチャート上でスクロールをさせたい希望があり、そのためには下記懸念があり、当初はcanvasで進めていました。

- nodeが生成/消去が激しく行われる中、チャート上でDragイベントを拾えるか？
- チャート上でのDragに対して描画が追いつくか?

ただ、SVGでも実はできるかもと思って実装してみることになったため、両方を比較できるようになりました。
そして、SVGでも表示はスムーズでチャートをスクロールしながらの表示も全然問題ないことが判明しました。
今後は仮想DOMを使うことも考慮して、SVGを使ったチャートの実装を進めていこうと思います。