LineChart: add custom tooltip just above a point
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.

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?
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.
The same problem, how set tooltip to top of bar.
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>
);
}
}
Since this is a very common case, shouldn't this be added to the library? Something like:
<Tooltip position="top" />
@xile611
Thx a lot @Odrin. Could you maybe show what your css (ui-chart, ui-chart-tooltip) looks like?
@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
}
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?
@Odrin daysaver, thx m8.
adopted the solution for LineChart aswell.
+1 for positioning tooltips at the top of bars in bar chart 😊
Any updates on this topic?
+1
+1
+1 need update
+1
+1
+1
+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 Perhaps something has changed in api over the past year, I didn't test it on new versions.
+1
+1
+1
+1
Why is this still not a feature?
Using below logic you can achieve individual tool-tip for each dot.
Demo Link: Line chart with custom Tooltip
-
Hide default Tooltip
<Tooltip cursor={false} wrapperStyle={{ display: "none" }} />
-
Add mouse event function on Line (when dot is active)
<Line activeDot={{ onMouseOver: this.showToolTip, onMouseLeave: this.hideToolTip }} .... /> -
custom tooltip div
<div className="ui-chart-tooltip" ref={ref => (this.tooltip = ref)} > <div className="ui-chart-tooltip-content" /> </div> -
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 Will this work for other charts also like barcharts ?
Thanks @Odrin ! Your workaround works very well :+1:
I think there are may workaround now.
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.
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.
For those who came here as were I looking for a solution try:
- upgrading the lib to the latest version;
- pass allowEscapeViewBox prop to your Tooltip;
- use some CSS for tooltip wrapper positioning, e.g. negative margin.