Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to position tooltip on the top of a barchart in rechart?

Problem: I have created a bar chart with a custom tooltip. Now What I need is to position tooltip on top of the bar not within the area of the chart. Like in this picture.

enter image description here

This is how It looks like now.

enter image description here

Here I am providing how I organize my code.

import React, { Component } from "react";
import {
  BarChart,
  Tooltip,
  Bar,
  Legend,
  ResponsiveContainer,
  Cell
} from "recharts";

import { Card, CardTitle, CardBody } from "reactstrap";

import "./SessionDuration.css";

const colors = ["#26a0a7", "#79d69f", "#f9ec86", "#ec983d"];

const data = [
  {
    name: "Page A",
    pv: 2400,
    amt: 2400
  },
  {
    name: "Page B",
    pv: 1398,
    amt: 2210
  },
  {
    name: "Page C",
    pv: 9800,
    amt: 2290
  },
  {
    name: "Page D",
    pv: 3908,
    amt: 2000
  }
];

const getTitleOfTooltip = (label) =>{
  if (label ===  0) {
    return "<=5 min";
  }
  if (label === 1) {
    return "5-30 min";
  }
  if (label === 2) {
    return "30-60 min";
  }
  if (label === 3) {
    return ">60";
  }
}
const getIntroOfPage = label => {
  if (label ===  0) {
    return "Page A is about men's clothing";
  }
  if (label === 1) {
    return "Page B is about women's dress";
  }
  if (label === 2) {
    return "Page C is about women's bag";
  }
  if (label === 3) {
    return "Page D is about household goods";
  }
};

class SessionDuration extends Component {
  render() {
    return (
      <Card className="session-duration-card">
        <CardTitle className="session-duration-card-header">
          Session Duration
        </CardTitle>
        <CardBody>
          <ResponsiveContainer width="100%" height="100%" aspect={4.0 / 5.5}>
            <BarChart
              data={data}
              margin={{
                top: 3,
                right: 5,
                left: 5,
                bottom: 13
              }}
              barGap={10}
            >
              <Tooltip
                coordinate={{ x: 0, y: 150 }}
                content={<CustomTooltip />}
              />

              <Bar dataKey="pv" fill="#8884d8">
                {data.map((entry, index) => (
                  <Cell key={`cell-${index + 1}`} fill={colors[index]} />
                ))}
              </Bar>
            </BarChart>
          </ResponsiveContainer>
        </CardBody>
      </Card>
    );
  }
}

export default SessionDuration;

const CustomTooltip = ({ active, payload, label }) => {
  if (active) {
    return (
      <div className="session-duration-tooltip">
        <p className="session-duration-tooltip-label">{getTitleOfTooltip(label)}</p>
        <p className="session-duration-tooltip-intro">
          {getIntroOfPage(label)}
        </p>
        <p className="session-duration-tooltip-desc">
          Anything you want can be displayed here.
        </p>
      </div>
    );
  }

  return null;
};

Here I am providing my CSS file.

@media only screen and (min-width: 1024px) {
  .session-duration-card {
    margin-top: 14.5%;
    margin-left: -44%;
    width: 190% !important;
    border-radius: none !important;
    height: 86%

  }

  .session-duration-card-header {
    font-weight: 700;
    text-align: left;
    padding-left: 6%
  }

  .session-duration-tooltip {
    width: 210%;
  }

  .session-duration-tooltip-label {
    font-weight: 700;
    font-size: 11px;
  }
}

@media only screen and (min-width: 308px) and (max-width: 1024px) {
  .session-duration-card {
    margin-top: 5%;
    margin-left: 3.2%;
    width: 94.5%;
    border-radius: none !important;
  }

  .session-duration-card-header {
    font-weight: 700;
    text-align: center;
  }
}

@media only screen and (min-width: 1666px) {
  .session-duration-card {
    margin-top: 11%;
    margin-left: -13%;
    width: 190% !important;
    height: 97%;
    border-radius: none !important;
  }

  .session-duration-card-header {
    font-weight: 700;
    text-align: center;
  }
}

I tried a lot to find a solution for my problem but I was unable to get it done can someone help me by modifying my code to get this work done. Thank you very much.

like image 956
dwp Avatar asked Mar 23 '19 03:03

dwp


1 Answers

To solve this problem, will need to get the height and width of each bar and tooltip.

  1. Need two functions onMouseMove and onMouseOut to get the height and width of the Bar component.
  2. Using useState to get and keep the height and width to the state, also place extra boolean to the state, to handle when the mouse is outside of the Bar. (So, to function we add object with two paramets data and boolean ).
  3. In the Tooltip component, set the position object for static values X, Y and using the state values, set the height and width.
  4. Using useEffect, to find the .recharts-tooltip-wrapper class, to define the height and width of the DOM element.
  5. Set styles for .recharts-tooltip-wrapper. (Rechart. js in .recharts-tooltip-wrapper has its own generated style, we need to keep some static style of the .recharts-tooltip-wrapper and add our).

