[Chartjs]-Custom tooltips with react-chartjs-2 library

8๐Ÿ‘

You have to use the custom callback in the tooltip property to define your own positioning and set the hovered dataset in the component state

state = {
  top: 0,
  left: 0,
  date: '',
  value: 0,
};

_chartRef = React.createRef();

setPositionAndData = (top, left, date, value) => {
  this.setState({top, left, date, value});
};

render() {
  chartOptions = {
    "tooltips": {
      "enabled": false,
      "mode": "x",
      "intersect": false,
      "custom": (tooltipModel) => {
        // if chart is not defined, return early
        chart = this._chartRef.current;
        if (!chart) {
          return;
        }

        // hide the tooltip when chartjs determines you've hovered out
        if (tooltipModel.opacity === 0) {
          this.hide();
          return;
        }

        const position = chart.chartInstance.canvas.getBoundingClientRect();

        // assuming your tooltip is `position: fixed`
        // set position of tooltip
        const left = position.left + tooltipModel.caretX;
        const top = position.top + tooltipModel.caretY;

        // set values for display of data in the tooltip
        const date = tooltipModel.dataPoints[0].xLabel;
        const value = tooltipModel.dataPoints[0].yLabel;

        this.setPositionAndData({top, left, date, value});
      },
    }
  }

  return (
    <div>
      <Line data={data} options={chartOptions} ref={this._chartRef} />
      { this.state.showTooltip
        ? <Tooltip style={{top: this.state.top, left: this.state.left}}>
            <div>Date: {this.state.date}</div>
            <div>Value: {this.state.value}</div>
          </Tooltip>
        : null
      }
    </div>
  );
}

You can use the tooltips supplied by React Popper Tooltip or roll your own โ€“ pass the top and left to the tooltip for positioning, and the date and value (in my example) should be used to show the data in the tooltip.

5๐Ÿ‘

If anyone looking answer customization of tooltip and gradient chart here is my code:

My Packages:

"react": "^17.0.2"
"chart.js": "^3.7.1"
"react-chartjs-2": "^4.1.0"
"tailwindcss": "^3.0.23"

ToopTip Component:

import React, { memo } from "react";
import { monetarySuffix } from "@src/helpers/util";
// tooltip.js
const GraphTooltip = ({ data, position, visibility }) => {
  return (
    <div
      className={`absolute px-4 py-3.5 rounded-lg shadow-lg bg-chart-label-gradient text-white overflow-hidden transition-all duration-300 hover:!visible
      ${visibility ? "visible" : "invisible"}
        `}
      style={{
        top: position?.top,
        left: position?.left,
      }}
    >
      {data && (
        <>
          <h5 className="w-full mb-1.5 block text-[12px] uppercase">
            {data.title}
          </h5>

          <ul className="divide-y divide-gray-100/60">
            {data.dataPoints.map((val, index) => {
              return (
                <li
                  key={index}
                  className="m-0 py-1.5 text-base font-rubik font-medium text-left capitalize last:pb-0"
                >
                  {val?.dataset.label}
                  {":"} {monetarySuffix(val?.raw)}
                </li>
              );
            })}
          </ul>
        </>
      )}
    </div>
  );
};

export default memo(GraphTooltip);

Chart Component

import React, { useMemo, useState, useRef, useCallback } from 'react';
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
  Filler,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import GraphTooltip from './chart-tooltip';

ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
  Filler
);

