- Tech
ただのグラフで終わらせない Chart.js 活用術 — カスタム背景レイヤー+折れ線+ズーム操作を SvelteKit で作り込んだ

Chart.js のカスタム描画で、背景・アイコン・ズームを盛り込んだ気温グラフを SvelteKit で作った話。
何を作ったのか
まずは完成したものから。

作ったのは「ある1日の気温が、時間とともにどう変わるか」を表すグラフです。ただの折れ線グラフではなく、次のような要素を盛り込みました。
- 背景の色帯 … 「寒い」「快適」「暑い」などの温度ゾーンを、背景の色で塗り分けています。今の気温が”快適な範囲”にあるのか一目でわかります。
- 2本の折れ線 … 「屋内の気温」と「屋外の気温」を別々の線で描き、見比べられるようにしました。
- 天気アイコン … 各時間に、その時の天気(晴れ・曇り・雨・雪)の小さな絵を表示しています。
- マウス操作 … マウスのホイールで拡大、ドラッグで範囲を選んでズーム、線にカーソルを合わせるとその時刻の詳しい情報が出ます。
使った道具立てはとてもシンプルです。
- SvelteKit / Svelte 5(画面を作るための仕組み)
- Chart.js(グラフを描くためのライブラリ)
- chartjs-plugin-zoom(Chart.js にマウス操作を足す公式の追加部品)
ポイントは、グラフ専用の特別なライブラリを追加で入れていないこと。基本は Chart.js ひとつで、作れました。
なぜ Chart.js だけで足りるのか
「背景を塗り分けたり、アイコンを乗せたり…、ライブラリ標準でできるの?」と思うかもしれません。答えは「半分は標準機能で、残り半分はちょっとした”自作の追加部品”で」です。
やりたいことを分類すると、こうなります。
| やりたいこと | どうやって実現したか |
|---|---|
| 折れ線を描く・情報を吹き出しで出す | Chart.js の標準機能 |
| マウスで拡大・移動する | 公式の追加部品(プラグイン)を入れるだけ |
| 背景を温度ゾーンで塗り分ける | 自作の追加部品で、背景に直接お絵描き |
| 線の上に天気アイコンを乗せる | 標準機能に「画像」を渡すだけ |
この記事でいちばん伝えたいのは、表の3行目です。Chart.js には「描画のすきまに、自分で好きな絵を足し込める仕組み」が用意されています。これを使えば、ライブラリが標準で用意していない見た目も、後から自由に付け足せるのです。
以下、特に工夫した「3つのヤマ場」を順番に紹介します。
ヤマ場① 背景を温度ゾーンで塗り分ける
やりたかったこと
「気温18〜26℃なら快適、それ以下は寒い、それ以上は暑い」——この”温度の区分け”を、グラフの背景の色で表したいと思いました。こうすると、線がどのゾーンを通っているかが直感的にわかります。
ところが、これは Chart.js の標準メニューには載っていません。そこで「自作の追加部品(プラグイン)」の出番です。
どう解決したか
Chart.js は、グラフを描く途中の決まったタイミングで「ここで何かしたい人いますか?」と声をかけてくれます。今回は「線やアイコンを描く”直前”」というタイミングをつかまえて、背景に色のついた長方形を自分で描きました。線より先に描くことで、ちゃんと”背景”として下に敷かれます。
// 快適度ゾーン(寒い〜暑い)を背景に直接描く追加部品
const comfortZonePlugin = {
id: 'comfortZones',
// 「線やアイコンを描く直前」に呼ばれる
beforeDatasetsDraw(chart) {
const { ctx, chartArea, scales } = chart;
const yScale = scales.y;
for (const zone of COMFORT_ZONES) {
// 「26℃」が画面の上から何ピクセルの位置か、を計算してもらう
const yTop = yScale.getPixelForValue(zone.to);
const yBottom = yScale.getPixelForValue(zone.from);
// その範囲を、ゾーンの色で塗りつぶす
ctx.fillStyle = zone.color;
ctx.fillRect(chartArea.left, yTop, chartArea.right - chartArea.left, yBottom - yTop);
}
}
};
ここで効いているのが getPixelForValue という機能です。「26℃って、画面上だとどこ?」を Chart.js に聞くと、ピクセル位置で答えてくれます。これを使うと、気温の数字さえ指定すれば、あとは正しい高さに勝手に塗ってくれるわけです。
うれしい副作用として、後で出てくる「ズームで拡大」をしても、背景の帯は気温の位置にぴったり追従します。拡大するたびに「26℃は今どこ?」を聞き直して塗っているので、線とズレないのです。
ゾーンの定義はこんなふうに、別ファイルにまとめてあります(色や範囲を変えたいときはここだけ直せばOK)。
export const COMFORT_ZONES = [
{ label: '寒い', from: -5, to: 10, color: 'rgba(59, 130, 246, 0.18)' },
{ label: 'やや寒い', from: 10, to: 18, color: 'rgba(96, 165, 250, 0.12)' },
{ label: '快適', from: 18, to: 26, color: 'rgba(34, 197, 94, 0.16)' },
{ label: 'やや暑い', from: 26, to: 32, color: 'rgba(251, 146, 60, 0.14)' },
{ label: '暑い', from: 32, to: 40, color: 'rgba(239, 68, 68, 0.18)' }
ヤマ場② 折れ線の上に天気アイコンを乗せる
やりたかったこと
「7時は晴れ、18時は雨」——こうした天気を、その時刻の線の上に小さな絵で表示したいと思いました。

どう解決したか
Chart.js の折れ線は、各データに「点(マーカー)」を打てます。この点の形は、丸や四角だけでなく、好きな画像にもできます。そこで、天気の絵(SVG というイラスト形式)を画像として読み込み、点の代わりに使いました。
// SVGのイラストを「画像」として読み込んでおく
function loadIcon(src, maxSize) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = src; // 例: '/icons/weather/sunny.svg'
});
}
// 各時刻の天気に対応する画像を並べて、点のスタイルとして渡す
const weatherIcons = dataPoints.map((p) => icons[p.condition]);
const markers = {
data: dataPoints.map((p) => ({ x: p.x, y: p.y + 1.8 })), // 線より少し上にずらす
pointStyle: weatherIcons, // ← 丸の代わりに天気の絵を表示
showLine: false // この系列は線を描かず、絵だけ出す
};
ヤマ場③ マウスで拡大・移動
やりたかったこと
1日分のグラフだと、細かい時間帯(例:朝の数時間だけ)を詳しく見たいことがあります。そこで、マウスでグラフを拡大したり、横に動かしたりできるようにしました。
拡大したイメージ

