Chart.js を使って kintone でダッシュボードを作ってみよう!

著者名: 武井 琢治 (External link)

目次

caution
警告

レコード一括取得時に 1 万を超えるレコードを取得する可能性がある場合は、運用・適用中のプログラムのご確認並びに修正対応の検討をお願いします。
詳細は offset の制限値を考慮した kintone のレコード一括取得について を確認ください。

はじめに

皆さん、グラフは使っていますか?

今回は、kintone 標準機能では作れないグラフを、
Cybozu CDN の Chart.js というライブラリを使用して実現します。

今回、Chart.js ライブラリで拡張するグラフは以下の 3 つです。

  • 前年同月比グラフ → 製品の月次売上高と前年同月の売上高を並べた棒グラフを作成します。
  • 棒グラフ&折れ線グラフをひとつにまとめたグラフ → 月次売上高と試用申込数の推移が分かるグラフを作成します。
  • ドーナツ型グラフ → 製品別の売上高とその割合が分かるグラフを作成します。

また、これらのグラフをカスタマイズビューに表示することで、ダッシュボードを作成してみます。

完成イメージ

下準備

アプリの準備

ダッシュボードを表示するためのアプリを作成します。
今回紹介する作成例で必要なアプリは以下の 4 アプリになります。

  • ダッシュボードアプリ
  • 製品マスターアプリ
  • 販売実績アプリ
  • 試用申し込み管理アプリ
ダッシュボードアプリ

アプリの作成を始めたところになります。
「ダッシュボード」アプリは表示するだけのアプリですので、フォームは何も作らなくてかまいません。

一覧の作成画面です。
一覧名は「ダッシュボード」に設定します。
レコード一覧の表示形式を「カスタマイズ」にし、以下の HTML を入力します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<p style="margin-left:50px; display:inline-block">■指定日の月までの1年間の実績グラフを表示します</p>
<div id="datepicker" style="margin-left:50px;"></div>

<p id="p1" style="margin-left:50px; display:inline-block">■売上高(前年同月比)</p>
<p id="p2" style="margin-left:50px; display:inline-block; margin-left:35%;">■売上高・試用申込数</p>
<br />

<div id="canvas1Div" style="margin-left:50px; display:inline-block">
  <canvas id="canvas1"></canvas>
</div>
<div id="canvas2Div" style="margin-left:50px; display:inline-block">
  <canvas id="canvas2"></canvas>
</div>
<br /><br />

<p id="p3" style="margin-left:50px;">■製品別売上高</p>
<div id="canvas3Div" style="margin-left:50px">
  <canvas id="canvas3"></canvas>
</div>

また、以下のとおり「アプリの設定 > JavaScript / CSS でカスタマイズ」に以下のサンプルコードを設定します。
サンプルコードはエディタにコピーして使用してください。
ファイル名はそれぞれ「dashboard.js」、「dashboard.css」とし、文字コードは「UTF-8」で保存します。

PC 用の JavaScript ファイル

ファイルの設定順は以下のとおりにしてください。

  • https://js.cybozu.com/jquery/3.6.1/jquery.min.js
  • https://unpkg.com/kintone-ui-component@1.7.0/umd/kuc.min.js
  • https://js.cybozu.com/kintone-rest-api-client/3.3.2/KintoneRestAPIClient.min.js
  • https://js.cybozu.com/chartjs/v2.7.3/Chart.min.js
  • https://js.cybozu.com/luxon/3.0.4/luxon.min.js
  • dashboard.js(詳細後述)
PC 用の CSS ファイル

設定すると次のような並びになります。

ここまで設定が済んだら、右上の「アプリを公開」ボタンを押して設定完了です。

製品マスターアプリ

次の設定でアプリを作成してください。

フィールドコード フィールドタイプ 備考
製品名 文字列(1 行) 重複禁止
単価 数値

また、マスターレコードを作成します。
今回は以下のようにしました。

販売実績アプリ

次の設定でアプリを作成してください。

