0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Amazon QuickSight で Highcharts のビジュアル導入を検証

0
Last updated at Posted at 2025-06-30

はじめに

2024年11月22日のアップデートで、QuickSightでHighchartsのビジュアルが提供されるようになりました(プレビュー)。今回のアップデートにより、従来のQuickSightでは対応していなかったビジュアルをHighchartsを用いて作成及びカスタマイズできるようになりました。

画像.png

本記事では、QuickSightで新しく使用できるようになったHighchartsの機能について、いくつかピックアップして検証を行い、業務上での活用方法について考えました。

語句

Highcharts

JavaScriptを用いてグラフ描画を行うライブラリです。Web アプリケーションでインタラクティブなグラフや視覚化を作成するために設計されています。

Highcharts Demo

Highchartsで使用可能なチャートを確認できるサイトです。
本記事では、各種チャートのJavascriptコードを確認する際に活用します。

Demo Central

ユーザー登録不要でダッシュボードのサンプルの閲覧や編集、QuickSightの一部機能を無料で試すことの出来るサイトです。本記事では、各種チャートのJsonコードを確認する際に活用します。

検証項目

1.ガントチャート
2.スモールマルチプル
3.ランク
4.コンボグラフ
5.Highchartsのリファレンス
6.ヒートマップ
7.その他

検証結果

1.ガントチャート

観点: 既存機能で実装できなかったものが、実装できるか

1.Demo Central環境にて、ガントチャートサンプルの右にある赤枠部のコードをコピー
スクリーンショット 2025-06-16 154219.png

2.QuickSight環境にて、赤枠部のボタンを押下
スクリーンショット 2025-06-16 154505.png

3.プロパティにチャートコードが表示されることを確認
スクリーンショット 2025-06-16 154635.png

4.1.でコピーしたコードを貼り付けると、ガントチャートのサンプルを再現できることを確認
スクリーンショット 2025-06-16 154831.png

[Jsonコード]

{
  "chart": {
    "type": "xrange"
  },
  //X range default legend doesn't support series colors.
  //Hence, using subtitle to create a custom legend.
  //Color is fed in using html styling done in LegendText calculation.
  //This does take away legend interactivity; If that is important,
  //comment/remove subtitle and set legend.enabled to true below.
  "subtitle": {
    "text": ["join", ["unique", ["getColumn", 6]], "    "],
    "align": "center",
    "verticalAlign": "bottom",
    "y": 10,
    "useHTML": true
  },
  "legend": {
    "enabled": false
  },
  "xAxis": {
    "type": "datetime"
  },
  "yAxis": {
    "title": {
      "text": ""
    },
    "categories": ["Prototyping", "Development", "Testing"],
    "reversed": true
  },
  "series": [
    "map", //Iterate through each project
    ["unique", ["getColumn", 0]], //Project 1, 2 & 3
    {
      "name": ["item"],
      "pointWidth": 20,
      "dataLabels": { "enabled": true },
      "data": [
        "map", //Iterate through below filtered dataset
        [
          "filter",
          ["getColumn", 0, 1, 2, 3, 4, 5], //Project, ProjectColor, Phase, Start and end date
          ["==", ["get", ["item"], 0], ["item", 2]] //Where Project = Project from outer map
        ],
        {
          "x": ["get", ["item"], 3],
          "x2": ["get", ["item"], 4],
          //Xrange chart's y axis expects numeric values - even for categories.
          //Hence, converting Phase to category index using case statement.
          "y": [
            "case",
            ["==", ["get", ["item"], 2], "Prototyping"],
            0,
            ["==", ["get", ["item"], 2], "Development"],
            1,
            ["==", ["get", ["item"], 2], "Testing"],
            2
          ],
          "partialFill": ["get", ["item"], 5],
          //Though assigned at point level, this is really series colors.
          //Ensure that all rows of a project has same color specified in dataset.
          "color": ["get", ["item"], 1]
        }
      ]
    }
  ]
}

結論として、Highchartsのビジュアルを活用し、QuickSight上でガントチャートの実装は可能。

2.スモールマルチプル

観点: 既存機能で対応していない水平棒グラフに対して、実装できるか

Highchartsにおけるスモールマルチプル実現方法として、以下の3点が考えられる。

a. 複数のチャートコンテナを用意し、複数のチャートを並べる
→ HTML内に複数の描画領域を用意し、それぞれに独立したチャートを描画することで実現可能。

結果: 複数チャートコンテナを用意するには、HTML上でチャート描画用の<div>コンテナを分ける必要がある。QuickSightのHighcharts機能では、HTMLの直接編集やコンテナ分割ができないため、複数チャートを並べて表示するのは難しい。

b. 1つのチャート内で複数のグラフ領域(gridレイアウト)を使う方法
→ Highchartsのgridオプションを用いて、一つのチャート内に複数のプロット領域を設けることで、スモールマルチプルを表現可能。

結果: QuickSightのHighchartsでは gridオプションを利用した細かいレイアウト制御はできないため、再現不可。

c. チャート内で複数グラフを表示させる
→一つのチャート内で複数のseriesを設定し、単一の軸上または複数軸を組み合わせて複数データ系列を描画する方法。あくまで「一つの描画領域」に収まるため、スモールマルチプルのような独立した分割表示とは異なる。

結果: Highchartsの1つのチャート内で複数グラフを「上下に並べる」スモールマルチプル風の実装は可能。ただし、各軸(xAxis・yAxis)の "top" と "height" の数値を細かく調整してバランスを取る必要があり、表示位置のズレや重なりが発生しやすい。

スクリーンショット 2025-06-17 121839.png

