+) => {
+ const bisect = bisector((p: P) => p.x)
+ return (posX: number) => {
+ const x = xScale.invert(posX)
+ const item = data[bisect.left(data, x) - 1]
+ const result = item ? { ...item, x: posX } : undefined
+ return result
+ }
+}
+
export const DistributionChart = (props: {
data: P[]
w: number
@@ -39,7 +53,7 @@ export const DistributionChart =
(props: {
xScale: ScaleContinuousNumeric
yScale: ScaleContinuousNumeric
onMouseOver?: (p: P | undefined) => void
- Tooltip?: TooltipComponent
+ Tooltip?: TooltipComponent
}) => {
const { color, data, yScale, w, h, Tooltip } = props
@@ -50,7 +64,6 @@ export const DistributionChart = (props: {
const px = useCallback((p: P) => xScale(p.x), [xScale])
const py0 = yScale(yScale.domain()[0])
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
- const xBisector = bisector((p: P) => p.x)
const { xAxis, yAxis } = useMemo(() => {
const xAxis = axisBottom(xScale).ticks(w / 100)
@@ -58,6 +71,8 @@ export const DistributionChart = (props: {
return { xAxis, yAxis }
}, [w, xScale, yScale])
+ const onMouseOver = useEvent(betAtPointSelector(data, xScale))
+
const onSelect = useEvent((ev: D3BrushEvent
) => {
if (ev.selection) {
const [mouseX0, mouseX1] = ev.selection as [number, number]
@@ -69,14 +84,6 @@ export const DistributionChart =
(props: {
}
})
- const onMouseOver = useEvent((mouseX: number) => {
- const queryX = xScale.invert(mouseX)
- const item = data[xBisector.left(data, queryX) - 1]
- const result = item ? { ...item, x: queryX } : undefined
- props.onMouseOver?.(result)
- return result
- })
-
return (
(props: {
xScale: ScaleTime
yScale: ScaleContinuousNumeric
onMouseOver?: (p: P | undefined) => void
- Tooltip?: TooltipComponent
+ Tooltip?: TooltipComponent
pct?: boolean
}) => {
const { colors, data, yScale, w, h, Tooltip, pct } = props
@@ -119,7 +126,6 @@ export const MultiValueHistoryChart = (props: {
const px = useCallback((p: SP) => xScale(p.data.x), [xScale])
const py0 = useCallback((p: SP) => yScale(p[0]), [yScale])
const py1 = useCallback((p: SP) => yScale(p[1]), [yScale])
- const xBisector = bisector((p: P) => p.x)
const { xAxis, yAxis } = useMemo(() => {
const [min, max] = yScale.domain()
@@ -141,6 +147,8 @@ export const MultiValueHistoryChart =
(props: {
return d3Stack(data)
}, [data])
+ const onMouseOver = useEvent(betAtPointSelector(data, xScale))
+
const onSelect = useEvent((ev: D3BrushEvent
) => {
if (ev.selection) {
const [mouseX0, mouseX1] = ev.selection as [number, number]
@@ -152,14 +160,6 @@ export const MultiValueHistoryChart =
(props: {
}
})
- const onMouseOver = useEvent((mouseX: number) => {
- const queryX = xScale.invert(mouseX)
- const item = data[xBisector.left(data, queryX) - 1]
- const result = item ? { ...item, x: queryX } : undefined
- props.onMouseOver?.(result)
- return result
- })
-
return (
(props: {
xScale: ScaleTime
yScale: ScaleContinuousNumeric
onMouseOver?: (p: P | undefined) => void
- Tooltip?: TooltipComponent
+ Tooltip?: TooltipComponent
pct?: boolean
}) => {
const { color, data, yScale, w, h, Tooltip, pct } = props
@@ -204,7 +204,6 @@ export const SingleValueHistoryChart = (props: {
const px = useCallback((p: P) => xScale(p.x), [xScale])
const py0 = yScale(yScale.domain()[0])
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
- const xBisector = bisector((p: P) => p.x)
const { xAxis, yAxis } = useMemo(() => {
const [min, max] = yScale.domain()
@@ -218,6 +217,8 @@ export const SingleValueHistoryChart =
(props: {
return { xAxis, yAxis }
}, [w, h, pct, xScale, yScale])
+ const onMouseOver = useEvent(betAtPointSelector(data, xScale))
+
const onSelect = useEvent((ev: D3BrushEvent
) => {
if (ev.selection) {
const [mouseX0, mouseX1] = ev.selection as [number, number]
@@ -229,14 +230,6 @@ export const SingleValueHistoryChart =
(props: {
}
})
- const onMouseOver = useEvent((mouseX: number) => {
- const queryX = xScale.invert(mouseX)
- const item = data[xBisector.left(data, queryX) - 1]
- const result = item ? { ...item, x: queryX } : undefined
- props.onMouseOver?.(result)
- return result
- })
-
return (
= { x: X; y: Y; obj?: T }
+
+export interface ContinuousScale extends AxisScale {
+ invert(n: number): T
+}
-export type Point = { x: X; y: Y; datum?: T }
export type XScale = P extends Point ? AxisScale : never
export type YScale = P extends Point ? AxisScale : never
@@ -118,18 +124,19 @@ export const AreaWithTopStroke = (props: {
)
}
-export const SVGChart = >(props: {
+export const SVGChart = (props: {
children: ReactNode
w: number
h: number
xAxis: Axis
yAxis: Axis
onSelect?: (ev: D3BrushEvent) => void
- onMouseOver?: (mouseX: number, mouseY: number) => P | undefined
- Tooltip?: TooltipComponent
+ onMouseOver?: (mouseX: number, mouseY: number) => TT | undefined
+ Tooltip?: TooltipComponent
}) => {
const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props
- const [mouseState, setMouseState] = useState<{ pos: TooltipPosition; p: P }>()
+ const [mouse, setMouse] = useState<{ x: number; y: number; data: TT }>()
+ const tooltipMeasure = useMeasureSize()
const overlayRef = useRef(null)
const innerW = w - MARGIN_X
const innerH = h - MARGIN_Y
@@ -148,7 +155,7 @@ export const SVGChart = >(props: {
if (!justSelected.current) {
justSelected.current = true
onSelect(ev)
- setMouseState(undefined)
+ setMouse(undefined)
if (overlayRef.current) {
select(overlayRef.current).call(brush.clear)
}
@@ -168,26 +175,40 @@ export const SVGChart = >(props: {
const onPointerMove = (ev: React.PointerEvent) => {
if (ev.pointerType === 'mouse' && onMouseOver) {
- const [mouseX, mouseY] = pointer(ev)
- const p = onMouseOver(mouseX, mouseY)
- if (p != null) {
- const pos = getTooltipPosition(mouseX, mouseY, innerW, innerH)
- setMouseState({ pos, p })
+ const [x, y] = pointer(ev)
+ const data = onMouseOver(x, y)
+ if (data !== undefined) {
+ setMouse({ x, y, data })
} else {
- setMouseState(undefined)
+ setMouse(undefined)
}
}
}
const onPointerLeave = () => {
- setMouseState(undefined)
+ setMouse(undefined)
}
return (
-
- {mouseState && Tooltip && (
-
-
+
+ {mouse && Tooltip && (
+
+
)}