react-vis icon indicating copy to clipboard operation
react-vis copied to clipboard

Hint / Tooltip for LineSeries charts

Open JamesPeiris opened this issue 6 years ago • 6 comments

Hey folks 👋 Really liking this library!

I had a look in Storybook, then in the docs / issues and can't find any reference to hints or tooltips (on hover) over Line Series plots.

I'd prefer not to add a legend as the chart is intended to be used for simple comparisons, but it would be great if on hover over a plotted line, the name for that dataset could appear.

Maybe something along the lines of a name prop like:

<LineSeries name="Apples" data={data} color="lime" />
<LineSeries name="Oranges" data={data} color="red" />

Which would surface something like: image

Is this something that's possible/desired by others?

Thanks in advance!

JamesPeiris avatar May 06 '19 14:05 JamesPeiris

I would like help on this too. Just like you do, I have multiple LineSeries. I'm currently using the onNearestX function to display the Hint, but the problem is it gets called regardless of where my mouse is on the graph. Which means it displays hints for all graphs

<LineSeries
            color={seriesColors['series1']}
            onNearestX={(value, { index }) => this.setState({ hoverValue1: value, index })}
            data={[ { x: 1, y: 10 }, { x: 2, y: 5 }, { x: 3, y: 15 } ]}
/>

<LineSeries
            color={seriesColors['series2']}
            onNearestX={(value, { index }) => this.setState({ hoverValue2: value, index })}
            onSeriesMouseOut={() => this.setState({ hoverValue2: null })}
            data={[ { x: 1, y: 20 }, { x: 2, y: 10 }, { x: 3, y: 30 } ]}
/>

{this.state.hoverValue1 && (
<Hint value={this.state.hoverValue1}>
              <div background={seriesColors['series1']}>
                <div>Name 1:</div>
                <div>${this.state.hoverValue1.y}</div>
              </div>
</Hint>
)}

{this.state.hoverValue2 && (
<Hint value={this.state.hoverValue2}>
              <div background={seriesColors['series2']}>
                <div>Name 2:</div>
                <div>${this.state.hoverValue2.y}</div>
              </div>
</Hint>
)}

thesubway avatar May 08 '19 19:05 thesubway

export default class Analytics extends React.Component<Props, State> { state = { drawMode: 0, data: data, data1: data1, data2: data2, value: false, value1: false, value2: false, hoveredItem: false }; mouseOvercall = () => { this.setState({ value1: false }); console.log('checkdata', this.state.value1); } render() { const { data1, data, data2 } = this.state; const markSeriesProps = { data, onNearestXY: (value: any) => this.setState({ value }) }; const markSeriesProps1 = { data1, onNearestXY: (value1: any) => this.setState({ value1 }) }; const markSeriesProps2 = { data2, onNearestXY: (value2: any) => this.setState({ value2 }) }; return ( <XYPlot width={1000} height={400} onMouseLeave={() => this.setState({ value: false, value1: false, value2: false })}> <XAxis /> <YAxis /> <HorizontalGridLines /> <VerticalGridLines /> <ContinuousColorLegend width={30} height={30} backgroundcolor="red" startTitle="first" endTitle="" />

    <LineMarkSeries
      name="Apples"
      // onSeriesMouseOut={() => this.setState({ value: false, value1: false, value2: false })}
      onMouseLeave={this.mouseOvercall}
      onSeriesMouseOver={this.mouseOvercall}
      // lineStyle={{ stroke: "black" }}
      // markStyle={{ stroke: "blue" }}
      color="red"
      style={{ mark: { stroke: 'white' } }}
      data={data}
      {...markSeriesProps}
    />
    <LineMarkSeries
      name="oranges"
      onMouseLeave={() => this.setState({ value: false, })}
      // lineStyle={{ stroke: "pink" }}
      // markStyle={{ stroke: "yellow" }}
      color="blue"
      style={{ mark: { stroke: 'white' } }}
      data={data1}
      onSeriesMouseOver={() => this.setState({ value: false })}
      {...markSeriesProps1}
    />
    <LineMarkSeries
      // lineStyle={{ stroke: "pink" }}
      // markStyle={{ stroke: "yellow" }}
      onMouseLeave={() => this.setState({ value1: false })}
      color="green"
      style={{ mark: { stroke: 'white' } }}
      data={data2}
      {...markSeriesProps2}
      onSeriesMouseOver={() => this.setState({ value1: false })}
    />
    {this.state.value ? <Hint
      value={this.state.value} style={{
        fontSize: 14,
        // text: {
        //   display: 'none'
        // },
        value: {
          color: 'white'
        }
      }} /> : null}
    {this.state.value1 ? <Hint value={this.state.value1} style={{
      fontSize: 14,
      // text: {
      //   display: 'none'
      // },
      value: {
        color: 'white'
      }
    }} /> : null}
    {this.state.value2 ? <Hint value={this.state.value2} style={{
      // text: {
      //   display: 'none'
      // },
      value: {
        color: 'white'
      }
    }} /> : null}
  </XYPlot>
);

}

}