{
  "chart": {
    "type": "bar",
    "height": 400
  },
  "xAxis": [
    {
      "top": "0%",
      "height": "45%",
      "title": { "text": "カテゴリ" },
      "offset": 0,
      "categories": ["getColumn",0]
    },
    {
      "top": "60%",
      "height": "45%",
      "title": { "text": "カテゴリ" },
      "offset": 0,
      "categories": ["getColumn",0]
    }
  ],
  "yAxis": [
    {
      "top": "0%",
      "height": "45%",
      "offset": 0,
      "type": "category",
      "title": { "text": "売上1" }
    },
    {
      "top": "55%",
      "height": "45%",
      "offset": 0,
      "type": "category",
      "title": { "text": "売上2" }
    }
  ],
  "series": [
    {
      "name": "売上1",
      "data": ["getColumn",1],
      "xAxis": 0,
      "yAxis": 0
    },
    {
      "name": "売上2",
      "data": ["getColumn",2],
      "xAxis": 1,
      "yAxis": 1
    }
  ],
  "legend": {
    "enabled": true,
    "align": "center",
    "verticalAlign": "bottom"
  }
}

結論として、疑似的に複数系列を1つのチャート内に重ねて表現することは可能だが、実装には手間がかかり視認性も劣るため、非推奨。

3.ランク

観点: 既存機能では工夫して実装していたものが、「より効率的に」実装できるか​

1.Demo Central環境にて、テストデータを表示
スクリーンショット 2025-06-16 160816.png

2.テストデータをCSV出力
スクリーンショット 2025-06-16 161030.png

3.QuickSight環境にて、2.のテストデータを折れ線グラフで表示
スクリーンショット 2025-06-16 161537.png

4.rank関数を使用して、計算フィールドを作成
スクリーンショット 2025-06-16 162227.png

※以下のサイトでrankの記載が無いため、計算フィールドの作成が必要だと思われる
 https://docs.aws.amazon.com/quicksight/latest/user/jle-arithmetics.html

5.チャートコードに以下のコードを貼り付けると、ランクで分けられたグラフが表示されることを確認
スクリーンショット 2025-06-16 162717.png

[Jsonコード]

{
  "xAxis":{
    "type":"datetime"
  },
  "yAxis":{
    "title":{
      "text":"rank_sales"
    }
  },
  "series": ["map", //map関数(ループ)
    ["unique",["getColumn",1]], //店名リストを取り出す 
    {
      "type":"line",
      "name":["item"],//ループ内の店名を使用
      "data":["filter",
        ["getColumn",0,2,1], // date,store_name,rank_sale
        ["==",["get",["item"],2],["item",2]] //店ごとのデータを抽出
      ]
    }
  ]
}

※5.のキャプチャの縦軸は1位が下になっていて、下位が上になっているが、Y軸の反転表示を有効にすることで変更可能
スクリーンショット 2025-06-16 163623.png

[Jsonコード]

{
  "xAxis": {
    "type": "datetime"
  },
  "yAxis": {
    "title": {
      "text": "rank_sales"
    },
    "reversed": true // 新規追加
  },
  "series": [
    "map",
    ["unique", ["getColumn", 1]],
    {
      "type": "line",
      "name": ["item"],
      "data": [
        "filter",
        ["getColumn", 0, 2, 1],
        ["==", ["get", ["item"], 2], ["item", 2]]
      ]
    }
  ]
}

結論として、Highchartsのビジュアルを活用し、QuickSight上でランクの実装は可能。ただし、rank関数のフィールド作成が必要。

4.コンボグラフ

観点: 既存機能では工夫して実装していたものが、「より効率的に」実装できるか​

1.Demo Central環境にて、コンボグラフのコードをコピー
スクリーンショット 2025-06-17 104853.png

2.QuickSight環境にて、チャートコードに1.でコピーしたコードを貼り付けたところ、コンボグラフが再現されることを確認
スクリーンショット 2025-06-17 105053.png

[Jsonコード]

{
  "xAxis": {
    "type":"category",
    "categories": ["unique",["getColumn",0]] //Industry
  },
  "yAxis": {
    "title": {
      "text": "Sales"
    }
  },
  "tooltip": {
    "pointFormat": "<span style='color:{point.color}'>\u25CF</span> <b>{series.name} </b> : {point.y:,.2f}" 
  },
  "plotOptions": {
    "series": {
      "borderRadius": "25%"
    }
  },
  "series":["+", //Adding dynamic column series with pie and line series
    //Yes, + operator allow your to add arrays as well.
    ["map", //Iterate through below list
      ["unique",["getColumn",1]], //Region
      {
        "type":"column",
        "name":["item"],  //AMER, APJ, EMEA
        "data":["map",
          ["filter",
            ["getColumn",0,1,2],  //Region, Industry, Sales
            ["==", ["get",["item"],1], ["item",2]]  //Where region = region from outer map
          ],
          {
            "name":["get",["item"],0],  //Industry
            "y": ["get",["item"],2] //Sales
          }
        ]
      }
    ],
    [
      //Create series entry for line chart
      {
        "type":"line",
        "name":"Avg Sales",
        "data":["map",
          ["unique",["getColumn",0]], //Industry
          {
            "name":["item"], //Communication, Consumer products etc
            //There will be three rows returned for each industry - one per region
            //So, calculating average by diving sum of sales by sum of 1.
            //Keeping the denominator dynamic to allow for addition of more regions
            "y":["/",
              //Sum of sales using reduce operation
              ["reduce",
                ["filter", 
                  ["getColumn",0,2], //Industry, Sales
                  ["==", ["get",["item"],0],["item",2]] //Where Industry = Industry from outer map
                ],
                ["+",["acc"],["get",["item"],1]], //Add Sales to accumulator
                0   //Starting value of accumulator
              ],
              //Same reduce logic as above, except that we add static value 1
              ["reduce",
                ["filter",
                  ["getColumn",0,2],
                  ["==", ["get",["item"],0],["item",2]]
                ],
                ["+",["acc"],1], //Adding static value of 1 to accumulator
                0
              ]
            ]
          }
        ]
      },
    //Create series entry for pie chart
      {
        "type":"pie",
        "name":"Regional Sales",
        "data":["map",
          ["unique",["getColumn",1]], //Region
          {
            "name":["item"],  //AMER, APJ, EMEA
            //There will be ten rows returned for each region - one per industry
            //So, calculating sum of sales across these rows for each region.
            "y": ["reduce",
              ["filter",
                ["getColumn",1,2],  //Region, Sales
                ["==", ["get",["item"],0],["item",2]] //Where region == region from outer map
              ],
              ["+",["acc"],["get",["item"],1]], //Add Sales to accumulator
              0 //Starting value of accumulator
            ],
            "dataLabels":["case",
              //Using data label of first donut segment to show the overall total
              ["==", ["itemIndex"], 0],
              {
                "enabled":true,
                "distance":-50,
                "format":"{point.total:,.0f}"
              },
              //Disabling datalabel for other segments
              {"enabled":false},
            ]
          }
        ],
        "center": [75, 55],
        "size": 100,
        "innerSize": "70%"
      }
    ]
  ]
}

