Search code examples
reactjsd3.jschartstooltipreact-component

how to add tooltip in react d3 v4 bar chart


I have to add the tooltip on react d3 v4 bar chart on mouseover.I have tried customized function mentioned below,

onMouseOverHandler(d){
   var tooltip = d3Select("body").append("div")   
    .attr("class", "tooltip")               
    .style("opacity", 0);

    tooltip.transition().duration(200).style("opacity", .9);      
    tooltip.html(d)  
    .style("left", d3Select(this).attr("cx") + "px")     
    .style("top", d3Select(this).attr("cy") + "px");

but it's not working. someone can you help me for this one.

Thanks, Arun S


Solution

  • I added a tooltip to the example you linked to in your comment to this fork of the original GitHub repository.

    I created a Tooltip component. Of course, keep in mind that this isn't necessarily the "right" or only way to add a tooltip to a React application that uses D3.

    I went through the following steps:

    1. Created state in the Chart component that tracks the data for which bar, if any, is currently hovered

    2. Created onMouseOver and onMouseOut events in the Bars component to determine which bar has just been hovered or left and pass that up to the Chart component to set the new state

    3. Passed the state from the Chart component back down to a Tooltip component I created

    The Tooltip component looks like this:

    export default ({hoveredBar, scales}) => {
      const { xScale, yScale } = scales
      const styles = {
        left: `${xScale(hoveredBar.title) - 30}px`,
        top: `${yScale(hoveredBar.value)}px`
      }
    
      return (
        <div className="Tooltip" style={styles}>
          <table>
            <thead>
              <tr>
                <th colSpan="2">{hoveredBar.title}</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td colSpan="1">Bodies</td>
                <td colSpan="1">{hoveredBar.value}</td>
              </tr>
              <tr>
                <td colSpan="1">Year</td>
                <td colSpan="1">{hoveredBar.year}</td>
              </tr>
            </tbody>
          </table>
        </div>
      )
    }
    

    I used it in the Chart component, and tracked the currently hovered bar as state:

    class Chart extends Component {
      constructor(props) {
        super(props)
        this.xScale = scaleBand()
        this.yScale = scaleLinear()
    
        this.state = {
          hoveredBar: null
        }
      }
    
      render() {
        const margins = { top: 50, right: 20, bottom: 100, left: 60 }
        const svgDimensions = {
          width: Math.max(this.props.parentWidth, 300),
          height: 500
        }
    
        const maxValue = Math.max(...data.map(d => d.value))
    
        const xScale = this.xScale
          .padding(0.5)
          .domain(data.map(d => d.title))
          .range([margins.left, svgDimensions.width - margins.right])
    
        const yScale = this.yScale
          .domain([0, maxValue])
          .range([svgDimensions.height - margins.bottom, margins.top])
    
        return (
          <div className="Chart">
            <svg width={svgDimensions.width} height={svgDimensions.height}>
              <Axes
                scales={{ xScale, yScale }}
                margins={margins}
                svgDimensions={svgDimensions}
              />
              <Bars
                scales={{ xScale, yScale }}
                margins={margins}
                data={data}
                maxValue={maxValue}
                svgDimensions={svgDimensions}
                onMouseOverCallback={datum => this.setState({hoveredBar: datum})}
                onMouseOutCallback={datum => this.setState({hoveredBar: null})}
              />
            </svg>
            { this.state.hoveredBar ?
              <Tooltip
                hoveredBar={this.state.hoveredBar}
                scales={{ xScale, yScale }}
              /> :
              null
            }
          </div>
        )
      }
    }
    

    And I set the onMouseOver and onMouseOut events in the Bars component:

    export default class Bars extends Component {
      constructor(props) {
        super(props)
    
        this.colorScale = scaleLinear()
          .domain([0, this.props.maxValue])
          .range(['#F3E5F5', '#7B1FA2'])
          .interpolate(interpolateLab)
      }
    
      render() {
        const { scales, margins, data, svgDimensions } = this.props
        const { xScale, yScale } = scales
        const { height } = svgDimensions
    
        const bars = (
          data.map(datum =>
            <rect
              key={datum.title}
              x={xScale(datum.title)}
              y={yScale(datum.value)}
              height={height - margins.bottom - scales.yScale(datum.value)}
              width={xScale.bandwidth()}
              fill={this.colorScale(datum.value)}
              onMouseOver={() => this.props.onMouseOverCallback(datum)}
              onMouseOut={() => this.props.onMouseOutCallback(null)}
            />,
          )
        )
    
        return (
          <g>{bars}</g>
        )
      }
    }