フィールドコード フィールドタイプ 備考
会社名 文字列(1 行)
日付 日付
製品名 ルックアップ 製品マスターアプリの製品名フィールドをコピー元のフィールドに指定します。
製品マスターアプリの単価フィールドを単価フィールドにコピーします。
単価 数値
販売数 数値
販売額 計算 計算式:単価*販売数

また、以下のように、レコードを作成します。

試用申し込み管理アプリ

次の設定でアプリを作成してください。

フィールドコード フィールドタイプ 備考
会社名 文字列(1 行)
日付 日付
製品名 ルックアップ 製品マスターアプリの製品名フィールドをコピー元のフィールドに指定します。

また、以下のように、レコードを作成します。

サンプルコード

dashboard.js

{販売実績アプリ ID}{試用申し込み管理アプリ ID} はご自身の環境に合わせて書き換えてください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/*
 * kintone dashboard sample program
 * Copyright (c) 2016 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */

/* eslint-disable max-statements */
/* global Kucs */
/* global luxon */

(() => {
  'use strict';

  const Kuc = Kucs['1.7.0'];
  // 販売実績アプリID
  const SALES_APPID = {販売実績アプリID};
  // 試用申し込み管理アプリID
  const TRIAL_APPID = {試用申し込み管理アプリID};

  let oneTimeFlg = true;
  let chart1, chart2, chart3;

  // luxonで使用する日付フォーマット
  const dateFormat = 'yyyy-MM-dd';
  const dateFormatM1 = 'M月';
  const dateFormatM2 = 'M';
  const dateFormatY = 'yyyy';
  const endOfFormatM = 'month';

  // ローディング画面を出す関数
  const setLoading = () => {
    const $body = $('body');
    $body.css('width', '100%');
    // eslint-disable-next-line max-len
    const $loading = $('<div>').attr('id', 'loading').attr('class', 'loading').attr('style', 'width: 100%; height: 100%; position:absolute; top:0; left:0; text-align:center; background-color:#666666; opacity:0.6; z-index: 9;');
    const $div = $('<div>').attr('id', 'imgBox').attr('style', 'width: 100%; height: 100%;');
    // eslint-disable-next-line max-len
    const $img = $('<img>').attr('src', 'data:image/gif;base64,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');
    $loading.append($div.append($img));
    $body.append($loading);
    $('#imgBox').attr('style', 'margin-top: ' + Math.floor($('#loading').height() / 2) + 'px;');
    $body.css('position', 'fixed');
  };

  // ローディング画面を消す関数
  const removeLoading = () => {
    const $loading = $('.loading');
    $loading.remove();

    const $body = $('body');
    $body.css('position', '');
  };

  // dataオブジェクトの中身を消す関数
  const refreshData = (data) => {
    for (const key in data) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        data[key] = 0;
      }
    }
    return data;
  };

  // レコード取得用クエリ作成
  const strQuery = (dateS, dateE) => `日付 <= "${dateE}" and 日付 >= "${dateS}"`;

  // 全レコード取得関数
  const fetchRecords = async (appId, query, orderField) => {
    const client = new KintoneRestAPIClient();
    const params = {
      app: appId,
      condition: query,
      orderBy: orderField
    };

    return await client.record.getAllRecordsWithOffset(params);
  };

  // DatePicker設置
  const createDatePicker = () => {
    const datePickerMax = new Kuc.DatePicker({
      value: luxon.DateTime.local().toFormat(dateFormat),
      id: 'datePickerMax'
    });

    $('#datepicker').append(datePickerMax);
    datePickerMax.addEventListener('change', event => {
      createCompareGraph(datePickerMax.value);
    });

    oneTimeFlg = false;
  };

  // グラフを描画する
  const createCompareGraph = async (max) => {
    setLoading();

    const maxDateTime = max ? luxon.DateTime.fromFormat(max, dateFormat) : luxon.DateTime.local();
    const minDateTime = maxDateTime.minus({months: 11});

    // 'yyyy-MM-dd'フォーマットの文字列
    const maxMonth = maxDateTime.endOf(endOfFormatM).toFormat(dateFormat);
    const maxMonth2 = maxDateTime.minus({months: 12}).endOf(endOfFormatM).toFormat(dateFormat);
    const minMonth = maxDateTime.minus({months: 11}).startOf(endOfFormatM).toFormat(dateFormat);
    const minMonth2 = maxDateTime.minus({months: 23}).startOf(endOfFormatM).toFormat(dateFormat);

    // 11ヶ月前,10ヶ月前,....,1ヶ月前,今月 の順に、12ヶ月分が 'M月'フォーマットで格納されている配列
    const nMonthsBefore = Array(12).fill().map((e, i)=>maxDateTime.minus({months: i}).toFormat(dateFormatM1)).reverse();

    // 日付昇順にするので
    const orderField = '日付';
    // 指定日~1年前までの売上レコード取得
    const canvas1Rec = await fetchRecords(SALES_APPID, strQuery(minMonth, maxMonth), orderField);
    // 1年前~2年前までの売上レコード取得
    const canvas1Rec2 = await fetchRecords(SALES_APPID, strQuery(minMonth2, maxMonth2), orderField);
    // 指定日~1年前までの試用申込数レコード取得
    const canvas2Rec = await fetchRecords(TRIAL_APPID, strQuery(minMonth, maxMonth), orderField);

    // グラフデータ作成用にテンポラリとして使うdataオブジェクト
    let data = [];
    nMonthsBefore.forEach(el=>{
      data[el] = 0;
    });
    // 今月~1年前までの売上グラフデータ作成
    canvas1Rec.forEach(e=>{
      data[luxon.DateTime.fromFormat(e.日付.value, dateFormat).toFormat(dateFormatM1)] += Number(e.販売額.value);
    });
    // 今月~1年前までの売上グラフデータ
    const eventuallyThisYearData = nMonthsBefore.map((e)=>data[e]);

    // dataを初期化
    data = refreshData(data);
    // 1年前~2年前までの売上グラフのデータ作成
    canvas1Rec2.forEach(e=>{
      data[luxon.DateTime.fromFormat(e.日付.value, dateFormat).toFormat(dateFormatM1)] += Number(e.販売額.value);
    });
    // 1年前~2年前までの売上グラフのデータ
    const eventuallyLastYearData = nMonthsBefore.map((e)=>data[e]);

    // dataを初期化
    data = refreshData(data);
    // 指定日~1年前までの試用申込数グラフのデータ作成
    canvas2Rec.forEach(e=>{
      data[luxon.DateTime.fromFormat(e.日付.value, dateFormat).toFormat(dateFormatM1)] += 1;
    });
    // 指定日~1年前までの試用申込数グラフデータ
    const eventuallyTrialData = nMonthsBefore.map(e=>data[e]);

    // ドーナツグラフのデータ作成
    const prodData = [];
    const prodLabels = [];
    canvas1Rec.forEach(e=>{
      const prod = e.製品名.value;
      if (typeof (prodData[prod]) === 'undefined') {
        prodData[prod] = 0;
        prodLabels.push(prod);
      }
      prodData[prod] += Number(e.販売額.value);
    });

    // 製品名ラベル表示順を売上データ降順にソート
    prodLabels.sort((a, b) => prodData[b] - prodData[a]);

    // 製品ごとの背景色
    const prodBGColorMST = {
      製品A: 'rgba(54, 162, 235, 0.4)',
      製品B: 'rgba(255, 99, 132, 0.4)',
      製品C: 'rgba(0, 100, 0, 0.4)',
      製品D: 'rgba(255, 60, 255, 0.4)',
      製品E: 'rgba(255, 255, 0, 0.4)',
      製品F: 'rgba(255, 112, 0, 0.4)',
      製品G: 'rgba(60, 60, 120, 0.4)',
      製品H: 'rgba(255, 0, 0, 0.4)',
      製品I: 'rgba(0, 255, 255, 0.4)'
    };

    /*
    // 【参考】製品縁取り色
    const prodBDColorMST = {
      製品A:"rgba(54, 162, 235, 1)",
      製品B:"rgba(255,99,132,1)",
      製品C:"rgba(0, 170, 0, 1)",
      製品D:"rgba(255, 170, 255, 1)",
      製品E:"rgba(255, 255, 0, 1)",
      製品F:"rgba(255, 112, 0, 1)",
      製品G:"rgba(100, 0, 0, 1)",
      製品H:"rgba(255, 184, 184, 1)",
      製品I:"rgba(0, 255, 255, 1)"
    };
    */

    // ドーナツグラフのデータ
    const eventuallyByProductData = prodLabels.map(e=>prodData[e]);
    const sumOfeventuallyByProductData = eventuallyByProductData.reduce((a, c)=>a + c, 0);
    const prodBGColor = prodLabels.map(e=>prodBGColorMST[e]);
    // const prodBDColor = prodLabels.map(e=>prodBDColorMST[e]);

    // 前年同月比グラフを描画(1つ目のグラフ)
    const ctx = document.getElementById('canvas1');
    if (chart1) {
      chart1.destroy();
    }
    chart1 = new Chart(ctx, {
      type: 'bar',
      data: {
        labels: nMonthsBefore,
        datasets: [
          {
            label: '前年同月売上',
            data: eventuallyLastYearData,
            backgroundColor: 'rgba(54, 162, 235, 0.2)',
            borderColor: 'rgba(54, 162, 235, 1)',
            borderWidth: 1,
            test: 'aiueo'
          },
          {
            label: minDateTime.toFormat('yyyy年M月~') + maxDateTime.toFormat('yyyy年M月売上'),
            data: eventuallyThisYearData,
            backgroundColor: 'rgba(255, 99, 132, 0.2)',
            borderColor: 'rgba(255,99,132,1)',
            borderWidth: 1,
            test: 'ABCDE'
          }
        ]
      },
      options: {
        scales: {
          yAxes: [{
            display: true,
            scaleLabel: {
              display: true,
              labelString: '売上',
              fontFamily: 'monospace'
            },
            ticks: {
              beginAtZero: true,
              maxTicksLimit: 8,
              callback: (value) => value + '万円'
            }
          }]
        },
        tooltips: {
          enabled: true,
          mode: 'single',
          callbacks: {
            title: (tooltipItems, titleData) => {
              const tmp = tooltipItems[0].xLabel.split('月');
              // 1) tooltipItems[0].datasetIndex  0:指定日(maxDateTime)の前年、1:指定日(maxDateTime)の年
              // 2) tooltipItems[0].xLabelの月がmaxDateTimeより大きかったら-1年にする
              let retYear = maxDateTime.minus({years: 1}).toFormat(dateFormatY);
              if (Number(tmp[0]) > Number(maxDateTime.toFormat(dateFormatM2))) {
                if (tooltipItems[0].datasetIndex === 0) {
                  retYear = maxDateTime.minus({years: 2}).toFormat(dateFormatY);
                }
              } else if (tooltipItems[0].datasetIndex === 1) {
                retYear = maxDateTime.toFormat(dateFormatY);
              }
              return `${retYear}${tooltipItems[0].xLabel}`;
            },
            label: (tooltipItems, data3) => tooltipItems.yLabel + '万円'
          }
        }
      }
    });

    // 売上と試用申込数グラフを描画(2つ目のグラフ)
    const ctx2 = document.getElementById('canvas2');
    if (chart2) {
      chart2.destroy();
    }
    chart2 = new Chart(ctx2, {
      type: 'bar',
      data: {
        labels: nMonthsBefore,
        datasets: [
          {
            yAxisID: 'y-axis-1',
            type: 'line',
            label: '試用申込数',
            data: eventuallyTrialData,
            backgroundColor: 'rgba(54, 162, 235, 0.2)',
            borderColor: 'rgba(54, 162, 235, 1)',
            borderWidth: 1
          },
          {
            yAxisID: 'y-axis-0',
            type: 'bar',
            label: minDateTime.toFormat('yyyy年M月~') + maxDateTime.toFormat('yyyy年M月売上'),
            data: eventuallyThisYearData,
            backgroundColor: 'rgba(255, 99, 132, 0.2)',
            borderColor: 'rgba(255,99,132,1)',
            borderWidth: 1
          }
        ]
      },
      options: {
        scales: {
          yAxes: [{
            position: 'left',
            id: 'y-axis-0',
            display: true,
            scaleLabel: {
              display: true,
              labelString: '売上',
              fontFamily: 'monospace'
            },
            ticks: {
              beginAtZero: true,
              maxTicksLimit: 8,
              callback: (value) => value + '万円'
            }
          },
          {
            position: 'right',
            id: 'y-axis-1',
            display: true,
            scaleLabel: {
              display: true,
              labelString: '試用申込数',
              fontFamily: 'monospace'
            },
            ticks: {
              beginAtZero: true,
              maxTicksLimit: 6
            }
          }]
        },
        tooltips: {
          enabled: true,
          mode: 'single',
          callbacks: {
            title: (tooltipItems, data4) => {
              const tmp = tooltipItems[0].xLabel.split('月');
              // tooltipItems[0].xLabelの月がmaxDateTimeより大きかったら-1年する
              // eslint-disable-next-line max-len
              const retYear = Number(tmp[0]) > Number(maxDateTime.toFormat(dateFormatM2)) ? maxDateTime.minus({years: 1}).toFormat(dateFormatY) : maxDateTime.toFormat(dateFormatY);
              return `${retYear}${tooltipItems[0].xLabel}`;
            },
            label: (tooltipItems, data5) => {
              if (tooltipItems.datasetIndex === 0) {
                return `試用申込数:${tooltipItems.yLabel}`;
              }
              return `売上:${tooltipItems.yLabel}万円`;
            }
          }
        }
      }
    });

    // ドーナツグラフを描画
    const ctx3 = document.getElementById('canvas3');
    if (chart3) {
      chart3.destroy();
    }
    chart3 = new Chart(ctx3, {
      type: 'doughnut',
      data: {
        labels: prodLabels,
        datasets: [
          {
            data: eventuallyByProductData,
            backgroundColor: prodBGColor,
            // borderColor: prodBDColor,
            borderWidth: 1
          }
        ]
      },
      options: {
        showAllTooltips: true,
        animation: {
          animateScale: true
        },
        tooltips: {
          callbacks: {
            title: (tooltipItems, data6) => {
              return data6.labels[tooltipItems[0].index];
            },
            label: (tooltipItems, data7) => {
              const per = Math.round((data7.datasets[0].data[tooltipItems.index] / sumOfeventuallyByProductData * 100));
              return `${per}% ${data7.datasets[0].data[tooltipItems.index]}万円`;
            }
          }
        }

      }
    });
    // グラフのデフォルトフォントサイズ設定
    Chart.defaults.global.defaultFontSize = 10;

    // datePickerの作成は画面読み込み時のみ
    if (oneTimeFlg) {
      createDatePicker();
    }
    removeLoading();
  };

  /*
  // 【参考】ドーナツグラフの各製品色をランダムに決定する関数
  const dynamicColors = () => {
    const r = Math.floor(Math.random() * 255);
    const g = Math.floor(Math.random() * 255);
    const b = Math.floor(Math.random() * 255);
    return `rgb(${r},${g},${b})`;
  }
  // 【参考】ドーナツグラフの各項目を常に表示
  Chart.pluginService.register({
    beforeRender: (chart) => {
      if (chart.config.options.showAllTooltips) {
        chart.pluginTooltips = [];
        chart.config.data.datasets.forEach((dataset, i) => {
          chart.getDatasetMeta(i).data.forEach((sector, j) => {
            chart.pluginTooltips.push(new Chart.Tooltip({
              _chart: chart.chart,
              _chartInstance: chart,
              _data: chart.data,
              _options: chart.options.tooltips,
              _active: [sector]
            }, chart));
          });
        });
        chart.options.tooltips.enabled = false;
      }
    },
    afterDraw: (chart, easing) => {
      if (chart.config.options.showAllTooltips) {
        if (!chart.allTooltipsOnce) {
          if (easing !== 1) return;
          chart.allTooltipsOnce = true;
        }
        chart.options.tooltips.enabled = true;
        Chart.helpers.each(chart.pluginTooltips, (tooltip) => {
          tooltip.initialize();
          tooltip.update();
          tooltip.pivot();
          tooltip.transition(easing).draw();
        });
        chart.options.tooltips.enabled = false;
      }
    }
  });
  */

  kintone.events.on('app.record.index.show', (event) => {
    if (event.viewName === 'ダッシュボード') {
      createCompareGraph();
    }
    return event;
  });
})();