// ["filter", ["getColumn", 0, 2], ["==", ["get", ["item"], 0], ["item", 2]]]	
// ["getColumn", 0, 2] → Industry, Sales の列データを取得。
// 条件: Industry(["item"])と一致するデータだけに絞り込む。
	
// ["reduce", [...], ["+", ["acc"], ["get", ["item"], 1]], 0]	
// reduceは配列の要素を1つにまとめている
// フィルタされたデータに対し、Salesを全部加算。
// ["acc"] は累積合計の変数。
	
// ["reduce", [...], ["+", ["acc"], 1], 0]	
// フィルタされたデータに対し、1ずつ加算して件数(カウント)を取得。
// データの数だけ 1 を足していく。
	
// ["/", sum, count]	
// 割り算

3.Colorプロパティを追加することで、色変更が可能
スクリーンショット 2025-06-17 105544.png

        "type":"column",
        "name":["item"],  //AMER, APJ, EMEA
        "color": ["case",
                  ["==", ["item"], "AMER"], "rgb(124, 181, 236)", // 例: AMERなら薄い青
                  ["==", ["item"], "APJ"], "rgb(67, 67, 72)",   // 例: APJなら薄いグレー
                  ["==", ["item"], "EMEA"], "rgb(144, 237, 125)", // 例: EMEAなら薄い緑
                  "gray" // 上記以外のRegionの場合のデフォルト色
                 ],

4.ドーナツグラフの修正が可能
スクリーンショット 2025-06-17 110503.png

中心位置を修正可能
スクリーンショット 2025-06-17 110705.png

直径サイズを修正可能
スクリーンショット 2025-06-17 110836.png

内側の空洞サイズを修正可能
スクリーンショット 2025-06-17 111201.png

5.グラフの種類の修正が可能

type:areaに変更
スクリーンショット 2025-06-17 111432.png

type:scatterに変更
スクリーンショット 2025-06-17 111604.png

type:bubbleに変更
スクリーンショット 2025-06-17 111707.png

結論として、Highchartsのビジュアルを活用し、QuickSight上でコンボグラフの実装は可能。

5.Highchartsのリファレンス

観点:QuickSight上のリファレンス以外が使用可能か

1.Highcharts Demo環境にて、Dumbbell seriesのJavascriptコードをコピー

スクリーンショット 2025-06-17 091426.png

2.QuickSight環境にて、チャートコードに1.でコピーしたコードを貼り付けたところ、グラフが再現されないことを確認
スクリーンショット 2025-06-17 091708.png

[Javascriptコード]

const data = [{
    name: 'Austria',
    low: 70.1,
    high: 81.3
}, {
    name: 'Belgium',
    low: 71.0,
    high: 81.9
},  {
    name: 'Czechia',
    low: 69.6,
    high: 77.4
}, {
    name: 'Estonia',
    low: 70.4,
    high: 76.9
}, {
    name: 'Greece',
    low: 73.8,
    high: 80.3
}, {
    name: 'Hungary',
    low: 69.2,
    high: 74.5
}, {
    name: 'Iceland',
    low: 73.8,
    high: 83.2
}, {
    name: 'Lithuania',
    low: 71.1,
    high: 74.5
}, {
    name: 'Norway',
    low: 74.3,
    high: 83.2
},  {
    name: 'Portugal',
    low: 66.7,
    high: 81.2
}, {
    name: 'Romania',
    low: 68.2,
    high: 72.9
},  {
    name: 'Slovakia',
    low: 69.8,
    high: 74.8
}, {
    name: 'Sweden',
    low: 74.7,
    high: 83.2
}, {
    name: 'Switzerland',
    low: 73.2,
    high: 84.0
}];

Highcharts.chart('container', {

    chart: {
        type: 'dumbbell',
        inverted: true
    },

    legend: {
        enabled: false
    },

    subtitle: {
        text: '1970 vs 2021 Source: ' +
            '<a href="https://ec.europa.eu/eurostat/en/web/main/data/database"' +
            'target="_blank">Eurostat</a>'
    },

    title: {
        text: 'Change in Life Expectancy'
    },

    tooltip: {
        shared: true
    },

    xAxis: {
        type: 'category'
    },

    yAxis: {
        title: {
            text: 'Life Expectancy (years)'
        }
    },

    series: [{
        name: 'Life expectancy change',
        data: data
    }]

});

