Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

custom tooltips with react-chartjs-2 library

Tags:

chart.js

I am having issue with the default tooltip that chartjs provides as I can not add html inside the tooltips. I had been looking at how i can add the html/jsx inside the tooltip. I see an example with using customized tooltips here Chart JS Show HTML in Tooltip. can someone point me an example how to achieve the same with react-chartjs-2 library?

like image 891
varun Avatar asked Jun 29 '17 00:06

varun


2 Answers

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.

like image 111
kumarharsh Avatar answered Oct 18 '22 23:10

kumarharsh


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;
like image 1
Amit Rajbhandari Avatar answered Oct 19 '22 00:10

Amit Rajbhandari