getRelativeCoordinate

Computes relative element coordinates from mouse or touch event.

The output coordinates are relative to the top-left corner of the active element (= currentTarget), where the top-left corner is (0, 0). Moving right, the x-coordinate increases, and moving down, the y-coordinate increases.

The coordinates are rounded to the nearest integer and account for CSS transform scale. So element that's scaled will return the same coordinates as element that's not scaled.

In other words: you zoom in or out, numbers stay the same.

This function works with both HTML elements and SVG elements.

It works with both Mouse and Touch events. For Touch events, it returns an array of coordinates, one for each touch point. For Mouse events, it returns a single coordinate object.

Available since Recharts 3.8

import {
  RelativePointer,
  MouseHandlerDataParam,
  getRelativeCoordinate,
  Area,
  AreaChart,
  XAxis,
  YAxis,
  Cross,
  useChartWidth,
  useChartHeight,
  Legend,
  Text,
  TextProps,
  ZIndexLayer,
  DefaultZIndexes,
} from 'recharts';
import { generateMockData, RechartsDevtools } from '@recharts/devtools';
import { useState, MouseEvent, TouchEvent, useCallback } from 'react';

const data = generateMockData(30, 123);

const TextWithOutline = (textProps: TextProps) => (
  <Text stroke="white" strokeWidth={3} fill="black" paintOrder="stroke" {...textProps} />
);

const PixelCrosshair = ({ pointer }: { pointer: RelativePointer | null }) => {
  const width = useChartWidth();
  const height = useChartHeight();
  if (pointer == null || width == null || height == null) {
    return null;
  }
  return (
    <ZIndexLayer zIndex={DefaultZIndexes.cursorLine}>
      <TextWithOutline
        x={pointer.relativeX + 5}
        y={0}
        verticalAnchor="start"
      >{`x: ${pointer.relativeX}`}</TextWithOutline>
      <TextWithOutline
        y={pointer.relativeY + 5}
        x={width}
        verticalAnchor="start"
        textAnchor="end"
      >{`y: ${pointer.relativeY}`}</TextWithOutline>
      <Cross
        /*
         * pointerEvents: none is necessary because without it, browser will detect hovering over the Cross itself
         * which makes it trigger mouseLeave on the chart which makes it erase the Cross which looks like it's blinking.
         * If we skip pointer events on the cross then we can skip the mouseLeave and the movement is smooth.
         */
        style={{ pointerEvents: 'none' }}
        x={pointer.relativeX}
        y={pointer.relativeY}
        top={0}
        left={0}
        width={width}
        height={height}
        stroke="green"
        strokeWidth={1}
        strokeDasharray="4"
      />
    </ZIndexLayer>
  );
};

export default function CrosshairExample({
  initialPointers = [],
}: {
  initialPointers?: ReadonlyArray<RelativePointer>;
}) {
  const [pointers, setPointers] = useState<ReadonlyArray<RelativePointer>>(initialPointers);

  const handleMouseMove = useCallback(
    (_data: MouseHandlerDataParam, event: MouseEvent<SVGGraphicsElement>) => {
      /*
       * Here you have three coordinates available to your use:
       * 1. MouseHandlerDataParam.activeCoordinate
       *    - this is the coordinate where Recharts decided to display the Tooltip
       *    - may or may not be close to the mouse position
       *    - relative to chart position
       * 2. RelativePointer resolved by getRelativeCoordinate
       *    - mouse pointer position
       *    - relative to the element position
       *    - because we have registered this event on the chart, the coordinates here are also relative to the chart
       * 3. event.clientX (and pageX and screenX)
       *    - these are the standard browser event coordinates
       *    - some absolute some relative, depending on scroll and viewport, see the docs for details
       *    - https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
       *
       * This particular example demonstrates the use of (2.)
       */
      const chartPointer: RelativePointer = getRelativeCoordinate(event);
      setPointers([chartPointer]);
    },
    [setPointers],
  );

  const handleTouchMove = useCallback(
    (_data: unknown, event: TouchEvent<SVGGraphicsElement>) => {
      const chartPointers: RelativePointer[] = getRelativeCoordinate(event);
      setPointers(chartPointers);
    },
    [setPointers],
  );

  const handleLeave = useCallback(() => {
    setPointers([]);
  }, [setPointers]);

  return (
    <AreaChart
      style={{
        width: '100%',
        maxWidth: '500px',
        maxHeight: '200px',
        aspectRatio: 1,
        touchAction: 'none',
      }}
      responsive
      data={data}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleLeave}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleLeave}
    >
      <XAxis dataKey="label" />
      <YAxis width="auto" />
      <Area type="monotone" dataKey="x" />
      {pointers.map((pointer, index) => (
        <PixelCrosshair key={index} pointer={pointer} />
      ))}
      <Legend />
      <RechartsDevtools />
    </AreaChart>
  );
}

Return value

RelativePointer

Coordinates relative to the top-left corner of the element. Single object for Mouse events, array of objects for Touch events.

  • eventHTMLMousePointer | SVGMousePointer

    The mouse or touch event from React event handlers (works with both HTML and SVG elements)