あいつの日誌β

働きながら旅しています。

chart.js v2.6.0 の tooltip を React.js でカスタマイズして使う

追記

そういえば tootlitp のデザインは下記URL から拝借しています。書き忘れていたので追記します。ちなみにこの CSS にドロップシャドウを追加したらシャレオツな感じになった気がします。

【5パターン】画像を使わず CSS3 のみで作れる吹き出しを作ってみた – Pure CSS3 Balloons | Stronghold Archive

あらすじ

Chart.js の tooltip をカスタマイズする必要があったので、その勉強した結果をメモしておきます。

どんなの?

こんなの

f:id:okamuuu:20170704222524p:plain

準備

react-storybook 上で演習します

create-react-app practice-react-chartjs && cd $_
getstorybook
npm run storybook

open http://localhost:6006 して以下の画面が表示されれば準備完了です

演習開始

chart.js をinstlall

npm install --save chart.js

シンプルな Bar Chart を作成します。

create src/components/bar-chart.js

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { Chart } from 'chart.js'

class BarChart extends Component {

  componentDidMount() {
    const ctx = this.node.getContext('2d')
    const chart = new Chart(ctx, {
      type: "bar",
      data: {
        labels: ["好き", "嫌い", "どちらでもない"],
        datasets: [{
          label: "りんご",
          data: [ 5, 1, 4]
        },
        {
          label: "なし",
          data: [ 8, 1, 1]
        },
        {
          label: "みかん",
          data: [ 2, 1, 7]
        }],
      },
      options: {
        scales: {
          yAxes: [{
            display: true,
            ticks: {
              beginAtZero: true,
              max: 10,
            },
          }],
        }
      }
    })
  }

  render() {
    return (
      <div>
        <canvas ref={(node) => this.node = node}></canvas>
      </div>
    )
  }
}

export default BarChart

edit stories/index.js

import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import BarChart from '../src/components/bar-chart'

storiesOf('Welcome', module)
  .add('BarChart', () => (
    <BarChart />
  )); 

tooltip component を作成する

css in js にします。content に空文字を設定する時は "' '" のようにしましょう。

npm install glamor --save

create src/components/tooltip.js

import React, { Component } from 'react'
import { css } from 'glamor'

let rule = css({
  position: "relative",
  display: "inline-block",
  padding: "0 15px",
  width: "auto",
  minWidth: "115px",
  height: "40px",
  lineHeight: "34px",
  color: "#19283C",
  textAlign: "center",
  background: "#F6F6F6",
  border: "3px solid #19283C",
  boxShadow: "2px 1px 8px 0 rgba(0,0,0,0.3)",
  zIndex: 0,
  ":before": {
    content: "' '",
    position: "absolute",
    bottom: "-8px",
    left: "50%",
    marginLeft: "-9px",
    width: "0px",
    height: "0px",
    borderStyle: "solid",
    borderWidth: "9px 9px 0 9px",
    borderColor: "#F6F6F6 transparent transparent transparent",
    zIndex: 0
  },
  ":after": {
    content: "' '",
    position: "absolute",
    bottom: "-12px",
    left: "50%",
    marginLeft: "-10px",
    width: "0px",
    height: "0px",
    borderStyle: "solid",
    borderWidth: "10px 10px 0 10px",
    borderColor: "#19283C transparent transparent transparent",
    zIndex: -1
  }
})

class Tooltip extends Component {

  componentDidMount() {
  }

  render() {
    return (
      <div className={`${rule}`}>{this.props.children}</div>
    )
  }
}

export default Tooltip

storybook に追加する edit: story/index.js

import React from 'react';
import { storiesOf, action, linkTo } from '@kadira/storybook';
import BarChart from '../src/components/bar-chart'
import Tooltip from '../src/components/tooltip'

storiesOf('Welcome', module)
  .add('BarChart', () => (
    <BarChart />
  ))  
  .add('Tooltip', () => (
    <Tooltip>test</Tooltip>
  ));

Chart.js の tooltip option で挙動を確認

試しに Chart.js に以下のオプションを追加してマウスをグラフに hover させると tooltip のイベントが発生します。ここで取得した情報を先ほどの Tooltip に渡します。

+++ b/src/components/bar-chart.js
@@ -32,7 +32,17 @@ class BarChart extends Component {
               max: 10,
             },
           }],
-        }
+        },
+        tooltips: {
+          enabled: false,
+          mode: 'nearest',
+          position: 'nearest',
+          intersect: false,
+          custom: function(tooltip) {
+            console.log(this._chart, tooltip)
+          }
+        },
       }
     })

tooltip の表示、非表示については tooltip.opacity で判定できます。

tooltip の位置については tooltip.caretXthis.tooltip.caretY で相対的な位置がわかります。 これらと this._chart.canvas.getBoundingClientRect() を組み合わせることで計算することができます。

tooltip の表示位置を position: absolute にして以下の style を操作することで実現させます。

  • el.styleopacity
  • el.style.left
  • el.style.top

tooltip に表示する要素は tooltip.body[0].lines を参照すれば取得できます。

tooltip component をカスタマイズ

というわけで tooltip component に対して以下の props を渡せるようにします。

el.style.opacity el.style.left el.style.top text

動作確認をする際、 react-storybook には Knobs という便利な Addon があるのでこれを使います。が、使い方は割愛します。

edit src/components/tooltip.js

   render() {
+    const { opacity, top, left, children } = this.props;
     return (
-      <div className={`${rule}`}>{this.props.children}</div>
+      <div style={{position: "absolute", opacity, top, left}}>
+        <div className={`${tooltip}`}>{children}</div>
+      </div>
     )

位置情報を計算する

公式サイトに計算方法が載ってあるのでこの計算式を流用します。

http://www.chartjs.org/docs/latest/configuration/tooltip.html

例えば以下のようにします。chart.canvas.getBoundingClientRect() を実行すると canvas の位置がとれるのでそれを使用します。ただし、scroll するとずれるので注意。

正直ここの計算式はどれが正しいのかわかってないのですがたぶんこの計算式でまあまあ良い感じになると思います。

function customTooltip(canvas, tooltipModel) {
  if (tooltipModel.opacity === 0) {
    return {opacity: 0, left: 0, top: 0, text: ""} 
  }
  // const position = chart.canvas.getBoundingClientRect()
  // console.log(position.left, position.right)
  return {
    opacity: 1,
    // left: caret - tooltip の幅 / 2 + potion.left
    // top: caret - tooltip の高さ + potion.top
    left: tooltipModel.caretX - 151 / 2 + 8,
    top: tooltipModel.caretY - tooltipModel.height - 8,
    text: tooltipModel.body[0].lines.join("")
  }
}

あとは BarChart Compnent に onTooltipFire というイベントハンドラを仕込んで受け取った情報を Tooltip に渡せば良いです。

疲れてきたので後は github に上げておきます。興味がある方はご覧頂き、宜しければ⭐️でも付けてやってください

ソースコード

https://github.com/okamuuu/react-chart.js-example

storybook

https://okamuuu.github.io/react-chart.js-example/