recharts icon indicating copy to clipboard operation
recharts copied to clipboard

LineChart: add custom tooltip just above a point

Open veej opened this issue 9 years ago • 70 comments

Hi everyone, in my project I'm trying to achieve something like in the image below, where the tooltip is rendered just above the point it refers to.

image

Looking at the props the custom tooltip receives, coordinates follow the mouse cursor. Is there a way to have coordinates fixed to the point in the graph?

veej avatar Jan 31 '17 09:01 veej

Have same problem with bar chart when trying place tooltip on the top of bar. It's possible to change tooltip's position on bar mouse enter, but not on cursor render.

Odrin avatar Mar 03 '17 13:03 Odrin

The same problem, how set tooltip to top of bar.

vkarpusha avatar Mar 09 '17 08:03 vkarpusha

My workaround with area chart:

import React from 'react';
import {
  AreaChart,
  XAxis,
  YAxis,
  Tooltip,
  ResponsiveContainer,
  Area,
} from 'recharts';

export default class UIAreaChart extends React.PureComponent {
  static propTypes = {
    data: React.PropTypes.array.isRequired
  };

  constructor(props) {
    super(props);

    this.area = null;
    this.tooltip = null;
    this.point = null;

    this.onChartMouseMove = this.onChartMouseMove.bind(this);
    this.onChartMouseLeave = this.onChartMouseLeave.bind(this);
  }

  onChartMouseMove(chart) {
    if (chart.isTooltipActive) {
      let point = this.area.props.points[chart.activeTooltipIndex];

      if (point != this.point) {
        this.point = point;
        this.updateTooltip();
      }
    }
  }

  onChartMouseLeave() {
    this.point = null;
    this.updateTooltip();
  }

  updateTooltip() {
    if (this.point) {
      let x = Math.round(this.point.x);
      let y = Math.round(this.point.y);

      this.tooltip.style.opacity = '1';
      this.tooltip.style.transform = `translate(${x}px, ${y}px)`;
      this.tooltip.childNodes[0].innerHTML = this.point.payload['value'];
    }
    else {
      this.tooltip.style.opacity = '0';
    }
  }

  render() {
    return (
      <div className="ui-chart">
        <ResponsiveContainer width="100%" height="100%">
          <AreaChart data={this.props.data}
                     onMouseMove={this.onChartMouseMove}
                     onMouseLeave={this.onChartMouseLeave}>
            <XAxis dataKey="date" />
            <YAxis />
            <Tooltip />
            <Area ref={ref => this.area = ref} type="monotone" dataKey="value"/>
          </AreaChart>
        </ResponsiveContainer>
        <div className="ui-chart-tooltip" ref={ref => this.tooltip = ref}>
          <div className="ui-chart-tooltip-content"></div>
        </div>
      </div>
    );
  }
}

Odrin avatar Mar 09 '17 08:03 Odrin

Since this is a very common case, shouldn't this be added to the library? Something like:

<Tooltip position="top" />

@xile611

matheusml avatar May 10 '17 13:05 matheusml

Thx a lot @Odrin. Could you maybe show what your css (ui-chart, ui-chart-tooltip) looks like?

benjazehr avatar Jun 02 '17 15:06 benjazehr

@angelozehr nothing special:

.ui-chart {
  position: relative;
  ...
}
.ui-chart-tooltip {
  pointer-events: none;
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 10;
  ... and lots of beautifying styles
}

Odrin avatar Jun 05 '17 09:06 Odrin

Any updates on position="top"? I am still confused regarding how to show tooltip on top of a data point? Could you please help me?

shailajashah31 avatar Sep 21 '17 13:09 shailajashah31

@Odrin daysaver, thx m8.

adopted the solution for LineChart aswell.

maxefi avatar Feb 06 '18 16:02 maxefi

+1 for positioning tooltips at the top of bars in bar chart 😊

christinajensen avatar Feb 28 '18 19:02 christinajensen

Any updates on this topic?

Lelith avatar Apr 03 '18 06:04 Lelith

+1

nathmack avatar Apr 18 '18 10:04 nathmack

+1

valaz avatar Apr 26 '18 12:04 valaz

+1 need update

alonecuzzo avatar Apr 26 '18 21:04 alonecuzzo

+1

revanthbomma avatar May 01 '18 16:05 revanthbomma

+1

solnikita avatar May 03 '18 16:05 solnikita

+1

nucab avatar Jun 26 '18 06:06 nucab

+1 @Odrin getting undefined from this: let point = this.area.props.points[chart.activeTooltipIndex];

Seems like there is no points-key in area-props. Am I missing something?

jesperlandmer avatar Jun 28 '18 14:06 jesperlandmer

@jesperlandmer Perhaps something has changed in api over the past year, I didn't test it on new versions.

Odrin avatar Jun 28 '18 14:06 Odrin

+1

UpsideDownRide avatar Jul 30 '18 12:07 UpsideDownRide

+1

akrstic01 avatar Sep 24 '18 13:09 akrstic01

+1

erosenberg avatar Feb 25 '19 20:02 erosenberg

+1

natasha-tullos avatar Apr 17 '19 16:04 natasha-tullos

Why is this still not a feature?

erosenberg avatar Apr 25 '19 20:04 erosenberg

Using below logic you can achieve individual tool-tip for each dot.