3.1.でコピーしたコードを、生成AI等を使用してJson形式に変換して貼り付けたところ、グラフが再現されることを確認
※コード内に直接データ入力するため、カラム指定を行わない。

スクリーンショット 2025-06-17 092404.png

[Jsonコード]

{
  "chart": {
    "type": "dumbbell",
    "inverted": true
  },
  "title": {
    "text": "Change in Life Expectancy"
  },
  "subtitle": {
    "text": "1970 vs 2021 Source: <a href='https://ec.europa.eu/eurostat/en/web/main/data/database' target='_blank'>Eurostat</a>"
  },
  "xAxis": {
    "type": "category"
  },
  "yAxis": {
    "title": {
      "text": "Life Expectancy (years)"
    }
  },
  "legend": {
    "enabled": false
  },
  "tooltip": {
    "shared": true
  },
  "series": [{
    "name": "Life expectancy change",
    "data": [
      {"name": "Austria", "low": 70.1, "high": 81.3},
      {"name": "Belgium", "low": 71.0, "high": 81.9},
      {"name": "Czechia", "low": 69.6, "high": 77.4},
      {"name": "Estonia", "low": 70.4, "high": 76.9},
      {"name": "Greece", "low": 73.8, "high": 80.3},
      {"name": "Hungary", "low": 69.2, "high": 74.5},
      {"name": "Iceland", "low": 73.8, "high": 83.2},
      {"name": "Lithuania", "low": 71.1, "high": 74.5},
      {"name": "Norway", "low": 74.3, "high": 83.2},
      {"name": "Portugal", "low": 66.7, "high": 81.2},
      {"name": "Romania", "low": 68.2, "high": 72.9},
      {"name": "Slovakia", "low": 69.8, "high": 74.8},
      {"name": "Sweden", "low": 74.7, "high": 83.2},
      {"name": "Switzerland", "low": 73.2, "high": 84.0}
    ]
  }]
}

4.QuickSight環境で、Clockも再現されることを確認
スクリーンショット 2025-06-17 092558.png

[Jsonコード]

{
  "chart": {
    "type": "gauge",
    "plotBackgroundColor": null,
    "plotBackgroundImage": null,
    "plotBorderWidth": 0,
    "plotShadow": false,
    "height": "80%"
  },
  "credits": {
    "enabled": false
  },
  "title": {
    "text": "The Highcharts clock"
  },
  "pane": {
    "background": [
      {},
      {
        "backgroundColor": {
          "radialGradient": {
            "cx": 0.5,
            "cy": -0.4,
            "r": 1.9
          },
          "stops": [
            [0.5, "rgba(255, 255, 255, 0.2)"],
            [0.5, "rgba(200, 200, 200, 0.2)"]
          ]
        }
      }
    ]
  },
  "yAxis": {
    "labels": {
      "distance": -23,
      "style": {
        "fontSize": "18px"
      }
    },
    "min": 0,
    "max": 12,
    "lineWidth": 0,
    "showFirstLabel": false,
    "minorTickInterval": "auto",
    "minorTickWidth": 3,
    "minorTickLength": 5,
    "minorTickPosition": "inside",
    "minorGridLineWidth": 0,
    "minorTickColor": "#666",
    "tickInterval": 1,
    "tickWidth": 4,
    "tickPosition": "inside",
    "tickLength": 10,
    "tickColor": "#666",
    "title": {
      "text": "Powered by<br/>Highcharts",
      "style": {
        "color": "#BBB",
        "fontWeight": "normal",
        "fontSize": "10px",
        "lineHeight": "10px"
      },
      "y": 10
    }
  },
  "tooltip": {
    "format": "{series.chart.tooltipText}"
  },
  "series": [
    {
      "data": [
        {
          "id": "hour",
          "y": 11.683333333333334,
          "dial": {
            "radius": "60%",
            "baseWidth": 4,
            "baseLength": "95%",
            "rearLength": 0
          }
        },
        {
          "id": "minute",
          "y": 10.4,
          "dial": {
            "baseLength": "95%",
            "rearLength": 0
          }
        },
        {
          "id": "second",
          "y": 2.0,
          "dial": {
            "radius": "100%",
            "baseWidth": 1,
            "rearLength": "20%"
          }
        }
      ],
      "animation": false,
      "dataLabels": {
        "enabled": false
      }
    }
  ]
}

5.Area Race Chartは、Highcharts Demo環境ではアニメーション付で表現されていたが、QuickSight環境ではJsonコードを貼り付けても再現できないことを確認

スクリーンショット 2025-06-17 093144.png

スクリーンショット 2025-06-17 093238.png

※Javascriptコードで使用されている、HTML内に埋め込まれたCSV文字列からデータを読み込む処理が、Jsonコードで使用できないことが原因だと思われる
例. document.getElementById('csv').innerHTML

[Javascriptコード]

const btn = document.getElementById('play-pause-button'),
    input = document.getElementById('play-range'),
    startYear = 1973,
    endYear = 2021;

// General helper functions
const arrToAssociative = arr => {
    const tmp = {};
    arr.forEach(item => {
        tmp[item[0]] = item[1];
    });

    return tmp;
};

function getSubtitle() {
    return `<span style='font-size: 60px'>${input.value}</span>`;
}

const formatRevenue = [];

