Clean up chart tooltip handling (#959)
This commit is contained in:
		
							parent
							
								
									be010da9f5
								
							
						
					
					
						commit
						8862425120
					
				|  | @ -32,7 +32,8 @@ const getBetPoints = (bets: Bet[]) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const BinaryChartTooltip = (props: SingleValueHistoryTooltipProps<Bet>) => { | const BinaryChartTooltip = (props: SingleValueHistoryTooltipProps<Bet>) => { | ||||||
|   const { x, y, xScale, datum } = props |   const { p, xScale } = props | ||||||
|  |   const { x, y, datum } = p | ||||||
|   const [start, end] = xScale.domain() |   const [start, end] = xScale.domain() | ||||||
|   return ( |   return ( | ||||||
|     <Row className="items-center gap-2 text-sm"> |     <Row className="items-center gap-2 text-sm"> | ||||||
|  |  | ||||||
|  | @ -162,7 +162,8 @@ export const ChoiceContractChart = (props: { | ||||||
| 
 | 
 | ||||||
|   const ChoiceTooltip = useMemo( |   const ChoiceTooltip = useMemo( | ||||||
|     () => (props: MultiValueHistoryTooltipProps<Bet>) => { |     () => (props: MultiValueHistoryTooltipProps<Bet>) => { | ||||||
|       const { x, y, xScale, datum } = props |       const { p, xScale } = props | ||||||
|  |       const { x, y, datum } = p | ||||||
|       const [start, end] = xScale.domain() |       const [start, end] = xScale.domain() | ||||||
|       const legendItems = sortBy( |       const legendItems = sortBy( | ||||||
|         y.map((p, i) => ({ |         y.map((p, i) => ({ | ||||||
|  |  | ||||||
|  | @ -25,7 +25,8 @@ const getNumericChartData = (contract: NumericContract) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const NumericChartTooltip = (props: SingleValueDistributionTooltipProps) => { | const NumericChartTooltip = (props: SingleValueDistributionTooltipProps) => { | ||||||
|   const { x, y } = props |   const { p } = props | ||||||
|  |   const { x, y } = p | ||||||
|   return ( |   return ( | ||||||
|     <span className="text-sm"> |     <span className="text-sm"> | ||||||
|       <strong>{formatPct(y, 2)}</strong> {formatLargeNumber(x)} |       <strong>{formatPct(y, 2)}</strong> {formatLargeNumber(x)} | ||||||
|  |  | ||||||
|  | @ -46,7 +46,8 @@ const getBetPoints = (bets: Bet[], scaleP: (p: number) => number) => { | ||||||
| const PseudoNumericChartTooltip = ( | const PseudoNumericChartTooltip = ( | ||||||
|   props: SingleValueHistoryTooltipProps<Bet> |   props: SingleValueHistoryTooltipProps<Bet> | ||||||
| ) => { | ) => { | ||||||
|   const { x, y, xScale, datum } = props |   const { p, xScale } = props | ||||||
|  |   const { x, y, datum } = p | ||||||
|   const [start, end] = xScale.domain() |   const [start, end] = xScale.domain() | ||||||
|   return ( |   return ( | ||||||
|     <Row className="items-center gap-2 text-sm"> |     <Row className="items-center gap-2 text-sm"> | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ import { bisector } from 'd3-array' | ||||||
| import { axisBottom, axisLeft } from 'd3-axis' | import { axisBottom, axisLeft } from 'd3-axis' | ||||||
| import { D3BrushEvent } from 'd3-brush' | import { D3BrushEvent } from 'd3-brush' | ||||||
| import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale' | import { ScaleTime, ScaleContinuousNumeric } from 'd3-scale' | ||||||
| import { pointer } from 'd3-selection' |  | ||||||
| import { | import { | ||||||
|   curveLinear, |   curveLinear, | ||||||
|   curveStepAfter, |   curveStepAfter, | ||||||
|  | @ -18,17 +17,25 @@ import { | ||||||
|   AreaPath, |   AreaPath, | ||||||
|   AreaWithTopStroke, |   AreaWithTopStroke, | ||||||
|   TooltipContent, |   TooltipContent, | ||||||
|   TooltipContainer, |  | ||||||
|   TooltipPosition, |  | ||||||
|   formatPct, |   formatPct, | ||||||
| } from './helpers' | } from './helpers' | ||||||
| import { useEvent } from 'web/hooks/use-event' | import { useEvent } from 'web/hooks/use-event' | ||||||
| 
 | 
 | ||||||
| export type MultiPoint<T = never> = { x: Date; y: number[]; datum?: T } | export type MultiPoint<T = never> = { | ||||||
| export type HistoryPoint<T = never> = { x: Date; y: number; datum?: T } |   x: Date | ||||||
| export type DistributionPoint<T = never> = { x: number; y: number; datum?: T } |   y: number[] | ||||||
| 
 |   datum?: T | ||||||
| type PositionValue<P> = TooltipPosition & { p: P } | } | ||||||
|  | export type HistoryPoint<T = never> = { | ||||||
|  |   x: Date | ||||||
|  |   y: number | ||||||
|  |   datum?: T | ||||||
|  | } | ||||||
|  | export type DistributionPoint<T = never> = { | ||||||
|  |   x: number | ||||||
|  |   y: number | ||||||
|  |   datum?: T | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| const getTickValues = (min: number, max: number, n: number) => { | const getTickValues = (min: number, max: number, n: number) => { | ||||||
|   const step = (max - min) / (n - 1) |   const step = (max - min) / (n - 1) | ||||||
|  | @ -48,8 +55,6 @@ export const SingleValueDistributionChart = <T,>(props: { | ||||||
| 
 | 
 | ||||||
|   const [viewXScale, setViewXScale] = |   const [viewXScale, setViewXScale] = | ||||||
|     useState<ScaleContinuousNumeric<number, number>>() |     useState<ScaleContinuousNumeric<number, number>>() | ||||||
|   const [mouseState, setMouseState] = |  | ||||||
|     useState<PositionValue<DistributionPoint<T>>>() |  | ||||||
|   const xScale = viewXScale ?? props.xScale |   const xScale = viewXScale ?? props.xScale | ||||||
| 
 | 
 | ||||||
|   const px = useCallback((p: DistributionPoint<T>) => xScale(p.x), [xScale]) |   const px = useCallback((p: DistributionPoint<T>) => xScale(p.x), [xScale]) | ||||||
|  | @ -69,67 +74,48 @@ export const SingleValueDistributionChart = <T,>(props: { | ||||||
|       setViewXScale(() => |       setViewXScale(() => | ||||||
|         xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) |         xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) | ||||||
|       ) |       ) | ||||||
|       setMouseState(undefined) |  | ||||||
|     } else { |     } else { | ||||||
|       setViewXScale(undefined) |       setViewXScale(undefined) | ||||||
|       setMouseState(undefined) |  | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const onMouseOver = useEvent((ev: React.PointerEvent) => { |   const onMouseOver = useEvent((mouseX: number) => { | ||||||
|     if (ev.pointerType === 'mouse') { |     const queryX = xScale.invert(mouseX) | ||||||
|       const [mouseX, mouseY] = pointer(ev) |     const item = data[xBisector.left(data, queryX) - 1] | ||||||
|       const queryX = xScale.invert(mouseX) |     if (item == null) { | ||||||
|       const item = data[xBisector.left(data, queryX) - 1] |       // this can happen if you are on the very left or right edge of the chart,
 | ||||||
|       if (item == null) { |       // so your queryX is out of bounds
 | ||||||
|         // this can happen if you are on the very left or right edge of the chart,
 |       return | ||||||
|         // so your queryX is out of bounds
 |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
|       const p = { x: queryX, y: item.y, datum: item.datum } |  | ||||||
|       setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) |  | ||||||
|     } |     } | ||||||
|   }) |     return { x: queryX, y: item.y, datum: item.datum } | ||||||
| 
 |  | ||||||
|   const onMouseLeave = useEvent(() => { |  | ||||||
|     setMouseState(undefined) |  | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="relative"> |     <SVGChart | ||||||
|       {mouseState && Tooltip && ( |       w={w} | ||||||
|         <TooltipContainer className="text-sm" {...mouseState}> |       h={h} | ||||||
|           <Tooltip xScale={xScale} {...mouseState.p} /> |       xAxis={xAxis} | ||||||
|         </TooltipContainer> |       yAxis={yAxis} | ||||||
|       )} |       onSelect={onSelect} | ||||||
|       <SVGChart |       onMouseOver={onMouseOver} | ||||||
|         w={w} |       Tooltip={Tooltip} | ||||||
|         h={h} |     > | ||||||
|         xAxis={xAxis} |       <AreaWithTopStroke | ||||||
|         yAxis={yAxis} |         color={color} | ||||||
|         onSelect={onSelect} |         data={data} | ||||||
|         onMouseOver={onMouseOver} |         px={px} | ||||||
|         onMouseLeave={onMouseLeave} |         py0={py0} | ||||||
|       > |         py1={py1} | ||||||
|         <AreaWithTopStroke |         curve={curveLinear} | ||||||
|           color={color} |       /> | ||||||
|           data={data} |     </SVGChart> | ||||||
|           px={px} |  | ||||||
|           py0={py0} |  | ||||||
|           py1={py1} |  | ||||||
|           curve={curveLinear} |  | ||||||
|         /> |  | ||||||
|       </SVGChart> |  | ||||||
|     </div> |  | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type SingleValueDistributionTooltipProps<T = unknown> = | export type SingleValueDistributionTooltipProps<T = unknown> = { | ||||||
|   DistributionPoint<T> & { |   p: DistributionPoint<T> | ||||||
|     xScale: React.ComponentProps< |   xScale: React.ComponentProps<typeof SingleValueDistributionChart<T>>['xScale'] | ||||||
|       typeof SingleValueDistributionChart<T> | } | ||||||
|     >['xScale'] |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
| export const MultiValueHistoryChart = <T,>(props: { | export const MultiValueHistoryChart = <T,>(props: { | ||||||
|   data: MultiPoint<T>[] |   data: MultiPoint<T>[] | ||||||
|  | @ -144,7 +130,6 @@ export const MultiValueHistoryChart = <T,>(props: { | ||||||
|   const { colors, data, yScale, w, h, Tooltip, pct } = props |   const { colors, data, yScale, w, h, Tooltip, pct } = props | ||||||
| 
 | 
 | ||||||
|   const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>() |   const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>() | ||||||
|   const [mouseState, setMouseState] = useState<PositionValue<MultiPoint<T>>>() |  | ||||||
|   const xScale = viewXScale ?? props.xScale |   const xScale = viewXScale ?? props.xScale | ||||||
| 
 | 
 | ||||||
|   type SP = SeriesPoint<MultiPoint<T>> |   type SP = SeriesPoint<MultiPoint<T>> | ||||||
|  | @ -177,65 +162,49 @@ export const MultiValueHistoryChart = <T,>(props: { | ||||||
|       setViewXScale(() => |       setViewXScale(() => | ||||||
|         xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) |         xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) | ||||||
|       ) |       ) | ||||||
|       setMouseState(undefined) |  | ||||||
|     } else { |     } else { | ||||||
|       setViewXScale(undefined) |       setViewXScale(undefined) | ||||||
|       setMouseState(undefined) |  | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const onMouseOver = useEvent((ev: React.PointerEvent) => { |   const onMouseOver = useEvent((mouseX: number) => { | ||||||
|     if (ev.pointerType === 'mouse') { |     const queryX = xScale.invert(mouseX) | ||||||
|       const [mouseX, mouseY] = pointer(ev) |     const item = data[xBisector.left(data, queryX) - 1] | ||||||
|       const queryX = xScale.invert(mouseX) |     if (item == null) { | ||||||
|       const item = data[xBisector.left(data, queryX) - 1] |       // this can happen if you are on the very left or right edge of the chart,
 | ||||||
|       if (item == null) { |       // so your queryX is out of bounds
 | ||||||
|         // this can happen if you are on the very left or right edge of the chart,
 |       return | ||||||
|         // so your queryX is out of bounds
 |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
|       const p = { x: queryX, y: item.y, datum: item.datum } |  | ||||||
|       setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) |  | ||||||
|     } |     } | ||||||
|   }) |     return { x: queryX, y: item.y, datum: item.datum } | ||||||
| 
 |  | ||||||
|   const onMouseLeave = useEvent(() => { |  | ||||||
|     setMouseState(undefined) |  | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="relative"> |     <SVGChart | ||||||
|       {mouseState && Tooltip && ( |       w={w} | ||||||
|         <TooltipContainer top={mouseState.top} left={mouseState.left}> |       h={h} | ||||||
|           <Tooltip xScale={xScale} {...mouseState.p} /> |       xAxis={xAxis} | ||||||
|         </TooltipContainer> |       yAxis={yAxis} | ||||||
|       )} |       onSelect={onSelect} | ||||||
|       <SVGChart |       onMouseOver={onMouseOver} | ||||||
|         w={w} |       Tooltip={Tooltip} | ||||||
|         h={h} |     > | ||||||
|         xAxis={xAxis} |       {series.map((s, i) => ( | ||||||
|         yAxis={yAxis} |         <AreaPath | ||||||
|         onSelect={onSelect} |           key={i} | ||||||
|         onMouseOver={onMouseOver} |           data={s} | ||||||
|         onMouseLeave={onMouseLeave} |           px={px} | ||||||
|       > |           py0={py0} | ||||||
|         {series.map((s, i) => ( |           py1={py1} | ||||||
|           <AreaPath |           curve={curveStepAfter} | ||||||
|             key={i} |           fill={colors[i]} | ||||||
|             data={s} |         /> | ||||||
|             px={px} |       ))} | ||||||
|             py0={py0} |     </SVGChart> | ||||||
|             py1={py1} |  | ||||||
|             curve={curveStepAfter} |  | ||||||
|             fill={colors[i]} |  | ||||||
|           /> |  | ||||||
|         ))} |  | ||||||
|       </SVGChart> |  | ||||||
|     </div> |  | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type MultiValueHistoryTooltipProps<T = unknown> = MultiPoint<T> & { | export type MultiValueHistoryTooltipProps<T = unknown> = { | ||||||
|  |   p: MultiPoint<T> | ||||||
|   xScale: React.ComponentProps<typeof MultiValueHistoryChart<T>>['xScale'] |   xScale: React.ComponentProps<typeof MultiValueHistoryChart<T>>['xScale'] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -252,7 +221,6 @@ export const SingleValueHistoryChart = <T,>(props: { | ||||||
|   const { color, data, pct, yScale, w, h, Tooltip } = props |   const { color, data, pct, yScale, w, h, Tooltip } = props | ||||||
| 
 | 
 | ||||||
|   const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>() |   const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>() | ||||||
|   const [mouseState, setMouseState] = useState<PositionValue<HistoryPoint<T>>>() |  | ||||||
|   const xScale = viewXScale ?? props.xScale |   const xScale = viewXScale ?? props.xScale | ||||||
| 
 | 
 | ||||||
|   const px = useCallback((p: HistoryPoint<T>) => xScale(p.x), [xScale]) |   const px = useCallback((p: HistoryPoint<T>) => xScale(p.x), [xScale]) | ||||||
|  | @ -276,61 +244,45 @@ export const SingleValueHistoryChart = <T,>(props: { | ||||||
|       setViewXScale(() => |       setViewXScale(() => | ||||||
|         xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) |         xScale.copy().domain([xScale.invert(mouseX0), xScale.invert(mouseX1)]) | ||||||
|       ) |       ) | ||||||
|       setMouseState(undefined) |  | ||||||
|     } else { |     } else { | ||||||
|       setViewXScale(undefined) |       setViewXScale(undefined) | ||||||
|       setMouseState(undefined) |  | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const onMouseOver = useEvent((ev: React.PointerEvent) => { |   const onMouseOver = useEvent((mouseX: number) => { | ||||||
|     if (ev.pointerType === 'mouse') { |     const queryX = xScale.invert(mouseX) | ||||||
|       const [mouseX, mouseY] = pointer(ev) |     const item = data[xBisector.left(data, queryX) - 1] | ||||||
|       const queryX = xScale.invert(mouseX) |     if (item == null) { | ||||||
|       const item = data[xBisector.left(data, queryX) - 1] |       // this can happen if you are on the very left or right edge of the chart,
 | ||||||
|       if (item == null) { |       // so your queryX is out of bounds
 | ||||||
|         // this can happen if you are on the very left or right edge of the chart,
 |       return | ||||||
|         // so your queryX is out of bounds
 |  | ||||||
|         return |  | ||||||
|       } |  | ||||||
|       const p = { x: queryX, y: item.y, datum: item.datum } |  | ||||||
|       setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) |  | ||||||
|     } |     } | ||||||
|   }) |     return { x: queryX, y: item.y, datum: item.datum } | ||||||
| 
 |  | ||||||
|   const onMouseLeave = useEvent(() => { |  | ||||||
|     setMouseState(undefined) |  | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="relative"> |     <SVGChart | ||||||
|       {mouseState && Tooltip && ( |       w={w} | ||||||
|         <TooltipContainer top={mouseState.top} left={mouseState.left}> |       h={h} | ||||||
|           <Tooltip xScale={xScale} {...mouseState.p} /> |       xAxis={xAxis} | ||||||
|         </TooltipContainer> |       yAxis={yAxis} | ||||||
|       )} |       onSelect={onSelect} | ||||||
|       <SVGChart |       onMouseOver={onMouseOver} | ||||||
|         w={w} |       Tooltip={Tooltip} | ||||||
|         h={h} |     > | ||||||
|         xAxis={xAxis} |       <AreaWithTopStroke | ||||||
|         yAxis={yAxis} |         color={color} | ||||||
|         onSelect={onSelect} |         data={data} | ||||||
|         onMouseOver={onMouseOver} |         px={px} | ||||||
|         onMouseLeave={onMouseLeave} |         py0={py0} | ||||||
|       > |         py1={py1} | ||||||
|         <AreaWithTopStroke |         curve={curveStepAfter} | ||||||
|           color={color} |       /> | ||||||
|           data={data} |     </SVGChart> | ||||||
|           px={px} |  | ||||||
|           py0={py0} |  | ||||||
|           py1={py1} |  | ||||||
|           curve={curveStepAfter} |  | ||||||
|         /> |  | ||||||
|       </SVGChart> |  | ||||||
|     </div> |  | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type SingleValueHistoryTooltipProps<T = unknown> = HistoryPoint<T> & { | export type SingleValueHistoryTooltipProps<T = unknown> = { | ||||||
|  |   p: HistoryPoint<T> | ||||||
|   xScale: React.ComponentProps<typeof SingleValueHistoryChart<T>>['xScale'] |   xScale: React.ComponentProps<typeof SingleValueHistoryChart<T>>['xScale'] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,13 @@ | ||||||
| import { ReactNode, SVGProps, memo, useRef, useEffect, useMemo } from 'react' | import { | ||||||
| import { select } from 'd3-selection' |   ReactNode, | ||||||
|  |   SVGProps, | ||||||
|  |   memo, | ||||||
|  |   useRef, | ||||||
|  |   useEffect, | ||||||
|  |   useMemo, | ||||||
|  |   useState, | ||||||
|  | } from 'react' | ||||||
|  | import { pointer, select } from 'd3-selection' | ||||||
| import { Axis } from 'd3-axis' | import { Axis } from 'd3-axis' | ||||||
| import { brushX, D3BrushEvent } from 'd3-brush' | import { brushX, D3BrushEvent } from 'd3-brush' | ||||||
| import { area, line, curveStepAfter, CurveFactory } from 'd3-shape' | import { area, line, curveStepAfter, CurveFactory } from 'd3-shape' | ||||||
|  | @ -108,19 +116,18 @@ export const AreaWithTopStroke = <P,>(props: { | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const SVGChart = <X, Y>(props: { | export const SVGChart = <X, Y, P, XS>(props: { | ||||||
|   children: ReactNode |   children: ReactNode | ||||||
|   w: number |   w: number | ||||||
|   h: number |   h: number | ||||||
|   xAxis: Axis<X> |   xAxis: Axis<X> | ||||||
|   yAxis: Axis<Y> |   yAxis: Axis<Y> | ||||||
|   onSelect?: (ev: D3BrushEvent<any>) => void |   onSelect?: (ev: D3BrushEvent<any>) => void | ||||||
|   onMouseOver?: (ev: React.PointerEvent) => void |   onMouseOver?: (mouseX: number, mouseY: number) => P | undefined | ||||||
|   onMouseLeave?: (ev: React.PointerEvent) => void |   Tooltip?: TooltipContent<{ xScale: XS } & { p: P }> | ||||||
|   pct?: boolean |  | ||||||
| }) => { | }) => { | ||||||
|   const { children, w, h, xAxis, yAxis, onMouseOver, onMouseLeave, onSelect } = |   const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props | ||||||
|     props |   const [mouseState, setMouseState] = useState<TooltipPosition & { p: P }>() | ||||||
|   const overlayRef = useRef<SVGGElement>(null) |   const overlayRef = useRef<SVGGElement>(null) | ||||||
|   const innerW = w - MARGIN_X |   const innerW = w - MARGIN_X | ||||||
|   const innerH = h - MARGIN_Y |   const innerH = h - MARGIN_Y | ||||||
|  | @ -139,6 +146,7 @@ export const SVGChart = <X, Y>(props: { | ||||||
|         if (!justSelected.current) { |         if (!justSelected.current) { | ||||||
|           justSelected.current = true |           justSelected.current = true | ||||||
|           onSelect(ev) |           onSelect(ev) | ||||||
|  |           setMouseState(undefined) | ||||||
|           if (overlayRef.current) { |           if (overlayRef.current) { | ||||||
|             select(overlayRef.current).call(brush.clear) |             select(overlayRef.current).call(brush.clear) | ||||||
|           } |           } | ||||||
|  | @ -156,29 +164,52 @@ export const SVGChart = <X, Y>(props: { | ||||||
|     } |     } | ||||||
|   }, [innerW, innerH, onSelect]) |   }, [innerW, innerH, onSelect]) | ||||||
| 
 | 
 | ||||||
|  |   const onPointerMove = (ev: React.PointerEvent) => { | ||||||
|  |     if (ev.pointerType === 'mouse' && onMouseOver) { | ||||||
|  |       const [mouseX, mouseY] = pointer(ev) | ||||||
|  |       const p = onMouseOver(mouseX, mouseY) | ||||||
|  |       if (p != null) { | ||||||
|  |         setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) | ||||||
|  |       } else { | ||||||
|  |         setMouseState(undefined) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const onPointerLeave = () => { | ||||||
|  |     setMouseState(undefined) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <svg className="w-full" width={w} height={h} viewBox={`0 0 ${w} ${h}`}> |     <div className="relative"> | ||||||
|       <clipPath id={clipPathId}> |       {mouseState && Tooltip && ( | ||||||
|         <rect x={0} y={0} width={innerW} height={innerH} /> |         <TooltipContainer top={mouseState.top} left={mouseState.left}> | ||||||
|       </clipPath> |           <Tooltip xScale={xAxis.scale() as XS} p={mouseState.p} /> | ||||||
|       <g transform={`translate(${MARGIN.left}, ${MARGIN.top})`}> |         </TooltipContainer> | ||||||
|         <XAxis axis={xAxis} w={innerW} h={innerH} /> |       )} | ||||||
|         <YAxis axis={yAxis} w={innerW} h={innerH} /> |       <svg className="w-full" width={w} height={h} viewBox={`0 0 ${w} ${h}`}> | ||||||
|         <g clipPath={`url(#${clipPathId})`}>{children}</g> |         <clipPath id={clipPathId}> | ||||||
|         <g |           <rect x={0} y={0} width={innerW} height={innerH} /> | ||||||
|           ref={overlayRef} |         </clipPath> | ||||||
|           x="0" |         <g transform={`translate(${MARGIN.left}, ${MARGIN.top})`}> | ||||||
|           y="0" |           <XAxis axis={xAxis} w={innerW} h={innerH} /> | ||||||
|           width={innerW} |           <YAxis axis={yAxis} w={innerW} h={innerH} /> | ||||||
|           height={innerH} |           <g clipPath={`url(#${clipPathId})`}>{children}</g> | ||||||
|           fill="none" |           <g | ||||||
|           pointerEvents="all" |             ref={overlayRef} | ||||||
|           onPointerEnter={onMouseOver} |             x="0" | ||||||
|           onPointerMove={onMouseOver} |             y="0" | ||||||
|           onPointerLeave={onMouseLeave} |             width={innerW} | ||||||
|         /> |             height={innerH} | ||||||
|       </g> |             fill="none" | ||||||
|     </svg> |             pointerEvents="all" | ||||||
|  |             onPointerEnter={onPointerMove} | ||||||
|  |             onPointerMove={onPointerMove} | ||||||
|  |             onPointerLeave={onPointerLeave} | ||||||
|  |           /> | ||||||
|  |         </g> | ||||||
|  |       </svg> | ||||||
|  |     </div> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user