虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

Chart.jsでいい感じの色を自動で割り当てる!

こんにちは、虎の穴ラボのH.Kです。
今回はChart.jsで可変の表示要素に対していい感じの色を割り当てる方法をご紹介します。

この記事で解決する課題

取得したデータによって比較対象に当たる要素数が可変になるようなグラフ(Chart.js)に綺麗に色をつける

f:id:toranoana-lab:20191128190953p:plain
自動で色を割り振った完成形

Chart.jsとは

簡単にグラフを描画できるライブラリです。
パラメータで受け取った値を元にキャンバス要素にグラフを出力してくれます。
円グラフ、折れ線グラフなど多くの形式で表示することが可能です。 また、自動で縦軸や横軸の値を調整してくれるので、値さえ用意すれば簡単に出力できるのが魅力です。
社内で使用しているツールのグラフ表示に使用しています。

www.chartjs.org

解決方法

早速、解決方法ですが、以下の手法を取ることにより解決しました。

  1. HSL色空間を用いる
  2. 要素数から色相の角度を計算し割り振る

ちなみに、自動で色付けするプラグインもありますが、以下の理由で採用を見送りました。

  • Aの男性データ、Aの女性データ、Bの男性データ、Bの女性データのようなものをグラフで表示する際に「A同士」や「男性同士」といった比較が視覚的にできないという問題があったため

ここからは順を追って詳細を解説します。

HSL色空間の指定

そもそもHSL色空間とはなんなのかというと・・・

HSL 色モデルは色相 (H)、彩度 (S)、明度 (L) の各成分によって与えられた色を定義します。

引用:MDN web docs developer.mozilla.org

ざっくりとした説明をすると以下のようになります。

  • 色相:赤とか青とか緑とかがシームレスに並んで円になってる(0° = 360°が赤)
  • 彩度:高いと原色、低いと灰色
  • 明度:高いと白、低いと黒、真ん中が原色

今回は綺麗な色をつけたいので原色系で全て色をつければ良さそうです。
というわけで以下を満たす色を動的に作っていきます。
hsl(n,100%,50%)
※ 色相は円の角度で指定するため 0 <= n <= 360 となります。

要素数から色相の角度を計算

ここは好きな言語を使って0〜360を按分してあげれば良いです。
社内ツールはサーバサイドKotlinで値を作成し、Thymeleafでバインドする仕組みとなっています。
今回はサーバサイドに処理をまとめたため、Kotlinのサンプルコードを示します。
もちろん同じようなコードをJavaScriptで書けばフロント側だけで処理を完結させることも可能です。

// 引数に要素数(columnSize)を渡す
private fun createColors(columnSize: Int): Array<String?> {
  //配列の領域を確保
  val colors: Array<String?> = arrayOfNulls(columnSize)
  val loopSize = columnSize - 1
  for (i in 0..loopSize) {
    var division = (360 / columnSize) * i
    //配列に格納
    colors[i] = "hsl($division,100%,50%)"
  }
  return colors
}

ここで作成した配列をChart.jsの引数に渡してあげれば完成です!
具体的には折れ線グラフの場合、 Chart.data.datasets[n].borderColor = 上記の配列[n]; としてあげます。

と思ったら、ちょっと見づらい・・・

完璧だ!と、意気込んでリリースしたのですが、「ちょっと見づらいよ」と指摘されました。

  • 原因1:黄色系の色が視認しづらい
  • 原因2:隣接要素が似ている色だと混ざりやすい

ということでしたので以下の修正を加えました。

// 引数に要素数(columnSize)を渡す
private fun createColors(columnSize: Int): Array<String?> {
  val exclusionColorRangeFrom = 45 // 割り振らない色の開始位置(黄色系の色が始まる位置)
  val exclusionColorRangeLength = 100 // 割り振らない色分の角度
  //配列の領域を確保
  val colors: Array<String?> = arrayOfNulls(columnSize)
  val loopSize = columnSize - 1
  val maxColorRange = 360 - exclusionColorRangeLength // 使用しない色の範囲分を引いておく
  val halfBaseColor = maxColorRange / 2 // 類似要素分の基準の角度を保持
  for (i in 0..loopSize) {
    var division = (maxColorRange / columnSize) * (i / 2) // 類似要素分、角度を半分にする
    //除外範囲の計算
    if (i % 2 !== 0) {
      division += halfBaseColor // 類似要素なら基準の角度を加算する
    }
    if (division > exclusionColorRangeFrom) { // 使わない色の範囲が始まってた場合
      division += exclusionColorRangeLength // 使わない色の範囲の長さを加算する
    }
  }
  return colors
}

この修正により、みんなに満足してもらえるグラフを描画することができました!

告知

年内最後の採用説明会を12/17(火)に秋葉原で実施します。
Webエンジニアだけでなく、通販サイトのWebディレクター枠もありますので、ご興味のある方はご参加ください。

yumenosora.connpass.com

予定が合わないという方向けにカジュアル面談も実施しています。

news.toranoana.jp