Size-aware chart tooltip positioning (#980)

This commit is contained in:
Marshall Polaris 2022-10-01 00:10:17 -07:00 committed by GitHub
parent 2f3ae5192e
commit 2f1221f094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -16,6 +16,7 @@ import dayjs from 'dayjs'
import clsx from 'clsx' import clsx from 'clsx'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import { useMeasureSize } from 'web/hooks/use-measure-size'
export type Point<X, Y, T = unknown> = { x: X; y: Y; obj?: T } export type Point<X, Y, T = unknown> = { x: X; y: Y; obj?: T }
@ -135,6 +136,7 @@ export const SVGChart = <X, TT>(props: {
}) => { }) => {
const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props
const [mouse, setMouse] = useState<{ x: number; y: number; data: TT }>() const [mouse, setMouse] = useState<{ x: number; y: number; data: TT }>()
const tooltipMeasure = useMeasureSize()
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
@ -188,10 +190,18 @@ export const SVGChart = <X, TT>(props: {
} }
return ( return (
<div className="relative"> <div className="relative overflow-hidden">
{mouse && Tooltip && ( {mouse && Tooltip && (
<TooltipContainer <TooltipContainer
pos={getTooltipPosition(mouse.x, mouse.y, innerW, innerH)} setElem={tooltipMeasure.setElem}
pos={getTooltipPosition(
mouse.x,
mouse.y,
innerW,
innerH,
tooltipMeasure.width,
tooltipMeasure.height
)}
> >
<Tooltip <Tooltip
xScale={xAxis.scale()} xScale={xAxis.scale()}
@ -227,31 +237,31 @@ export const SVGChart = <X, TT>(props: {
) )
} }
export type TooltipPosition = { export type TooltipPosition = { left: number; bottom: number }
top?: number
right?: number
bottom?: number
left?: number
}
export const getTooltipPosition = ( export const getTooltipPosition = (
mouseX: number, mouseX: number,
mouseY: number, mouseY: number,
w: number, containerWidth: number,
h: number containerHeight: number,
tooltipWidth?: number,
tooltipHeight?: number
) => { ) => {
const result: TooltipPosition = {} let left = mouseX + 12
if (mouseX <= (3 * w) / 4) { let bottom = containerHeight - mouseY + 12
result.left = mouseX + 10 // in the left three quarters if (tooltipWidth != null) {
} else { const overflow = left + tooltipWidth - containerWidth
result.right = w - mouseX + 10 // in the right quarter if (overflow > 0) {
left -= overflow
}
} }
if (mouseY <= h / 4) { if (tooltipHeight != null) {
result.top = mouseY + 10 // in the top quarter const overflow = tooltipHeight - mouseY
} else { if (overflow > 0) {
result.bottom = h - mouseY + 10 // in the bottom three quarters bottom -= overflow
}
} }
return result return { left, bottom }
} }
export type TooltipProps<X, T> = { export type TooltipProps<X, T> = {
@ -260,15 +270,18 @@ export type TooltipProps<X, T> = {
xScale: ContinuousScale<X> xScale: ContinuousScale<X>
data: T data: T
} }
export type TooltipComponent<X, T> = React.ComponentType<TooltipProps<X, T>> export type TooltipComponent<X, T> = React.ComponentType<TooltipProps<X, T>>
export const TooltipContainer = (props: { export const TooltipContainer = (props: {
setElem: (e: HTMLElement | null) => void
pos: TooltipPosition pos: TooltipPosition
className?: string className?: string
children: React.ReactNode children: React.ReactNode
}) => { }) => {
const { pos, className, children } = props const { setElem, pos, className, children } = props
return ( return (
<div <div
ref={setElem}
className={clsx( className={clsx(
className, className,
'pointer-events-none absolute z-10 whitespace-pre rounded border border-gray-200 bg-white/80 p-2 px-4 py-2 text-xs sm:text-sm' 'pointer-events-none absolute z-10 whitespace-pre rounded border border-gray-200 bg-white/80 p-2 px-4 py-2 text-xs sm:text-sm'