const chart = Highcharts.chart('container', {
    chart: {
        events: {
            // Some annotation labels need to be rotated to make room
            load: function () {
                const labels = this.annotations[0].labels;
                labels
                    .find(a => a.options.id === 'vinyl-label')
                    .graphic.attr({
                        rotation: -20
                    });
                labels
                    .find(a => a.options.id === 'cassettes-label')
                    .graphic.attr({
                        rotation: 20
                    });
            }
        },
        type: 'area',
        marginTop: 100,
        animation: {
            duration: 700,
            easing: t => t
        }
    },
    title: {
        text: 'Music revenue race chart'
    },
    subtitle: {
        text: getSubtitle(),
        floating: true,
        align: 'right',
        verticalAlign: 'middle',
        x: -100,
        y: -110
    },
    data: {
        csv: document.getElementById('csv').innerHTML,
        itemDelimiter: '\t',
        complete: function (options) {
            for (let i = 0; i < options.series.length; i++) {
                formatRevenue[i] = arrToAssociative(options.series[i].data);
                options.series[i].data = null;
            }
        }
    },
    xAxis: {
        allowDecimals: false,
        min: startYear,
        max: endYear
    },
    yAxis: {
        reversedStacks: false,
        title: {
            text: 'Revenue in the U.S.'
        },
        labels: {
            format: '${text} B'
        }
    },
    tooltip: {
        split: true,
        headerFormat: '<span style="font-size: 1.2em">{point.x}</span>',
        pointFormat:
            '{series.name}: <b>${point.y:,.1f} B</b> ({point.percentage:.1f}%)',
        crosshairs: true
    },
    plotOptions: {
        area: {
            stacking: 'normal',
            pointStart: startYear,
            marker: {
                enabled: false
            }
        }
    },
    annotations: [
        {
            labelOptions: {
                borderWidth: 0,
                backgroundColor: undefined,
                verticalAlign: 'middle',
                allowOverlap: true,
                style: {
                    pointerEvents: 'none',
                    opacity: 0,
                    transition: 'opacity 500ms'
                }
            },
            labels: [
                {
                    text: 'Vinyl',
                    verticalAlign: 'top',
                    point: {
                        x: 1975,
                        xAxis: 0,
                        y: 1.45,
                        yAxis: 0
                    },
                    style: {
                        fontSize: '0.8em',
                        color: '#000'
                    },
                    id: 'vinyl-label'
                },
                {
                    text: 'LP-EP',
                    point: {
                        x: 1980,
                        xAxis: 0,
                        y: 0.2,
                        yAxis: 0
                    },
                    style: {
                        fontSize: '1.4em',
                        color: '#ffffff'
                    },
                    id: 'lpep-label'
                },
                {
                    text: 'Cass',
                    point: {
                        x: 1987,
                        xAxis: 0,
                        y: 2.6,
                        yAxis: 0
                    },
                    style: {
                        fontSize: '1.5em',
                        color: '#ffffff'
                    },
                    id: 'cassettes-label'
                },
                {
                    text: 'CD',
                    point: {
                        x: 1999,
                        xAxis: 0,
                        y: 6,
                        yAxis: 0
                    },
                    style: {
                        fontSize: '4em',
                        color: '#ffffff'
                    },
                    id: 'cd-label'
                },
                {
                    text: 'DL',
                    point: {
                        x: 2011,
                        xAxis: 0,
                        y: 4,
                        yAxis: 0
                    },
                    style: {
                        fontSize: '1.2em',
                        color: '#ffffff'
                    },
                    id: 'dl-label'
                },
                {
                    text: 'Strm',
                    point: {
                        x: 2018,
                        xAxis: 0,
                        y: 5,
                        yAxis: 0
                    },
                    style: {
                        fontSize: '1.5em',
                        color: '#ffffff'
                    },
                    id: 'streams-label'
                }
            ]
        }
    ],

    responsive: {
        rules: [
            {
                condition: {
                    maxWidth: 500
                },
                chartOptions: {
                    title: {
                        align: 'left'
                    },
                    subtitle: {
                        y: -150,
                        x: -20
                    },
                    yAxis: {
                        labels: {
                            align: 'left',
                            x: 0,
                            y: -3
                        },
                        tickLength: 0,
                        title: {
                            align: 'high',
                            reserveSpace: false,
                            rotation: 0,
                            textAlign: 'left',
                            y: -20
                        }
                    }
                }
            }
        ]
    }
});

function pause(button) {
    button.title = 'play';
    button.className = 'fa fa-play';
    clearTimeout(chart.sequenceTimer);
    chart.sequenceTimer = undefined;
}

function update() {
    chart.update(
        {
            subtitle: {
                text: getSubtitle()
            }
        },
        false,
        false,
        false
    );

    const series = chart.series,
        labels = chart.annotations[0].labels,
        yearIndex = input.value - startYear,
        dataLength = series[0].options.data.length;

    // If slider moved back in time
    if (yearIndex < dataLength - 1) {
        for (let i = 0; i < series.length; i++) {
            const seriesData = series[i].data.slice(0, yearIndex);
            series[i].setData(seriesData, false);
        }
    }

    // If slider moved forward in time
    if (yearIndex > dataLength - 1) {
        const remainingYears = yearIndex - dataLength;
        for (let i = 0; i < series.length; i++) {
            for (let j = input.value - remainingYears; j < input.value; j++) {
                series[i].addPoint([formatRevenue[i][j]], false);
            }
        }
    }

    // Add current year
    for (let i = 0; i < series.length; i++) {
        const newY = formatRevenue[i][input.value];
        series[i].addPoint([newY], false);
    }

    labels.forEach(label => {
        label
            .graphic
            .css({
                opacity: input.value >= label.options.point.x | 0
            });
    });

    chart.redraw();

    input.value = parseInt(input.value, 10) + 1;

    if (input.value > endYear) {
        // Auto-pause
        pause(btn);
    }
}

