import React, { useState, useEffect, useRef, useCallback } from 'react'; import { PlotPluginProps } from '../types'; import { pluginLog } from '../utils'; import { usePlotPluginContext } from '../context'; interface CursorPluginAPI { focusedSeriesIdx: number | null; focusedPointIdx: number | null; coords: { // coords relative to plot canvas, css px plotCanvas: Coords; // coords relative to viewport , css px viewport: Coords; }; } interface CursorPluginProps extends PlotPluginProps { onMouseMove?: () => void; // anything? children: (api: CursorPluginAPI) => React.ReactElement | null; // on what interaction position should be captures capture?: 'mousemove' | 'mousedown'; // should the position be persisted when user leaves canvas area lock?: boolean; } interface Coords { x: number; y: number; } /** * Exposes API for Graph cursor position * * @alpha */ export const CursorPlugin: React.FC = ({ id, children, capture = 'mousemove', lock = false }) => { const pluginId = `CursorPlugin:${id}`; const plotCanvas = useRef(null); const plotCanvasBBox = useRef({ left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }); const pluginsApi = usePlotPluginContext(); // state exposed to the consumers, maybe better implement as CursorPlugin? const [focusedSeriesIdx, setFocusedSeriesIdx] = useState(null); const [focusedPointIdx, setFocusedPointIdx] = useState(null); const [coords, setCoords] = useState<{ viewport: Coords; plotCanvas: Coords } | null>(null); const clearCoords = useCallback(() => { setCoords(null); }, [setCoords]); useEffect(() => { pluginLog(pluginId, true, `Focused series: ${focusedSeriesIdx}, focused point: ${focusedPointIdx}`); }, [focusedPointIdx, focusedSeriesIdx]); useEffect(() => { if (plotCanvas && plotCanvas.current) { plotCanvasBBox.current = plotCanvas.current.getBoundingClientRect(); } }, [plotCanvas.current]); // on mount - init plugin useEffect(() => { const onMouseCapture = (e: MouseEvent) => { setCoords({ plotCanvas: { x: e.clientX - plotCanvasBBox.current.left, y: e.clientY - plotCanvasBBox.current.top, }, viewport: { x: e.clientX, y: e.clientY, }, }); }; const unregister = pluginsApi.registerPlugin({ id: pluginId, hooks: { init: (u) => { // @ts-ignore plotCanvas.current = u.root.querySelector('.u-over'); // @ts-ignore plotCanvas.current.addEventListener(capture, onMouseCapture); if (!lock) { // @ts-ignore plotCanvas.current.addEventListener('mouseleave', clearCoords); } }, setCursor: (u) => { setFocusedPointIdx(u.cursor.idx === undefined ? null : u.cursor.idx); }, setSeries: (u, idx) => { setFocusedSeriesIdx(idx); }, }, }); return () => { if (plotCanvas && plotCanvas.current) { plotCanvas.current.removeEventListener(capture, onMouseCapture); } unregister(); }; }, []); // only render children if we are interacting with the canvas return coords ? children({ focusedSeriesIdx, focusedPointIdx, coords, }) : null; };