🙄

【QuickSight】Highchartsを使って複数の面グラフと折れ線グラフを出してみる。

2024/12/05に公開

先日、QuickSightでHighchartsが使用できるアップデートがありました。
https://zenn.dev/marche/articles/f9db2ea91e5676

こういうの欲しいなとずっと思っていた、面グラフと折れ線グラフをいっぺんに出してみるようなものを作成しながら触ってみました。一個の方法だと思ってみていただければと。

最終的にはこんなビジュアルです。

パラメータを切り替えるとこんな感じ。

使用したテストデータ

時間 気温 行動
12:00 16.8 1
12:01 17.8 1 スマホ
・・・ ・・・ ・・・ ・・・
12:59 12.8 3 資料作成

という形で、どの時間にどのような行動をしたのかを面グラフで表し、その時の気温を折れ線グラフで表示して、表示する行動の面グラフは週のコントロールで指定する。みたいな感じです。

作成したHighchartsのJSON

{
  "chart": {
    "type": "area"
  },
  "xAxis": {
    "type": "datetime",
    "categories": ["unique", ["getColumn", 0]],
    "tickInterval":5
  },
  "yAxis": {
    "title": {
      "text": "気温"
    },
    "min":10
  },
  "tooltip": {
    "shared": true,
    "pointFormat": "<span style=\"color:{series.color}\">\u25CF</span> {series.name}: <b>{point.y:,.1f}°</b><br/>"
  },
  "series": [
    "+",
    [
      "map",
      ["unique", ["getColumn", 1]],
      {
        "name": ["item"],
        "data": [
          "map",
          [
            "filter",
            ["getColumn", 1, 2, 3, 4],
            ["==", ["get", ["item"], 2], ["get", ["item"], 3]]
          ],
          [
            "case",
            ["!=", ["get", ["item"], 0], ["item", 2]],
            null,
            ["==", ["get", ["item"], 0], ["item", 2]],
            ["get", ["item"], 1]
          ]
        ]
      }
    ],
    [
      "map",
      ["unique", ["getColumn", 3]],
      {
        "type": "line",
        "name": ["item"],
        "data": [
          "map",
          [
            "filter",
            ["getColumn", 3, 5],
            ["==", ["get", ["item"], 0], ["item", 2]]
          ],
          ["get", ["item"], 1]
        ],
        "color": [
          "case",
          ["==", ["item"], 1],
          "#ff6347",
          ["==", ["item"], 2],
          "#4169e1",
          ["==", ["item"], 3],
          "#3cb371"
        ],
        "dataLabels": {
          "enabled": true,
          "format": "{point.y}°",
          "style": {
            "color": "rgba(0, 0, 0, 0.7)",
            "fontSize": "11px",
            "fontWeight": "bold"
          },
          "align": "center",
          "verticalAlign": "middle"
        }
      }
    ]
  ]
}

使用したパラメータと計算フィールド

・パラメータは、weekという数値のデータで、週を変更するために使用しています。

・計算フィールドは3つ
 - params-week
  →現在のパラメータの数値を格納するだけ
 - params-何もしていない
  →パラメータで指定している週の行動のカラムが空白の時に0にして、何か文字列が入っている場合、気温の値を入れる
 - params-行動
  →パラメータで指定している週の行動を表示するようにする

params-week
${week}
params-何もしていない
ifelse(=${week}, ifelse(行動="",0,気温),NULL
)
params-行動
ifelse(=${week},行動,NULL
)

ディメンションはこの順番で追加しています。

作成したJSONの解釈

x軸とかy軸とかの話

"chart": {
    "type": "area"
  },
  "xAxis": {
    "type": "datetime",
    "categories": ["unique", ["getColumn", 0]],
    "tickInterval":5
  },
  "yAxis": {
    "title": {
      "text": "気温"
    },
    "min":10
  },
  "tooltip": {
    "shared": true,
    "pointFormat": "<span style=\"color:{series.color}\">\u25CF</span> {series.name}: <b>{point.y:,.1f}°</b><br/>"
  }

xAxisは、X軸をコントロールしている部分で、どのデータをX軸として指定しているかは、
"categories": ["unique", ["getColumn", 0]]の箇所でやっています。["unique", ["getColumn", 0]]この書き方で、["getColumn", 0]]の値の個別の値を表示するというような感じですかね。
tickIntervalはステップカウントのようなものです。

yAxisは、y軸をコントロールしている部分で、タイトルを気温にして、最小の値を10に設定しています。

tooltipは、ツールチップに何を表示するのか。をやっており、sharedにすることにより、関連するデータをすべて表示するようにしています。

データの表示方法を指定する部分