function play(button) {
    // Reset slider at the end
    if (input.value > endYear) {
        input.value = startYear;
    }
    button.title = 'pause';
    button.className = 'fa fa-pause';
    chart.sequenceTimer = setInterval(function () {
        update();
    }, 700);
}

btn.addEventListener('click', function () {
    if (chart.sequenceTimer) {
        pause(this);
    } else {
        play(this);
    }
});

play(btn);

// Trigger the update on the range bar click.
input.addEventListener('input', update);

[Jsonコード]

{
  "elements": {
    "buttonId": "play-pause-button",
    "inputId": "play-range"
  },
  "constants": {
    "startYear": 1973,
    "endYear": 2021
  },
  "helpers": {
    "arrToAssociative": "function(arr) { const tmp = {}; arr.forEach(item => { tmp[item[0]] = item[1]; }); return tmp; }",
    "getSubtitle": "function() { return `<span style='font-size: 60px'>${input.value}</span>`; }"
  },
  "chart": {
    "container": "container",
    "chart": {
      "events": {
        "load": "function() { const labels = this.annotations[0].labels; labels.find(a => a.options.id === 'vinyl-label').graphic.attr({ rotation: -20 }); labels.find(a => a.options.id === 'cassettes-label').graphic.attr({ rotation: 20 }); }"
      },
      "type": "area",
      "marginTop": 100,
      "animation": {
        "duration": 700,
        "easing": "function(t) { return t; }"
      }
    },
    "title": {
      "text": "Music revenue race chart"
    },
    "subtitle": {
      "text": "<DYNAMIC: getSubtitle()>",
      "floating": true,
      "align": "right",
      "verticalAlign": "middle",
      "x": -100,
      "y": -110
    },
    "data": {
      "csvSource": "<FROM DOM: #csv>",
      "itemDelimiter": "\t",
      "complete": "function(options) { for (let i = 0; i < options.series.length; i++) { formatRevenue[i] = arrToAssociative(options.series[i].data); options.series[i].data = null; } }"
    },
    "xAxis": {
      "allowDecimals": false,
      "min": 1973,
      "max": 2021
    },
    "yAxis": {
      "reversedStacks": false,
      "title": {
        "text": "Revenue in the U.S."
      },
      "labels": {
        "format": "${text} B"
      }
    },
    "tooltip": {
      "split": true,
      "headerFormat": "<span style=\"font-size: 1.2em\">{point.x}</span>",
      "pointFormat": "{series.name}: <b>${point.y:,.1f} B</b> ({point.percentage:.1f}%)",
      "crosshairs": true
    },
    "plotOptions": {
      "area": {
        "stacking": "normal",
        "pointStart": 1973,
        "marker": {
          "enabled": false
        }
      }
    },
    "annotations": [
      {
        "labelOptions": {
          "borderWidth": 0,
          "backgroundColor": null,
          "verticalAlign": "middle",
          "allowOverlap": true,
          "style": {
            "pointerEvents": "none",
            "opacity": 0,
            "transition": "opacity 500ms"
          }
        },
        "labels": [
          {
            "text": "Vinyl",
            "verticalAlign": "top",
            "point": { "x": 1975, "xAxis": 0, "y": 1.45, "yAxis": 0 },
            "style": { "fontSize": "0.8em", "color": "#000" },
            "id": "vinyl-label"
          },
          {
            "text": "LP-EP",
            "point": { "x": 1980, "xAxis": 0, "y": 0.2, "yAxis": 0 },
            "style": { "fontSize": "1.4em", "color": "#ffffff" },
            "id": "lpep-label"
          },
          {
            "text": "Cass",
            "point": { "x": 1987, "xAxis": 0, "y": 2.6, "yAxis": 0 },
            "style": { "fontSize": "1.5em", "color": "#ffffff" },
            "id": "cassettes-label"
          },
          {
            "text": "CD",
            "point": { "x": 1999, "xAxis": 0, "y": 6, "yAxis": 0 },
            "style": { "fontSize": "4em", "color": "#ffffff" },
            "id": "cd-label"
          },
          {
            "text": "DL",
            "point": { "x": 2011, "xAxis": 0, "y": 4, "yAxis": 0 },
            "style": { "fontSize": "1.2em", "color": "#ffffff" },
            "id": "dl-label"
          },
          {
            "text": "Strm",
            "point": { "x": 2018, "xAxis": 0, "y": 5, "yAxis": 0 },
            "style": { "fontSize": "1.5em", "color": "#ffffff" },
            "id": "streams-label"
          }
        ]
      }
    ],
    "responsive": {
      "rules": [
        {
          "condition": { "maxWidth": 500 },
          "chartOptions": {
            "title": { "align": "left" },
            "subtitle": { "y": -150, "x": -20 },
            "yAxis": {
              "labels": { "align": "left", "x": 0, "y": -3 },
              "tickLength": 0,
              "title": {
                "align": "high",
                "reserveSpace": false,
                "rotation": 0,
                "textAlign": "left",
                "y": -20
              }
            }
          }
        }
      ]
    }
  },
  "functions": {
    "pause": "function(button) { ... }",
    "update": "function() { ... }",
    "play": "function(button) { ... }",
    "eventListeners": {
      "buttonClick": "btn.addEventListener('click', ...)",
      "inputChange": "input.addEventListener('input', update)"
    },
    "init": "play(btn);"
  }
}

6.Area Rangeについても、QuickSight環境では再現できないことを確認

スクリーンショット 2025-06-17 093715.png

スクリーンショット 2025-06-17 093757.png

※JavaScriptコードに関数「beforeParse」が使用されており、Json変換時に使用できないためだと思われる