レコード一括取得時に 1 万を超えるレコードを取得する可能性がある場合は、運用・適用中のプログラムのご確認並びに修正対応の検討をお願いします。
詳細は offset の制限値を考慮した kintone のレコード一括取得について を確認ください。

dashboard.css

1
2
3
4
#canvas1Div,#canvas2Div,#canvas3Div{
    width: 43%;
    height: 40%;
}

サンプルコードの解説

ここからは、Chart.js 特有のコードを中心に解説していきます。
上から見ていきましょう。

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// 製品ごとの背景色
const prodBGColorMST = {
  製品A: 'rgba(54, 162, 235, 0.4)',
  製品B: 'rgba(255, 99, 132, 0.4)',
  製品C: 'rgba(0, 100, 0, 0.4)',
  製品D: 'rgba(255, 60, 255, 0.4)',
  製品E: 'rgba(255, 255, 0, 0.4)',
  製品F: 'rgba(255, 112, 0, 0.4)',
  製品G: 'rgba(60, 60, 120, 0.4)',
  製品H: 'rgba(255, 0, 0, 0.4)',
  製品I: 'rgba(0, 255, 255, 0.4)'
};

/*
// 【参考】製品縁取り色
const prodBDColorMST = {
  製品A:"rgba(54, 162, 235, 1)",
  製品B:"rgba(255,99,132,1)",
  製品C:"rgba(0, 170, 0, 1)",
  製品D:"rgba(255, 170, 255, 1)",
  製品E:"rgba(255, 255, 0, 1)",
  製品F:"rgba(255, 112, 0, 1)",
  製品G:"rgba(100, 0, 0, 1)",
  製品H:"rgba(255, 184, 184, 1)",
  製品I:"rgba(0, 255, 255, 1)"
};
*/