const GradientChart = () => {
  const [tooltipVisible, setTooltipVisible] = useState(false);
  const [tooltipData, setTooltipData] = useState(null);
  const [tooltipPos, setTooltipPos] = useState(null);

  const chartRef = useRef(null);

  const data = {
    labels: [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'Agust',
      'September',
      'October',
      'November',
      'December',
    ],
    datasets: [
      {
        fill: true,
        backgroundColor: (context) => {
          const chart = context.chart;
          const { ctx, chartArea } = chart;

          if (!chartArea) {
            return;
          }
          return createGradient(
            ctx,
            chartArea,
            '#F46079',
            '#F46079',
            'rgba(255,255,255,0)'
          );
        },
        borderColor: '#F46079',
        lineTension: 0.4,
        pointRadius: 5,
        pointHoverRadius: 10,
        pointBackgroundColor: '#FE5670',
        pointBorderColor: '#ffffff',
        pointBorderWidth: 1.5,
        label: 'Sales',
        data: [
          4500, 2800, 4400, 2800, 3000, 2500, 3500, 2800, 3000, 4000, 2600,
          3000,
        ],
      },
      {
        fill: true,
        backgroundColor: (context) => {
          const chart = context.chart;
          const { ctx, chartArea } = chart;

          if (!chartArea) {
            return;
          }
          return createGradient(
            ctx,
            chartArea,
            '#2f4b7c',
            '#2f4b7c',
            'rgba(255,255,255,0)'
          );
        },
        borderColor: '#2f4b7c',
        lineTension: 0.4,
        pointRadius: 5,
        pointHoverRadius: 10,
        pointBackgroundColor: '#FE5670',
        pointBorderColor: '#ffffff',
        pointBorderWidth: 1.5,
        label: 'Commision',
        data: [
          5000, 3500, 3000, 5500, 5000, 3500, 6000, 1500, 2000, 1800, 1500,
          2800,
        ],
      },
      {
        fill: true,
        backgroundColor: (context) => {
          const chart = context.chart;
          const { ctx, chartArea } = chart;

          if (!chartArea) {
            return;
          }
          return createGradient(
            ctx,
            chartArea,
            '#665191',
            '#665191',
            'rgba(255,255,255,0)'
          );
        },
        borderColor: '#665191',
        lineTension: 0.4,
        pointRadius: 5,
        pointHoverRadius: 10,
        pointBackgroundColor: '#FE5670',
        pointBorderColor: '#ffffff',
        pointBorderWidth: 1.5,
        label: 'Transaction',
        data: [
          1000, 2000, 1500, 2000, 1800, 1500, 2800, 2800, 3000, 2500, 3500,
          2800,
        ],
      },
    ],
  };

  const createGradient = (ctx, chartArea, c1, c2, c3) => {
    const chartWidth = chartArea.right - chartArea.left;
    const chartHeight = chartArea.bottom - chartArea.top;
    const gradient = '';
    const width = '';
    const height = '';
    if (!gradient || width !== chartWidth || height !== chartHeight) {
      width = chartWidth;
      height = chartHeight;
      gradient = ctx.createLinearGradient(
        0,
        chartArea.bottom,
        0,
        chartArea.top
      );
      gradient.addColorStop(0, c3);
      gradient.addColorStop(0.5, c2);
      gradient.addColorStop(1, c1);
    }
    return gradient;
  };

  const customTooltip = useCallback((context) => {
    if (context.tooltip.opacity == 0) {
      // hide tooltip visibilty
      setTooltipVisible(false);
      return;
    }

    const chart = chartRef.current;
    const canvas = chart.canvas;
    if (canvas) {
      // enable tooltip visibilty
      setTooltipVisible(true);

      // set position of tooltip
      const left = context.tooltip.x;
      const top = context.tooltip.y;

      // handle tooltip multiple rerender
      if (tooltipPos?.top != top) {
        setTooltipPos({ top: top, left: left });
        setTooltipData(context.tooltip);
      }
    }
  });

  const options = useMemo(() => ({
    responsive: true,
    scales: {
      y: {
        grid: {
          display: false,
        },
      },
    },
    interaction: {
      mode: 'index',
      intersect: false,
    },
    plugins: {
      legend: {
        display: false,
      },
      title: {
        display: false,
      },
      tooltip: {
        enabled: false,
        position: 'nearest',
        external: customTooltip,
      },
    },
  }));

  return (
    <div className="grad-chart-wrapper w-full relative">
      <Line options={{ ...options }} data={data} ref={chartRef} />

      {tooltipPos && (
        <GraphTooltip
          data={tooltipData}
          position={tooltipPos}
          visibility={tooltipVisible}
        />
      )}
    </div>
  );
};

export default GradientChart;

0๐Ÿ‘

Remember to think in React here (which is not always easy). Use the mycustomtooltipfunction to set state in your React class (specifically, add the tooltip that is passed to mycustometooltipfunction to the state โ€“ this will result in render being invoked. Now in the render function of your class, check if that state exists and add the JSX for your tooltip.

class MyChart extends Component {

    constructor(props) {
        super(props);
        this.state = {
            tooltip : undefined
        };
    }

    showTooltip = (tooltip) => {
        if (tooltip.opacity === 0) {
            this.setState({
                tooltip : undefined
            });
        } else {
            this.setState({ 
                tooltip
            });
        }
     }

     render() {
         const { tooltip } = this.state;

         let options = {
             ...
             tooltips : {
                 enabled : false,
                 custom : this.showTooltip,
             }
         }

         let myTooltip;
         if (tooltip) {
           // MAKE YOUR TOOLTIP HERE - using the tooltip from this.state.tooltip, or even have a tooltip JSX class
         }

         return (
             <div>
                 {myTooltip}
                 <Line ref="mygraph" key={graphKey} data={data} options={options} height={graphHeight} width={graphWidth}/>
             </div>
         )
    }
}

`

0๐Ÿ‘

this.chart.chart_instance.canvas.getBoundingClientRect();

If you get some error with chart_instance you should check parent of element value.

Try this:

this.chart.chartInstance.canvas.getBoundingClientRect();

Leave a comment