Display Bar/Line Combo Charts

Contents

Overview

This article introduces how to display a bar/line combo chart in a Kintone App using Chart.js (External link) .

  • A bar/line combo chart displays a bar graph and a line graph together.
  • It is used to compare two related values that have different units or scales.
  • The chart can show how those values change over time.

For example, the cost of digital ads can be shown as bars, and conversion rates as a line. Viewers of the chart can make assumptions on the ad creative's quality, based on the relationship between the bar and line values.

Sample Image

A bar/line combo chart is displayed on the Record List page of an App named "Ad Performance". The bars show the Ad Spend, and the line shows the Conversion Rate.

In this sample, Campaign A ran from January to June, and Campaign B ran from July to December. A bar/line combo chart lets you notice facts like the following:

Period Ad Campaign What happened
January–March Campaign A Conversion rate grew in proportion to ad spend
April–May Campaign A Conversion rate declined
June Campaign A Ad spend increased, but conversion rate did not improve
July Campaign B Switched ads; conversion rate rose
August–November Campaign B Conversion rate grew in proportion to ad spend again
December Campaign B Conversion rate declined again

Users can make assumptions from these facts, such as ad viewers being tired of the same ad after 3 to 4 months.

Prepare the App

Create an App (External link) named "Ad Performance" with the following fields and settings.

Field Type Field Name Field Code Notes
Number Year year
Drop-down Month month Set the options in order from 1 to 12
Drop-down Ad Campaign ad_campaign Set options such as Campaign A and Campaign B
Number Conversion Rate conversion_rate Set the unit to %
Number Ad Spend ad_spend Set the unit to $

The form should look like the below screenshot.

After creating the App, add a few records inside.

JavaScript Settings

This customization has two JavaScript settings that need to be set. The first is for loading the Chart.js library. The second is for creating the chart, based on the Kintone data.

Set the library

This example uses Chart.js (External link) v4.5.1. Set the following URL from the Kintone CDN into the App's JavaScript and CSS Customization settings (External link) .

Set the JavaScript Code

Type the following code into a text editor and save it as a JavaScript file. Upload it to the App's JavaScript and CSS Customization settings.

 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
(() => {
  'use strict';

  // Get all records from the App using the Get Records API and the Offset Method
  const getRecords = (optOffset = 0, optRecords = []) => {
    const LIMIT = 500;
    const params = {
      app: kintone.app.getId(),
      fields: ['year', 'month', 'ad_spend', 'conversion_rate'],
      query: `order by year asc, month asc limit ${LIMIT} offset ${optOffset}`,
    };

    return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then((resp) => {
      const records = optRecords.concat(resp.records);
      if (resp.records.length === LIMIT) {
        return getRecords(optOffset + LIMIT, records);
      }
      return records;
    });
  };

  // Display the chart on the Record List page
  kintone.events.on('app.record.index.show', (event) => {
    const header = kintone.app.getHeaderSpaceElement();
    // Prevent chart duplication bug
    if (!header || document.getElementById('ad-chart')) {
      return event;
    }

    // Create a canvas element to display the chart
    const wrapper = document.createElement('div');
    wrapper.style.maxWidth = '800px';
    wrapper.style.height = '300px';

    const canvas = document.createElement('canvas');
    canvas.id = 'ad-chart';
    wrapper.appendChild(canvas);
    header.appendChild(wrapper);

    // Get records, then create a chart
    return getRecords()
      .then((records) => {
        new Chart(canvas, {
          data: {
            // Set the labels for the x-axis which is the combination of month and year
            labels: records.map((record) => `${record.month.value} ${record.year.value}`),
            // Set the datasets
            datasets: [
              {
                type: 'bar',
                label: 'Ad Spend ($)',
                data: records.map((record) => Number(record.ad_spend.value)),
                backgroundColor: 'rgb(135, 206, 235)',
                yAxisID: 'y',
                order: 2,
              },
              {
                type: 'line',
                label: 'Conversion Rate (%)',
                data: records.map((record) => Number(record.conversion_rate.value)),
                borderColor: 'rgb(196, 92, 38)',
                yAxisID: 'y1',
                order: 1,
              },
            ],
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            // Set the y-axis for both datasets
            scales: {
              y: { beginAtZero: true },
              y1: {
                position: 'right',
                beginAtZero: true,
                grid: { drawOnChartArea: false },
              },
            },
          },
        });
        return event;
      })
      .catch((error) => {
        console.error(error);
        return event;
      });
  });
})();
caution
Caution