ranjeetkjha123 avatar Sep 26 '19 11:09 ranjeetkjha123

i was trying to get tooltip for multi linemarkseries. i got the tooltip for all but i want only for particular series when i hover on it not on all. Screenshot 2019-09-26 at 4 39 50 PM

ranjeetkjha123 avatar Sep 26 '19 11:09 ranjeetkjha123

Has anyone been able to get this work?

grigy avatar Dec 25 '19 06:12 grigy

Is there anyone who can help with this? I have an XYPlot with multiple line series and I want to display a single tooltip for the point in the line that you hovering over, not every line series in the XYPlot.

onValueMouseOver never seems to fire so I'm not able to use that instead of onNearestX. onNearestX fires for every line which doesn't help for figuring out which one your cursor is closest to. The event from XYPlot for onMouseMove doesn't provide any helpful info for finding your mouses coordinates.

EDIT: I was able to solve this by tracking whether a LineSeries is hovered at the given point and time using a ref, then in onNearestX checking if the given line is hovered before showing the hint. It's not as clean as i'd like and i think this is a pretty common use of charts. It would be nice to figure out something nicer to implement in the library.

Here is some sample code to help anyone else that stumbles on this issue

function MultipleLinesWithTooltip() {
  const data = [
    [
      { x: 1, y: 10 },
      { x: 2, y: 5 },
      { x: 3, y: 15 },
    ],
    [
      { x: 1, y: 10 },
      { x: 2, y: 5 },
      { x: 3, y: 15 },
    ],
  ];
  const isHoveringOverLine = useRef<{ [key: string]: Boolean }>({});
  const [hoveredPoint, setHoveredPoint] = useState<{
    x: number;
    y: number;
  }>();

  return (
    <XYPlot width={800} height={300}>
      {data.map((lineData, lineIndex) => (
        <LineSeries
          key={lineIndex}
          onSeriesMouseOver={(e) => {
            isHoveringOverLine.current[lineIndex] = true;
          }}
          onSeriesMouseOut={(e: React.MouseEvent<HTMLOrSVGElement>) => {
            isHoveringOverLine.current[lineIndex] = false;
          }}
          onNearestXY={(e, { index }) => {
            if (isHoveringOverLine.current[lineIndex]) {
              const hoveredLine = lineData[index];
              setHoveredPoint({
                x: hoveredLine.x,
                y: hoveredLine.y,
              });
            }
          }}
          data={lineData}
        />
      ))}
      {hoveredPoint && <Hint value={hoveredPoint} />}
    </XYPlot>
  );
}

hmillison avatar Jul 29 '20 18:07 hmillison

Quite late in the discussion, but since i was facing this very same problem, and was able to rig up a workaround, thought of sharing it here.

I was having this very same problem, with another corner case.

The case being, i had multiple line series in my chart, but there was a possibility of missing data points anywhere in the date range that was present. This makes the placement of onNearestXY tricky, as it will break if the series on which it is placed, has no data for the hovered date range.

The solution:

Create a dummy line series, for each point in the range of the x axis, and place onNearestX on that line series. on hover, send the date range to your callback function and use that date range to fetch corresponding data from each line series. Not a pretty solution, but gets things done.

X axis, in my case, was a date type axis, so here's what a sample implementation might look like :

function Chart() {

  const onNearestX = (value, { index }) => {
    const targetDate = value.x;

    const crossHairValues = [];

    chartSeriesData.forEach((series) => {
      const currentSeries = series.data.find((data) => data.x === targetDate) || {
        x: '',
        y: '',
      };

      if (currentSeries.x !== '') {
        crossHairValues.push({
          x: currentSeries.x,
          y: currentSeries.y,
        });
      }
    });

    setCrosshairValues(crossHairValues);
  };

    const dummyLineData = React.useMemo(() => {
        const startDate = moment.unix(from).startOf('day');
        const endDate = moment.unix(to).startOf('day');
        const data = [];
    
        while (startDate.isSameOrBefore(endDate)) {
          data.push({ x: startDate.valueOf(), y: 0 });
          startDate.add(1, 'day');
        }
    
        return data;
      }, [from, to]);


      return (

       <!---- All your line series here -->

          <LineSeries
                    onNearestX={onNearestX}
                    data={dummyLineData}
                    style={{
                      strokeWidth: 0,
                      fill: 'none',
                    }}
                  />

      )
}

ManishKrShukla avatar Mar 31 '21 05:03 ManishKrShukla