どう解決したか
これは、ほぼ「設定を書くだけ」で実現できます。chartjs-plugin-zoom という公式の追加部品を入れ、こう設定しました。
zoom: {
// ドラッグで横方向に移動(Shiftキーを押しながら)
pan: { enabled: true, mode: 'x', modifierKey: 'shift' },
zoom: {
wheel: { enabled: true }, // マウスホイールで拡大・縮小
pinch: { enabled: true }, // スマホのピンチ操作で拡大・縮小
drag: { enabled: true }, // ドラッグした範囲にズーム
mode: 'x' // 横方向(時間軸)だけ動かす
},
// 行きすぎ防止: 0〜24時の外には出ない/2時間より狭くしすぎない
limits: { x: { min: 0, max: 24, minRange: 2 } }
}
たったこれだけで、ホイールでの拡大、ドラッグでの範囲指定ズーム、Shift+ドラッグでの横移動が全部使えるようになります。limits(制限)を付けておくと、「グラフの外までスクロールして真っ白になる」といった事故も防げます。
まとめ
グラフライブラリは「決まった形しか作れない不自由なもの」ではありません。標準機能で足りない部分は、描画の途中に自分で絵を足す——この発想ひとつで、できることが一気に広がります。
今回作った要素は、テーマを変えればいろいろな場面で使えます。
- 背景の色帯 → 「正常/注意/危険」のしきい値表示、目標ライン帯
- 線+アイコン → イベントの発生地点(リリース、障害、キャンペーン)
- マウス操作 → 期間の長いデータを、細かく見たいところだけ拡大
気になった方は、ぜひ自分のグラフにも一工夫足してみてください。
ドコドア エンジニア部
このブログでは、アプリ開発の現場で培ったフロントエンド、バックエンド、インフラ構築の知識から生成AI活用のノウハウまで、実践的な情報をアプリ開発に悩む皆様へ向けて発信しています!
【主な技術スタック】 Flutter / Firebase / Svelte / AWS / GCP / OpenAI API