フリーのJavaScriptライブラリ「Chart.js」で面グラフ+折れ線グラフが描けて感動した件

## TL;DR

* Chart.jsで面グラフを描きました（棒グラフや折れ線グラフも描けるよ）
* 面グラフ+折れ線グラフのような複合グラフも描けます

## はじめに

業務でとある残高の推移のグラフを内訳付きで（ここではA残高、B残高とします）既存のWebシステムに表示することになりました。
調べてみるとChart.jsにより数値の推移を簡単にグラフにできるとのことで、今回は面グラフを描いてみることにしました。

## 面グラフとは？

折れ線グラフに対し、X軸と折れ線、または他の折れ線の間の領域に色を塗ったものが**面グラフ**と呼ばれるものです。

折れ線グラフではすべてX軸基準で大きさを表すため、それぞれのデータの値を比較するのに向いています。
対して面グラフではあるデータの上に別のデータが積み上がる形で表現されるため、データ全体の量とその内訳・比率を可視化するのに向いています。

### 面グラフの例

![面グラフの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267853/2857ab8f-85c7-7f90-739f-d53e0ef652bc.png)

### 折れ線グラフの例

![折れ線グラフの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267853/8c16f254-de41-99f7-0b9b-42f2b3fa1e5e.png)

棒グラフや積み上げ棒グラフは各項目の大小を比較するのに適しているのに対し、折れ線グラフや面グラフでは時系列に沿った数値の変化を表すのに適しています。

### 棒グラフの例

![棒グラフの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267853/61250764-8191-924a-f759-c91c35a486a6.png)

### 積み上げ棒グラフの例

![積み上げ棒グラフの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267853/8dd5e600-9261-6853-456f-eb750570d4fb.png)

今回の要件として、数値の**時間的変化**と残高の**内訳**を分かりやすく可視化したかったため、面グラフを採用することにしました。

## 面グラフの描き方

早速Chart.jsを使って描いてみます！

なお、今回使用するChart.jsのバージョンは**3.9.1**になります。

### キャンバスを用意する

まずグラフの描くエリアを作る必要があります。
Chart.jsでは、`canvas`要素内に描画します。

```html
<canvas id="balance-chart"></canvas>
```

### Chart.jsの読み込み

Chart.jsを使用するので、読み込んであげましょう。

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<script>
```

### 描画

グラフはキャンバスの2次元コンテキストを使用して描画するため、`getContext('2d')`で2次元コンテキストを取得したのち、`Chart`インスタンスのコンストラクタの第一引数に渡します。
第二引数には実際に描画したいグラフの詳細情報をセットします。

```javascript
const ctx2d = document.getElementById('balance-chart').getContext('2d');
const balanceChart = new Chart(ctx2d, {
    type: 'line',
    data: {
        labels: ['2023/1', '2023/2', '2023/3', '2023/4', '2023/5', '2023/6'],
        datasets: [
            {
                label: '残高A',
                data: [50000, 75000, 100000, 125000, 140000, 150000],
                cubicInterpolationMode: 'monotone',
                borderColor: 'rgba(0, 188, 212, 1)',
                backgroundColor: 'rgba(0, 188, 212, 0.5)',
                stack: 1,
                fill: 'origin',
            },
            {
                label: '残高B',
                data: [180000, 200000, 150000, 170000, 200000, 220000],
                cubicInterpolationMode: 'monotone',
                borderColor: 'rgba(255, 193, 7, 1)',
                backgroundColor: 'rgba(255, 193, 7, 0.5)',
                stack: 1,
                fill: '-1',
            },
        ],
    },
    options: {
        responsive: true,
        scales: {
            x: {
                categoryPercentage: 0.5,
            },
            y: {
                type: 'linear',
            	min: 0,
                stacked: true,
            },
            
        },
        plugins: {
            filler: {
                propagate: true,
            },
            tooltip: {
                callbacks: {
                    label: (context) => {
                        let label = context.dataset.label || '';
                        if (label) {
                            label += ': ';
                        }
                        if (context.parsed.y !== null) {
                            label += context.parsed.y.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '円';
                        }
                        return label;
                    },
                },
            },
        },
    },
});
```

これを画面で確認すると、上記の「面グラフ」の画像のようなグラフが描画されます。

#### プロパティについて

ポイントとなるプロパティについて説明します。
（見てすぐ分かる部分や今回重要でない部分は省略します）

##### `type`

`'line'`をセットすることで折れ線（面グラフの場合は領域の境界にあたる部分）が描画されます。

##### `data.datasets[index].fill`

ここで領域の塗りつぶし方を設定できます（データセットは0から数えます）。

- `'origin'`: X軸まで塗りつぶします（`true`でも可）
- `1`: データセット1まで塗りつぶします（絶対インデックス）
- `'-1'`: 1つ前のデータセットまで塗りつぶします（相対インデックス）

##### `data.datasets[index].stack`

積み上げグラフを表示する際の積み上げグループ名を指定します。

##### `data.datasets[index].borderColor`

折れ線の色を指定します。

##### `data.datasets[index].backgroundColor`

領域の色を指定します。

##### `options.scales.y.stacked`

データセットを積み上げるか否か。`true`をセットすると積み上げグラフになります。

##### `options.plugins.filter.propagate`

`true`をセットした場合、対象データセットが非表示になった時に塗りつぶし効果を伝播させます（デフォルトは`true`）。
基本的には`true`のままにしたほうが表示が自然です。

※説明が分かりにくい場合は、こちらのサイト(https://tr.you84815.space/chartjs/charts/area.html )で動作を確認できます。
※データセットの表示/非表示は凡例の項目をクリックすることで切り替えられます。

## 面グラフ+折れ線グラフを描いてみる

追加要件として、**残高の予想額**と実際の額を比較できるようにしてほしいと言われました。
予想額の面グラフをA残高+B残高に重ねて表示するのは見にくいため、予想額だけ折れ線グラフにして表示したいです。
そんなこともChart.jsでできるのでしょうか？

**実は、できます。**

試しに以下のデータセットを`data.datasets`の先頭に追加してみましょう。
（`fill: false`であること、`stack`プロパティを指定していないところがポイントです）

```javascript
            {
                label: '予想額',
                data: [250000, 260000, 240000, 220000, 270000, 300000],
                cubicInterpolationMode: 'monotone',
                borderColor: 'rgba(233, 30, 99, 1)',
                backgroundColor: 'rgba(233, 30, 99, 0)',  // なくてもいい
                fill: false,
            },
```

すると以下のようなグラフが描けました！！

![面グラフ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267853/8397be69-5885-b486-9d38-d6e046a522ee.png)

今回は面グラフ+折れ線グラフでしたが、面グラフ+棒グラフの組み合わせも作成することができます。
複合グラフも描画できるのは便利ですね！

## おわりに

ブラウザ上に様々なグラフを描けるライブラリが無料で使えるのはありがたいと思いました！
データベースに保存された表をそのまま表示してもいいですが、目が疲れると思うので、こうしたグラフライブラリで分かりやすく可視化してみるのはいかがでしょうか？

## 参考

- [Area Chart | Chart.js](https://www.chartjs.org/docs/3.9.1/charts/area.html)
- [Chart.jsでグラフを描画してみた ２](https://qiita.com/Haruka-Ogawa/items/392e26123f7e9618a47b)