ドーナツ型グラフの背景色です。
参考までに、縁取り色のつけ方もソースコードにコメントで載せておきました。

今回は、各製品に対してそれぞれ色指定する方法を採用しています。
製品数が多い場合や頻繁に増減する場合は、背景色/縁取り色ともにランダムで決定する方法も参照ください。
具体的な方法はサンプルコード内でコメントとして残しています。

200
201
202
203
204
// 前年同月比グラフを描画(1つ目のグラフ)
const ctx = document.getElementById('canvas1');
if (chart1) {
  chart1.destroy();
}

グラフの表示に必要な canvas 要素を取得しています。
また、グラフを再描画する時は古いグラフを削除しています。

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
chart1 = new Chart(ctx, {
  type: 'bar',
  data: {
    labels: nMonthsBefore,
    datasets: [
      {
        label: '前年同月売上',
        data: eventuallyLastYearData,
        backgroundColor: 'rgba(54, 162, 235, 0.2)',
        borderColor: 'rgba(54, 162, 235, 1)',
        borderWidth: 1,
        test: 'aiueo'
      },
      {
        label: minDateTime.toFormat('yyyy年M月~') + maxDateTime.toFormat('yyyy年M月売上'),
        data: eventuallyThisYearData,
        backgroundColor: 'rgba(255, 99, 132, 0.2)',
        borderColor: 'rgba(255,99,132,1)',
        borderWidth: 1,
        test: 'ABCDE'
      }
    ]
  },

Chart.js の最も重要な箇所です。
type: 'bar' で棒グラフを指定しています。
labels はグラフ下部の「●月」の表記です。

datasets 配列に 2 つのオブジェクト(当年/前年)を入れることで、「●月」に対して 2 つの棒グラフを表示できます。

232
233
234
235
236
237
238
239
240
241
scaleLabel: {
  display: true,
  labelString: '売上',
  fontFamily: 'monospace'
},
ticks: {
  beginAtZero: true,
  maxTicksLimit: 8,
  callback: (value) => value + '万円'
}

scaleLabel はグラフ左右のラベルです。
ticks はグラフ左右の目盛りの指定で、beginAtZero はゼロから起算するか、maxTicksLimit は目盛りの最大分割数、また callback で目盛りに「万円」の単位を付加しています。

244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
tooltips: {
  enabled: true,
  mode: 'single',
  callbacks: {
    title: (tooltipItems, titleData) => {
      const tmp = tooltipItems[0].xLabel.split('月');
      // 1) tooltipItems[0].datasetIndex  0:指定日(maxDateTime)の前年、1:指定日(maxDateTime)の年
      // 2) tooltipItems[0].xLabelの月がmaxDateTimeより大きかったら-1年にする
      let retYear = maxDateTime.minus({years: 1}).toFormat(dateFormatY);
      if (Number(tmp[0]) > Number(maxDateTime.toFormat(dateFormatM2))) {
        if (tooltipItems[0].datasetIndex === 0) {
          retYear = maxDateTime.minus({years: 2}).toFormat(dateFormatY);
        }
      } else if (tooltipItems[0].datasetIndex === 1) {
        retYear = maxDateTime.toFormat(dateFormatY);
      }
      return `${retYear}${tooltipItems[0].xLabel}`;
    },
    label: (tooltipItems, data3) => tooltipItems.yLabel + '万円'
  }
}

tooltips は、グラフにカーソルを合わせた時の処理です。
当年のグラフなら当年の、前年のグラフなら前年の年月を表示し、「万円」の単位等を付加しています。

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
datasets: [
  {
    yAxisID: 'y-axis-1',
    type: 'line',
    label: '試用申込数',
    data: eventuallyTrialData,
    backgroundColor: 'rgba(54, 162, 235, 0.2)',
    borderColor: 'rgba(54, 162, 235, 1)',
    borderWidth: 1
  },
  {
    yAxisID: 'y-axis-0',
    type: 'bar',
    label: minDateTime.toFormat('yyyy年M月~') + maxDateTime.toFormat('yyyy年M月売上'),
    data: eventuallyThisYearData,
    backgroundColor: 'rgba(255, 99, 132, 0.2)',
    borderColor: 'rgba(255,99,132,1)',
    borderWidth: 1
  }
];

こちらは mixed グラフの書き方です。
datasets 配列内にそれぞれ type を設定することで、「棒グラフ+折れ線グラフ」のようなグラフを描画できます。

また yAxisID を設定することで、それぞれ異なる scale(左右の目盛り)に対応させることができます。

357
358
chart3 = new Chart(ctx3, {
  type: 'doughnut',

ドーナツ型グラフを描画する際は、type: doughnut を指定するだけで OK です。

407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
// 【参考】ドーナツグラフの各項目を常に表示
Chart.pluginService.register({
  beforeRender: (chart) => {
    if (chart.config.options.showAllTooltips) {
      chart.pluginTooltips = [];
      chart.config.data.datasets.forEach((dataset, i) => {
        chart.getDatasetMeta(i).data.forEach((sector, j) => {
          chart.pluginTooltips.push(new Chart.Tooltip({
            _chart: chart.chart,
            _chartInstance: chart,
            _data: chart.data,
            _options: chart.options.tooltips,
            _active: [sector]
          }, chart));
        });
      });
      chart.options.tooltips.enabled = false;
    }
  },
  afterDraw: (chart, easing) => {
    if (chart.config.options.showAllTooltips) {
      if (!chart.allTooltipsOnce) {
        if (easing !== 1) return;
        chart.allTooltipsOnce = true;
      }
      chart.options.tooltips.enabled = true;
      Chart.helpers.each(chart.pluginTooltips, (tooltip) => {
        tooltip.initialize();
        tooltip.update();
        tooltip.pivot();
        tooltip.transition(easing).draw();
      });
      chart.options.tooltips.enabled = false;
    }
  }
});

ドーナツ型グラフの値表示です。
普通はカーソルを合わせた際に値が表示されるのですが、このコードを組み込むと常に値を表示できます。

おわりに

便利なグラフが簡単に描画できる Chart.js ライブラリの紹介となりましたが、いかがでしたでしょうか。
皆様のご参考になれば幸いです。

information

この Tips は、2023 年 3 月版 kintone で動作を確認しています。