[Javascriptコード]

Highcharts.chart('container', {
    data: {
        csvURL: 'https://cdn.jsdelivr.net/gh/highcharts/highcharts@b99fc27c/samples/data/temp-florida-bergen-2023.csv',
        beforeParse: function (csv) {
            return csv.replace(/\n\n/g, '\n');
        }
    },
    chart: {
        type: 'arearange',
        zooming: {
            type: 'x'
        },
        scrollablePlotArea: {
            minWidth: 600,
            scrollPositionX: 1
        }
    },
    title: {
        text: 'Temperature variation by day',
        align: 'left'
    },
    subtitle: {
        text: 'Source: ' +
            '<a href="https://veret.gfi.uib.no/"' +
            'target="_blank">Universitetet i Bergen</a>',
        align: 'left'
    },
    xAxis: {
        type: 'datetime',
        accessibility: {
            rangeDescription: 'Range: Jan 1st 2023 to Jan 1st 2024.'
        }
    },
    yAxis: {
        title: {
            text: null
        }
    },
    tooltip: {
        crosshairs: true,
        shared: true,
        valueSuffix: '°C',
        xDateFormat: '%A, %b %e'
    },
    legend: {
        enabled: false
    },
    series: [{
        name: 'Temperatures',
        color: {
            linearGradient: {
                x1: 0,
                x2: 0,
                y1: 0,
                y2: 1
            },
            stops: [
                [0, '#ff0000'],
                [1, '#0000ff']
            ]
        }
    }]
});

[Jsonコード]

{
  "data": {
    "csvURL": "https://cdn.jsdelivr.net/gh/highcharts/highcharts@b99fc27c/samples/data/temp-florida-bergen-2023.csv"
  },
  "chart": {
    "type": "arearange",
    "zooming": {
      "type": "x"
    },
    "scrollablePlotArea": {
      "minWidth": 600,
      "scrollPositionX": 1
    }
  },
  "title": {
    "text": "Temperature variation by day",
    "align": "left"
  },
  "subtitle": {
    "text": "Source: <a href=\"https://veret.gfi.uib.no/\" target=\"_blank\">Universitetet i Bergen</a>",
    "align": "left"
  },
  "xAxis": {
    "type": "datetime",
    "accessibility": {
      "rangeDescription": "Range: Jan 1st 2023 to Jan 1st 2024."
    }
  },
  "yAxis": {
    "title": {
      "text": null
    }
  },
  "tooltip": {
    "crosshairs": true,
    "shared": true,
    "valueSuffix": "°C",
    "xDateFormat": "%A, %b %e"
  },
  "legend": {
    "enabled": false
  },
  "series": [
    {
      "name": "Temperatures",
      "color": {
        "linearGradient": {
          "x1": 0,
          "x2": 0,
          "y1": 0,
          "y2": 1
        },
        "stops": [
          [0, "#ff0000"],
          [1, "#0000ff"]
        ]
      }
    }
  ]
}

結論として、Highchartsのビジュアルを活用し、QuickSight上でグラフによっては再現可能。
・再現可能:Dumbbell series、Clockなど
・再現不可:Area Race Chart、Area Rangeなど

6.ヒートマップ

観点: 既存機能では工夫して実装していたものが、「より効率的に」実装できるか​

1.Demo Central環境にて、ヒートマップのコードをコピー
スクリーンショット 2025-06-17 095337.png

[Jsonコード]

{
  "chart": {
    "type": "heatmap",
    "marginTop": 40,
    "marginBottom": 40,
    "plotBorderWidth": 0
  },

  "xAxis": {
    "categories": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
    "min": 0,
    "max": 6,
    "opposite": true, //Position X axis on top side of chart
    "lineWidth": 26, //X axis line width
    "offset": 13, //Shift the axis line up by half the width to avoid overlap with heatmap.
    "lineColor": "rgba(27, 26, 37, 0.2)",
    "gridLineWidth": 0.5,
    "gridLineColor":"Grey",
    "labels": {
      "y": 20, //Shift X axis label down to position over axis line.
      "style": {
        "textTransform": "uppercase",
        "fontWeight": "bold"
      }
    }
  },

  "yAxis": {
    "categories": ["unique", ["getColumn", 1]],//Keeping weeks dynamic
    "min":0,
    "title": {
      "text": ""
    },
    "reversed": true,
    "gridLineWidth": 0.5,
    "gridLineColor":"Grey",
    "labels": {
      "enabled": false
    }
  },

  "colorAxis": {
    "min": 0,
    "stops": [
      [0.2, "lightblue"],
      [0.4, "#CBDFC8"],
      [0.6, "#F3E99E"],
      [0.9, "#23e274"]
    ]
  },

  "legend": {
    "align": "right",
    "layout": "vertical",
    "margin": 10,
    "verticalAlign": "top",
    "y": 55,
    "symbolHeight": 280
  },

  "tooltip": {
    "format": "<b>Date : </b> {point.date:%Y-%m-%d} <br><b>Sales : </b> {point.value:,.2f} "
  },

  "series": [
    {
      "name": "Daily Sales",
      "borderWidth": 0.5,
      "borderColor": "Grey",
      "data":[
        "map", //Shaping data to below specified JSON format.
        ["getColumn", 0, 1, 2, 3, 4],
        {
          "x": ["get", ["item"], 0], //Calendar Day Index
          "y": ["get", ["item"], 1], //Calendar Week Index
          "date": ["get", ["item"], 2], //Order Date
          "dayOfMonth": ["get", ["item"], 3], //Day Of Month
          "value": ["get", ["item"], 4] //Sales
        }
      ],
      "dataLabels": [
        //Position day of month on top left of each heatmap cell.
        {
          "enabled": true,
          "format": "{point.dayOfMonth}",
          "align": "left",
          "verticalAlign": "top",
          "color": "green",
          "backgroundColor": "whitesmoke",
          "padding": 2,
          "x":1,"y":1,
          "style": {
            "textOutline": "none",
            "fontWeight": "normal",
            "fontSize": "12px"
          }
        },
        //Use default label position for Sales
        {
          "enabled": true,
          "format": "{point.value:,.2f}",
          "color": "black",
          "style": {
            "textOutline": "none",
            "fontWeight": "normal",
            "fontSize": "12px"
          }
        }
      ]
    }
  ]

}