Caution: The order in which JavaScript and CSS are uploaded to an app matters. In this example, ensure that the Chart.js library is uploaded before the JavaScript file. You can change the order of uploads by clicking and dragging on the arrows for each item on the Upload JavaScript / CSS page.

Click Save, and then on Update App. A bar/line combo chart should be displayed above the list of records.

Code Explanation

This section explains the sample code above.

Prevent chart duplication bug

The app.record.index.show event runs when the Record List page is displayed. Actions such as applying a filter cause the list to be redisplayed and trigger this event again.

In this case, a duplication bug can occur, resulting in multiple charts being displayed above the list of records. To prevent this, the code checks for existing graph elements in the header.

1
2
3
4
5
  kintone.events.on('app.record.index.show', (event) => {
    const header = kintone.app.getHeaderSpaceElement();
    if (!header || document.getElementById('ad-chart')) {
      return event;
    }

Create a canvas element to display the chart

A canvas element is required for Chart.js. A wrapper is also created with a set size.

1
2
3
4
5
6
7
8
    const wrapper = document.createElement('div');
    wrapper.style.maxWidth = '800px';
    wrapper.style.height = '300px';

    const canvas = document.createElement('canvas');
    canvas.id = 'ad-chart';
    wrapper.appendChild(canvas);
    header.appendChild(wrapper);

Get records with the Get Records API

Records are retrieved with the Get Records API , instead of using the records from the event object. This way, the chart can include records beyond those displayed on the current Record List page.

The query sorts the records by the Year and Month fields in ascending order so that the X-axis follows chronological order.

By setting the limit parameter for the Get Records API, a maximum of 500 records can be called with one request. To retrieve more records, the API can be called recursively by utilizing the offset parameter. For more information, refer to the Get All Records from an App: The Offset Method article.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const getRecords = (optOffset = 0, optRecords = []) => {
  const params = {
    app: kintone.app.getId(),
    fields: ['year', 'month', 'ad_spend', 'conversion_rate'],
    query: `order by year asc, month asc limit 500 offset ${optOffset}`,
  };

  return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then((resp) => {
    const records = optRecords.concat(resp.records);
    if (resp.records.length === 500) {
      return getRecords(optOffset + 500, records);
    }
    return records;
  });
};

Create a chart

The labels are set from the Month and Year fields, and map() is used to set the dataset values from the Ad Spend and Conversion Rate fields. Two Y-axis are configured so the bar and line can use different scales.

For more details on Chart.js options, refer to the Chart.js documentation (External link) .

 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
new Chart(canvas, {
      data: {
        // Set the labels for the x-axis which is the combination of month and year
        labels: records.map((record) => `${record.month.value} ${record.year.value}`),
        // Set the datasets 
        datasets: [
          {
            type: 'bar',
            label: 'Ad Spend ($)',
            data: records.map((record) => Number(record.ad_spend.value)),
            backgroundColor: 'rgb(135, 206, 235)',
            yAxisID: 'y',
            order: 2,
          },
          {
            type: 'line',
            label: 'Conversion Rate (%)',
            data: records.map((record) => Number(record.conversion_rate.value)),
            borderColor: 'rgb(196, 92, 38)',
            yAxisID: 'y1',
            order: 1,
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        // Set the y-axis for both datasets
        scales: {
          y: { beginAtZero: true },
          y1: {
            position: 'right',
            beginAtZero: true,
            grid: { drawOnChartArea: false },
          },
        },
      },
    });

Reference