series全体
"series": [
    "+",
     [
      "map",
      ["unique", ["getColumn", 1]],
      {
        "name": ["item"],
        "data": [
          "map",
          [
            "filter",
            ["getColumn", 1, 2, 3, 4],
            ["==", ["get", ["item"], 2], ["get", ["item"], 3]]
          ],
          [
            "case",
            ["!=", ["get", ["item"], 0], ["item", 2]],
            null,
            ["==", ["get", ["item"], 0], ["item", 2]],
            ["get", ["item"], 1]
          ]
        ]
      }
    ],
    [
      "map",
      ["unique", ["getColumn", 3]],
      {
        "type": "line",
        "name": ["item"],
        "data": [
          "map",
          [
            "filter",
            ["getColumn", 3, 5],
            ["==", ["get", ["item"], 0], ["item", 2]]
          ],
          ["get", ["item"], 1]
        ],
        "color": [
          "case",
          ["==", ["item"], 1],
          "#ff6347",
          ["==", ["item"], 2],
          "#4169e1",
          ["==", ["item"], 3],
          "#3cb371"
        ],
        "dataLabels": {
          "enabled": true,
          "format": "{point.y}°",
          "style": {
            "color": "rgba(0, 0, 0, 0.7)",
            "fontSize": "11px",
            "fontWeight": "bold"
          },
          "align": "center",
          "verticalAlign": "middle"
        }
      }
    ]
  ]

getCloumnの整理

・["getColumn", 0] → 時間
・["getColumn", 1] → params-行動
・["getColumn", 2] → params-何もしていない
・["getColumn", 3] → 週
・["getColumn", 4] → params-week
・["getColumn", 5] → 気温

面グラフの部分

type area
[
  "map",
  ["unique", ["getColumn", 1]],
  {
    "name": ["item"],
    "data": [
      "map",
      [
        "filter",
        ["getColumn", 1, 2, 3, 4],
        ["==", ["get", ["item"], 2], ["get", ["item"], 3]]
      ],
      [
        "case",
        ["!=", ["get", ["item"], 0], ["item", 2]],
        null,
        ["==", ["get", ["item"], 0], ["item", 2]],
        ["get", ["item"], 1]
      ]
    ]
  }
]

上から読んでいくと、まずmapで「行動」の文字列のユニークの数だけ回します。nameは「行動」の文字列から取得して、dataに関しては、filterを使用してパラメータで指定しているweekの値と「週」のカラムが同じものだけフィルタリングして、さらに、caseで、上位の["unique", ["getColumn", 1]]で選択されている、「行動」の文字列と同じ場合は「気温」のデータにしてそのほかをNULLにするといった形です。
["get", ["item"], 2]とかで指定している配列の番号は、["getColumn", 1, 2, 3, 4]の時の配列と対比しています。なので、["get", ["item"], 2]でいうと["getColumn", 3]みたいな感じです。

例えばではありますが、「行動」が"仕事"になっているときのmapで書かれる、面グラフはこんな感じです。

これを「行動」のユニーク分回して表示しているような感じです。

折れ線グラフの部分

type line
[
  "map",
  ["unique", ["getColumn", 3]],
  {
    "type": "line",
    "name": ["item"],
    "data": [
      "map",
      [
        "filter",
        ["getColumn", 3, 5],
        ["==", ["get", ["item"], 0], ["item", 2]]
      ],
      ["get", ["item"], 1]
    ],
    "color": [
      "case",
      ["==", ["item"], 1],
      "#ff6347",
      ["==", ["item"], 2],
      "#4169e1",
      ["==", ["item"], 3],
      "#3cb371"
    ],
    "dataLabels": {
      "enabled": true,
      "format": "{point.y}°",
      "style": {
        "color": "rgba(0, 0, 0, 0.7)",
        "fontSize": "11px",
        "fontWeight": "bold"
      },
      "align": "center",
      "verticalAlign": "middle"
    }
  }
]

上から読んでいくと、まずmapで「週」のユニークの数だけ回します(今回は3回)。typelineを指定して折れ線グラフを描きます。nameを「週」の数にして、dataに関しては、
mapで回している「週」のデータをfilterでフィルタリングしています。それぞれの折れ線グラフを色をcolorで指定していて、caseでそれぞれ分岐させます。dataLabelsで折れ線グラフにデータラベルを貼り付けます。

それぞれを合体

この面グラフと折れ線グラフを、seriesの中に、+でつなぎます。

"series": [
    "+",
    //面グラフのJSONコード
    ,
    //折れ線グラフJSONコード
  ]

さいごに

このアップデート自由度は跳ね上がった感じがしますが、なれるまで一個のビジュアルを作るのに結構時間かかっちゃう。
私の解釈が間違ってたらすみません。あくまでpreview中の一例としてみてください。

Discussion