2.QuickSight環境で、カレンダーにて再現されることを確認
スクリーンショット 2025-06-17 095608.png

※Jsonコードを修正することで、カラーを設定することも可能
スクリーンショット 2025-06-17 095755.png

  "colorAxis": {
    "min": 0,
    "stops": [
      [0.2, "gray"],
      [0.4, "lightblue"],
      [0.6, "lightgreen"],
      [0.9, "red"]
    ]
  },

結論として、Highchartsのビジュアルを活用し、QuickSight上でヒートマップの実装は可能。

7.その他

ツールチップ

マウスカーソルを合わせたときの表示がカスタマイズ可能
スクリーンショット 2025-06-17 100516.png

tooltip: {
  "pointFormat": "<span style='color:{point.color}'>●</span> <b>{series.name} </b> : {point.y:,.2f>" 
}

// ● 色付きマーク(point.color)
// シリーズ名(例: AMER、Avg Sales)
// yの値(売上)を小数第2位まで表示、3桁区切りで表示(,.2f)

画像表示や文字装飾が可能
スクリーンショット 2025-06-17 100832.png

  "tooltip": {
    "enabled": true,
    "useHTML": true,
    "pointFormat": "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/40px-React-icon.svg.png' style='width:20px; height:20px; vertical-align:middle;'/> <b>{series.name}</b>: ¥{point.y}<br/>"
  }

X軸の特定の値に関連する複数のデータを、1つのツールチップにまとめて表示可能
スクリーンショット 2025-06-17 101033.png

tooltip: {
  "enabled": true,
  "useHTML": false,
  "shared": true,
  "pointFormat": "<b>{series.name}</b>: ¥{point.y}<br/>"
},

テーブル

チャートコードにて、type:tableを追加したところ、テーブルが再現されないことを確認
スクリーンショット 2025-06-17 101348.png

グラフの装飾

グラフの装飾も可能
スクリーンショット 2025-06-17 101631.png

plotOptions: {
  "column": {
    "colorByPoint": true,
    "borderRadius": 5,
    "borderColor": "#333",
    "borderWidth": 1
  }
}

// "colorByPoint": true	
//	各棒(データポイント)に個別の色を自動で割り当てる設定です。
// "borderRadius": 5	
//	棒の 四隅を丸める半径のピクセル数です(ここでは半径5pxの丸み)。
// "borderColor": "#333"	
//	各棒の 枠線の色を指定します(ここでは濃いグレー #333)。
// "borderWidth": 1	
//	枠線の 太さです(ここでは1px)。

データラベル

データラベルの加工も可能
スクリーンショット 2025-06-17 101828.png

plotOptions: {
  "column": {
    "dataLabels": {
      "enabled": true,
      "format": "{point.y:,.0f}円"
    }
  }
}

// "dataLabels": { ... }	
//	棒グラフの各棒の上に値を表示するための設定。
// "enabled": true	
// 	データラベルの表示を有効にする。これを false にすると表示されなくなる。
// "format": "{point.y:,.0f}円"	
//	表示フォーマット。
//	{point.y} は棒の高さの値(Y値)。
//	:,.0f は数値の書式指定で、小数点以下ゼロ桁で、3桁ごとにカンマ区切りを付ける。
//	最後の 円 は単位の文字列として追加。

検証結果 まとめ

No 項目 Highchartsでの実装
1 ガントチャート
2 スモールマルチプル △ ※1
3 ランク
4 コンボグラフ
5 Highchartsのリファレンス △ ※2
6 ヒートマップ
7 その他 △ ※3

※1 工夫すれば実装は可能だが、視認性が落ちるため非推奨
※2 実装可能・不可能なチャートあり
※3 様々なカスタマイズが可能だが、テーブルの再現は不可

活用メリット

以下の点で活用のメリットがあると考えられる。

・グラフの色分けを、計算フィールドを作成することなく実施可能
例:コンボグラフ
・計算フィールドを作成することなく、算術式をふくめて表現可能
例:コンボグラフ

既存のグラフでは表現できなかったビジュアルが可能
例:グラフの組み合わせ・ガントチャート・カレンダー式のヒートマップ

終わりに

今回は、QuickSight環境での、Highchartsのビジュアルについて検証し、活用するメリットについて考えました。既存のQuickSightでは表現できなかったものが、表現できるようになったことは大きなメリットだと思います。また、計算式を作成する工程を省くことができるのも良いなと感じました。しかし、HighchartsのJSコードをJsonに変換する作業が少し面倒くさいと感じており、本機能がGAになった際には、JsonコードもHighcharts Demoで提供されるようになればいい、と思います。

参考記事

株式会社ジールでは、「ITリテラシーがない」「初期費用がかけられない」「親切・丁寧な支援がほしい」「ノーコード・ローコードがよい」「運用・保守の手間をかけられない」などのお客様の声を受けて、オールインワン型データ活用プラットフォーム「ZEUSCloud」を月額利用料にてご提供しております。
ご興味がある方は是非下記のリンクをご覧ください:

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?