Edit dazziling-code

SessionDuration.js

import React, { useState, useEffect } from 'react';
import { BarChart, Tooltip, Bar, ResponsiveContainer, Cell } from 'recharts';

import { Card, CardTitle, CardBody } from 'reactstrap';

import './SessionDuration.css';

const colors = ['#26a0a7', '#79d69f', '#f9ec86', '#ec983d'];

const data = [
  {
    name: 'Page A',
    pv: 2400,
    amt: 2400,
  },
  {
    name: 'Page B',
    pv: 1398,
    amt: 2210,
  },
  {
    name: 'Page C',
    pv: 9800,
    amt: 2290,
  },
  {
    name: 'Page D',
    pv: 3908,
    amt: 2000,
  },
];

const getTitleOfTooltip = label => {
  if (label === 0) {
    return '<=5 min';
  }
  if (label === 1) {
    return '5-30 min';
  }
  if (label === 2) {
    return '30-60 min';
  }
  if (label === 3) {
    return '>60';
  }
};
const getIntroOfPage = label => {
  if (label === 0) {
    return "Page A is about men's clothing";
  }
  if (label === 1) {
    return "Page B is about women's dress";
  }
  if (label === 2) {
    return "Page C is about women's bag";
  }
  if (label === 3) {
    return 'Page D is about household goods';
  }
};

export const SessionDuration = () => {
  const [position, setPosition] = useState(null);

  useEffect(() => {
    const tooltip = document.querySelector('.recharts-tooltip-wrapper');
    if (!tooltip) return;

    // Init tooltip values
    const tooltipHeight = tooltip.getBoundingClientRect().height;
    const tooltipWidth = tooltip.getBoundingClientRect().width;
    const spaceForLittleTriangle = 10;

    // Rewrite tooltip styles
    tooltip.style = `
      transform: translate(${position?.data.x}px, ${position?.data.y}px);
      pointer-events: none;  position: absolute;
      top: -${tooltipHeight + spaceForLittleTriangle}px;
      left: -${tooltipWidth / 2 - position?.data.width / 2}px;
      opacity: ${position?.show ? '1' : 0};
      transition: all 400ms ease 0s;
    `;
  }, [position]);

  return (
    <>
      <Card className="session-duration-card">
        <CardTitle className="session-duration-card-header">
          Session Duration
        </CardTitle>
        <CardBody>
          <ResponsiveContainer width="100%" height="100%" aspect={4.0 / 5.5}>
            <BarChart
              data={data}
              margin={{
                top: 100, // for tooltip visibility
                right: 5,
                left: 5,
                bottom: 13,
              }}
              barGap={10}
            >
              <Tooltip
                cursor={false} // hide hover effect 'grey rectangle'
                position={{
                  // Static position
                  x: position?.data.x ?? 0,
                  y: position?.data.y ?? 0,
                }}
                content={<CustomTooltip />}
              />

              <Bar
                dataKey="pv"
                fill="#8884d8"
                // Handlers
                onMouseMove={data => setPosition({ data: data, show: true })}
                onMouseOut={data => setPosition({ data: data, show: false })}
              >
                {data.map((entry, index) => (
                  <Cell key={`cell-${index + 1}`} fill={colors[index]} />
                ))}
              </Bar>
            </BarChart>
          </ResponsiveContainer>
        </CardBody>
      </Card>
    </>
  );
};

export default SessionDuration;

const CustomTooltip = ({ active, payload, label }) => {
  if (active) {
    return (
      <div className="session-duration-tooltip">
        <p className="session-duration-tooltip-label">
          {getTitleOfTooltip(label)}
        </p>
        <p className="session-duration-tooltip-intro">
          {getIntroOfPage(label)}
        </p>
        <p className="session-duration-tooltip-desc">
          Anything you want can be displayed here.
        </p>
      </div>
    );
  }

  return null;
};

SessionDuration.css

:root {
  --bg-color: hsl(270 3% 29% / 0.9);
}

.session-duration-tooltip {
  max-width: 250px;
  position: relative;
  padding: 0.5em;
  border-radius: 5px;
  background-color: var(--bg-color);
  color: aliceblue;
}

.session-duration-tooltip::after {
  content: '';
  height: 0;
  width: 0;
  position: absolute;
  bottom: -10px;
  left: 50%;
  transform: translateX(-50%);
  border-style: solid;
  border-width: 10px 10px 0 10px;
  border-color: var(--bg-color) transparent transparent transparent;
}
like image 173
Anton Avatar answered Oct 13 '22 09:10

Anton