Demo Link: Line chart with custom Tooltip

  1. Hide default Tooltip

    <Tooltip cursor={false} wrapperStyle={{ display: "none" }} />

  2. Add mouse event function on Line (when dot is active)

     <Line
           activeDot={{
             onMouseOver: this.showToolTip,
             onMouseLeave: this.hideToolTip
           }}
          ....
         />
    
  3. custom tooltip div

       <div className="ui-chart-tooltip" ref={ref => (this.tooltip = ref)} >
           <div className="ui-chart-tooltip-content" />
       </div>
    
  4. showToolTip and hideTooltip Function

          showToolTip = (e) => {
           let x = Math.round(e.cx);
           let y = Math.round(e.cy);
           this.tooltip.style.opacity = "1";
           this.tooltip.childNode[0].innerHTML = e.payload["value"];
    
           };
    
           hideTooltip = e => {
           this.tooltip.style.opacity = "0";
           };
    

rajatkanthaliya12 avatar May 09 '19 11:05 rajatkanthaliya12

@rajatkanthaliya12 Will this work for other charts also like barcharts ?

senelithperera avatar Oct 28 '19 10:10 senelithperera

Thanks @Odrin ! Your workaround works very well :+1:

mabzzz avatar Feb 18 '20 14:02 mabzzz

I think there are may workaround now.

xile611 avatar Mar 17 '20 02:03 xile611

THIS IS THE BEST WAY TO DO IT:

import React, { Component } from 'react';
import {
  ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
} from 'recharts';
import { colors } from '../pages/_app';
import moment from 'moment';

export default class Linechart extends Component {
  constructor(props){
		super(props)
  }
  state = {
    index: 0
  }

  render() {
    const data = this.props.data;
    const CustomTooltip = ({ active, payload, label }) => {
      let index = this.state.index;
      if (active) {
        return (
          <div className="custom-tooltip">
            <p className="label">{`${label} : ${payload[index].value}`}</p>
            <p className="intro">{`${label} : ${payload[index].name}`}</p>
            <p className="desc">Anything you want can be displayed here.</p>
          </div>
        );
      }
      return null;
    };

    return (
        <div style={{ width:'100%', height:'100%' }}>
        <ResponsiveContainer>
            <LineChart
              width={500}
              height={300}
              data={data}
              margin={{
                top: 5, right: 30, left: 0, bottom: 5,
              }}
            >
                <CartesianGrid strokeDasharray="3 3" />
                <XAxis tick={{fontSize: 10}} dataKey="date" tickFormatter={val => moment(val).format("Do MMM")} />
                <YAxis tick={{fontSize: 10}} />
                <Tooltip content={<CustomTooltip />} />
                <Legend wrapperStyle={{fontSize:12, width:'100%'}} />
                <Line activeDot={{
                  onMouseOver: ()=>this.setState({index:0})
                }} type="monotone" dataKey="lease" name="Lease to Own" stroke='#00ACC0' activeDot={{ r: 8 }} />
                <Line activeDot={{
                  onMouseOver: ()=>this.setState({index:1})
                }} type="monotone" dataKey="mortgage" name="Mortgages" stroke='#F9A825' />
                <Line activeDot={{
                  onMouseOver: ()=>this.setState({index:2})
                }} type="monotone" dataKey="construction" name="Construction Finance" stroke='#5BBC9C' />
                <Line activeDot={{
                  onMouseOver: ()=>this.setState({index:3})
                }} type="monotone" dataKey="investment" name="Investments" stroke='#E64A19' />
            </LineChart>
        </ResponsiveContainer>
        </div>
    );
  }
}

The trick here is to render a custom tooltip, and display data pertaining to the hovered line on the tooltip. For the tooltip: <Tooltip content={<CustomTooltip />} /> For each line add:

activeDot={{
onMouseOver: ()=>this.setState({index:1})
}}

To make it like:

<Line activeDot={{
onMouseOver: ()=>this.setState({index:1})
}} type="monotone" dataKey="mortgage" name="Mortgages" stroke='#F9A825' />

Add your custom tooltip:

const CustomTooltip = ({ active, payload, label }) => {
      let index = this.state.index;
      if (active) {
        return (
          <div className="custom-tooltip">
            <p className="label">{`${label} : ${payload[index].value}`}</p>
            <p className="intro">{`${label} : ${payload[index].name}`}</p>
            <p className="desc">Anything you want can be displayed here.</p>
          </div>
        );
      }
      return null;
};

The custom tooltip will display data of the nth line: where n starts from 0 (index number of line). For my case, I have 4 lines so the index numbers of the lines start from 0 to 3. The trick is to update the index of the custom tooltip to match the index of the line being hovered, And for this, I have used the 'index' state.

BossBele avatar May 13 '20 15:05 BossBele

THIS IS THE BEST WAY TO DO IT:

Nope, it's not the best way to do it. The best way to achieve this is built-in lib implementation without hacks from the developer side.

MaxDonchenko avatar May 20 '20 12:05 MaxDonchenko

For those who came here as were I looking for a solution try:

  1. upgrading the lib to the latest version;
  2. pass allowEscapeViewBox prop to your Tooltip;
  3. use some CSS for tooltip wrapper positioning, e.g. negative margin.

MaxDonchenko avatar May 20 '20 14:05 MaxDonchenko