useChartHighlight.tsx103 lines · main
| 1 | import dayjs from 'dayjs' |
| 2 | import { useState } from 'react' |
| 3 | |
| 4 | type ChartHighlightMouseEvent = { |
| 5 | activeLabel?: string |
| 6 | coordinates?: string |
| 7 | } |
| 8 | |
| 9 | export interface ChartHighlight { |
| 10 | left: string | undefined |
| 11 | right: string | undefined |
| 12 | coordinates: { left?: string; right?: string } |
| 13 | isSelecting: boolean |
| 14 | popoverPosition: { x: number; y: number } | null |
| 15 | handleMouseDown: (e: ChartHighlightMouseEvent) => void |
| 16 | handleMouseMove: (e: ChartHighlightMouseEvent) => void |
| 17 | handleMouseUp: (e: { chartX?: number; chartY?: number }) => void |
| 18 | clearHighlight: () => void |
| 19 | } |
| 20 | |
| 21 | export function useChartHighlight(): ChartHighlight { |
| 22 | const [left, setLeft] = useState<string | undefined>(undefined) |
| 23 | const [right, setRight] = useState<string | undefined>(undefined) |
| 24 | const [coordinates, setCoordinates] = useState<{ left?: string; right?: string }>({ |
| 25 | left: undefined, |
| 26 | right: undefined, |
| 27 | }) |
| 28 | const [isSelecting, setIsSelecting] = useState(false) |
| 29 | const [popoverPosition, setPopoverPosition] = useState<{ x: number; y: number } | null>(null) |
| 30 | const [initialPoint, setInitialPoint] = useState<string | undefined>(undefined) |
| 31 | |
| 32 | const handleMouseDown = (e: ChartHighlightMouseEvent) => { |
| 33 | clearHighlight() |
| 34 | if (!e || !e.activeLabel) return |
| 35 | setIsSelecting(true) |
| 36 | setLeft(e.activeLabel) |
| 37 | setRight(e.activeLabel) |
| 38 | setInitialPoint(e.activeLabel) |
| 39 | setCoordinates({ left: e.coordinates, right: e.coordinates }) |
| 40 | } |
| 41 | |
| 42 | const handleMouseMove = (e: ChartHighlightMouseEvent) => { |
| 43 | if (!isSelecting || !e || !e.activeLabel) return |
| 44 | |
| 45 | const currentTimestamp = dayjs(e.activeLabel) |
| 46 | const initialTimestamp = dayjs(initialPoint) |
| 47 | |
| 48 | if (currentTimestamp.isBefore(initialTimestamp)) { |
| 49 | // If dragging left, update left and keep right as initial |
| 50 | setLeft(e.activeLabel) |
| 51 | setRight(initialPoint) |
| 52 | setCoordinates({ |
| 53 | left: e.coordinates, |
| 54 | right: coordinates.right, |
| 55 | }) |
| 56 | } else { |
| 57 | // If dragging right, update right and keep left as initial |
| 58 | setRight(e.activeLabel) |
| 59 | setLeft(initialPoint) |
| 60 | setCoordinates({ |
| 61 | left: coordinates.left, |
| 62 | right: e.coordinates, |
| 63 | }) |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | const handleMouseUp = (e: unknown) => { |
| 68 | if (!isSelecting) return |
| 69 | setIsSelecting(false) |
| 70 | setInitialPoint(undefined) |
| 71 | |
| 72 | if ( |
| 73 | typeof e === 'object' && |
| 74 | e !== null && |
| 75 | 'chartX' in e && |
| 76 | 'chartY' in e && |
| 77 | typeof e.chartX === 'number' && |
| 78 | typeof e.chartY === 'number' |
| 79 | ) { |
| 80 | setPopoverPosition({ x: e.chartX, y: e.chartY }) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | const clearHighlight = () => { |
| 85 | setLeft(undefined) |
| 86 | setRight(undefined) |
| 87 | setCoordinates({ left: undefined, right: undefined }) |
| 88 | setPopoverPosition(null) |
| 89 | setInitialPoint(undefined) |
| 90 | } |
| 91 | |
| 92 | return { |
| 93 | left, |
| 94 | right, |
| 95 | coordinates, |
| 96 | isSelecting, |
| 97 | popoverPosition, |
| 98 | handleMouseDown, |
| 99 | handleMouseMove, |
| 100 | handleMouseUp, |
| 101 | clearHighlight, |
| 102 | } |
| 103 | } |