More refactoring to make chart tooltips more flexible (#975)
This commit is contained in:
parent
1fc2f15dae
commit
38b7c898f6
|
@ -25,19 +25,19 @@ const getBetPoints = (bets: Bet[]) => {
|
||||||
return sortBy(bets, (b) => b.createdTime).map((b) => ({
|
return sortBy(bets, (b) => b.createdTime).map((b) => ({
|
||||||
x: new Date(b.createdTime),
|
x: new Date(b.createdTime),
|
||||||
y: b.probAfter,
|
y: b.probAfter,
|
||||||
datum: b,
|
obj: b,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const BinaryChartTooltip = (props: TooltipProps<HistoryPoint<Bet>>) => {
|
const BinaryChartTooltip = (props: TooltipProps<Date, HistoryPoint<Bet>>) => {
|
||||||
const { p, xScale } = props
|
const { data, mouseX, xScale } = props
|
||||||
const { x, y, datum } = p
|
|
||||||
const [start, end] = xScale.domain()
|
const [start, end] = xScale.domain()
|
||||||
|
const d = xScale.invert(mouseX)
|
||||||
return (
|
return (
|
||||||
<Row className="items-center gap-2">
|
<Row className="items-center gap-2">
|
||||||
{datum && <Avatar size="xs" avatarUrl={datum.userAvatarUrl} />}
|
{data.obj && <Avatar size="xs" avatarUrl={data.obj.userAvatarUrl} />}
|
||||||
<span className="font-semibold">{formatDateInRange(x, start, end)}</span>
|
<span className="font-semibold">{formatDateInRange(d, start, end)}</span>
|
||||||
<span className="text-greyscale-6">{formatPct(y)}</span>
|
<span className="text-greyscale-6">{formatPct(data.y)}</span>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ const getBetPoints = (answers: Answer[], bets: Bet[]) => {
|
||||||
points.push({
|
points.push({
|
||||||
x: new Date(bet.createdTime),
|
x: new Date(bet.createdTime),
|
||||||
y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared),
|
y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared),
|
||||||
datum: bet,
|
obj: bet,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return points
|
return points
|
||||||
|
@ -181,12 +181,12 @@ export const ChoiceContractChart = (props: {
|
||||||
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
|
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
|
||||||
|
|
||||||
const ChoiceTooltip = useMemo(
|
const ChoiceTooltip = useMemo(
|
||||||
() => (props: TooltipProps<MultiPoint<Bet>>) => {
|
() => (props: TooltipProps<Date, MultiPoint<Bet>>) => {
|
||||||
const { p, xScale } = props
|
const { data, mouseX, xScale } = props
|
||||||
const { x, y, datum } = p
|
|
||||||
const [start, end] = xScale.domain()
|
const [start, end] = xScale.domain()
|
||||||
|
const d = xScale.invert(mouseX)
|
||||||
const legendItems = sortBy(
|
const legendItems = sortBy(
|
||||||
y.map((p, i) => ({
|
data.y.map((p, i) => ({
|
||||||
color: CATEGORY_COLORS[i],
|
color: CATEGORY_COLORS[i],
|
||||||
label: answers[i].text,
|
label: answers[i].text,
|
||||||
value: formatPct(p),
|
value: formatPct(p),
|
||||||
|
@ -197,9 +197,11 @@ export const ChoiceContractChart = (props: {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row className="items-center gap-2">
|
<Row className="items-center gap-2">
|
||||||
{datum && <Avatar size="xxs" avatarUrl={datum.userAvatarUrl} />}
|
{data.obj && (
|
||||||
|
<Avatar size="xxs" avatarUrl={data.obj.userAvatarUrl} />
|
||||||
|
)}
|
||||||
<span className="text-semibold text-base">
|
<span className="text-semibold text-base">
|
||||||
{formatDateInRange(x, start, end)}
|
{formatDateInRange(d, start, end)}
|
||||||
</span>
|
</span>
|
||||||
</Row>
|
</Row>
|
||||||
<Legend className="max-w-xs" items={legendItems} />
|
<Legend className="max-w-xs" items={legendItems} />
|
||||||
|
|
|
@ -21,12 +21,15 @@ const getNumericChartData = (contract: NumericContract) => {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumericChartTooltip = (props: TooltipProps<DistributionPoint>) => {
|
const NumericChartTooltip = (
|
||||||
const { x, y } = props.p
|
props: TooltipProps<number, DistributionPoint>
|
||||||
|
) => {
|
||||||
|
const { data, mouseX, xScale } = props
|
||||||
|
const x = xScale.invert(mouseX)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="text-semibold">{formatLargeNumber(x)}</span>
|
<span className="text-semibold">{formatLargeNumber(x)}</span>
|
||||||
<span className="text-greyscale-6">{formatPct(y, 2)}</span>
|
<span className="text-greyscale-6">{formatPct(data.y, 2)}</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,19 +37,21 @@ const getBetPoints = (bets: Bet[], scaleP: (p: number) => number) => {
|
||||||
return sortBy(bets, (b) => b.createdTime).map((b) => ({
|
return sortBy(bets, (b) => b.createdTime).map((b) => ({
|
||||||
x: new Date(b.createdTime),
|
x: new Date(b.createdTime),
|
||||||
y: scaleP(b.probAfter),
|
y: scaleP(b.probAfter),
|
||||||
datum: b,
|
obj: b,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const PseudoNumericChartTooltip = (props: TooltipProps<HistoryPoint<Bet>>) => {
|
const PseudoNumericChartTooltip = (
|
||||||
const { p, xScale } = props
|
props: TooltipProps<Date, HistoryPoint<Bet>>
|
||||||
const { x, y, datum } = p
|
) => {
|
||||||
|
const { data, mouseX, xScale } = props
|
||||||
const [start, end] = xScale.domain()
|
const [start, end] = xScale.domain()
|
||||||
|
const d = xScale.invert(mouseX)
|
||||||
return (
|
return (
|
||||||
<Row className="items-center gap-2">
|
<Row className="items-center gap-2">
|
||||||
{datum && <Avatar size="xs" avatarUrl={datum.userAvatarUrl} />}
|
{data.obj && <Avatar size="xs" avatarUrl={data.obj.userAvatarUrl} />}
|
||||||
<span className="font-semibold">{formatDateInRange(x, start, end)}</span>
|
<span className="font-semibold">{formatDateInRange(d, start, end)}</span>
|
||||||
<span className="text-greyscale-6">{formatLargeNumber(y)}</span>
|
<span className="text-greyscale-6">{formatLargeNumber(data.y)}</span>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import { range } from 'lodash'
|
import { range } from 'lodash'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ContinuousScale,
|
||||||
SVGChart,
|
SVGChart,
|
||||||
AreaPath,
|
AreaPath,
|
||||||
AreaWithTopStroke,
|
AreaWithTopStroke,
|
||||||
|
@ -31,6 +32,19 @@ const getTickValues = (min: number, max: number, n: number) => {
|
||||||
return [min, ...range(1, n - 1).map((i) => min + step * i), max]
|
return [min, ...range(1, n - 1).map((i) => min + step * i), max]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const betAtPointSelector = <X, Y, P extends Point<X, Y>>(
|
||||||
|
data: P[],
|
||||||
|
xScale: ContinuousScale<X>
|
||||||
|
) => {
|
||||||
|
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 = <P extends DistributionPoint>(props: {
|
export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
data: P[]
|
data: P[]
|
||||||
w: number
|
w: number
|
||||||
|
@ -39,7 +53,7 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
xScale: ScaleContinuousNumeric<number, number>
|
xScale: ScaleContinuousNumeric<number, number>
|
||||||
yScale: ScaleContinuousNumeric<number, number>
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
onMouseOver?: (p: P | undefined) => void
|
onMouseOver?: (p: P | undefined) => void
|
||||||
Tooltip?: TooltipComponent<P>
|
Tooltip?: TooltipComponent<number, P>
|
||||||
}) => {
|
}) => {
|
||||||
const { color, data, yScale, w, h, Tooltip } = props
|
const { color, data, yScale, w, h, Tooltip } = props
|
||||||
|
|
||||||
|
@ -50,7 +64,6 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
const px = useCallback((p: P) => xScale(p.x), [xScale])
|
const px = useCallback((p: P) => xScale(p.x), [xScale])
|
||||||
const py0 = yScale(yScale.domain()[0])
|
const py0 = yScale(yScale.domain()[0])
|
||||||
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
|
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
|
||||||
const xBisector = bisector((p: P) => p.x)
|
|
||||||
|
|
||||||
const { xAxis, yAxis } = useMemo(() => {
|
const { xAxis, yAxis } = useMemo(() => {
|
||||||
const xAxis = axisBottom<number>(xScale).ticks(w / 100)
|
const xAxis = axisBottom<number>(xScale).ticks(w / 100)
|
||||||
|
@ -58,6 +71,8 @@ export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||||
return { xAxis, yAxis }
|
return { xAxis, yAxis }
|
||||||
}, [w, xScale, yScale])
|
}, [w, xScale, yScale])
|
||||||
|
|
||||||
|
const onMouseOver = useEvent(betAtPointSelector(data, xScale))
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||||
|
@ -69,14 +84,6 @@ export const DistributionChart = <P extends DistributionPoint>(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 (
|
return (
|
||||||
<SVGChart
|
<SVGChart
|
||||||
w={w}
|
w={w}
|
||||||
|
@ -107,7 +114,7 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||||
xScale: ScaleTime<number, number>
|
xScale: ScaleTime<number, number>
|
||||||
yScale: ScaleContinuousNumeric<number, number>
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
onMouseOver?: (p: P | undefined) => void
|
onMouseOver?: (p: P | undefined) => void
|
||||||
Tooltip?: TooltipComponent<P>
|
Tooltip?: TooltipComponent<Date, P>
|
||||||
pct?: boolean
|
pct?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const { colors, data, yScale, w, h, Tooltip, pct } = props
|
const { colors, data, yScale, w, h, Tooltip, pct } = props
|
||||||
|
@ -119,7 +126,6 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||||
const px = useCallback((p: SP) => xScale(p.data.x), [xScale])
|
const px = useCallback((p: SP) => xScale(p.data.x), [xScale])
|
||||||
const py0 = useCallback((p: SP) => yScale(p[0]), [yScale])
|
const py0 = useCallback((p: SP) => yScale(p[0]), [yScale])
|
||||||
const py1 = useCallback((p: SP) => yScale(p[1]), [yScale])
|
const py1 = useCallback((p: SP) => yScale(p[1]), [yScale])
|
||||||
const xBisector = bisector((p: P) => p.x)
|
|
||||||
|
|
||||||
const { xAxis, yAxis } = useMemo(() => {
|
const { xAxis, yAxis } = useMemo(() => {
|
||||||
const [min, max] = yScale.domain()
|
const [min, max] = yScale.domain()
|
||||||
|
@ -141,6 +147,8 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||||
return d3Stack(data)
|
return d3Stack(data)
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
|
const onMouseOver = useEvent(betAtPointSelector(data, xScale))
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||||
|
@ -152,14 +160,6 @@ export const MultiValueHistoryChart = <P extends MultiPoint>(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 (
|
return (
|
||||||
<SVGChart
|
<SVGChart
|
||||||
w={w}
|
w={w}
|
||||||
|
@ -193,7 +193,7 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||||
xScale: ScaleTime<number, number>
|
xScale: ScaleTime<number, number>
|
||||||
yScale: ScaleContinuousNumeric<number, number>
|
yScale: ScaleContinuousNumeric<number, number>
|
||||||
onMouseOver?: (p: P | undefined) => void
|
onMouseOver?: (p: P | undefined) => void
|
||||||
Tooltip?: TooltipComponent<P>
|
Tooltip?: TooltipComponent<Date, P>
|
||||||
pct?: boolean
|
pct?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const { color, data, yScale, w, h, Tooltip, pct } = props
|
const { color, data, yScale, w, h, Tooltip, pct } = props
|
||||||
|
@ -204,7 +204,6 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||||
const px = useCallback((p: P) => xScale(p.x), [xScale])
|
const px = useCallback((p: P) => xScale(p.x), [xScale])
|
||||||
const py0 = yScale(yScale.domain()[0])
|
const py0 = yScale(yScale.domain()[0])
|
||||||
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
|
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
|
||||||
const xBisector = bisector((p: P) => p.x)
|
|
||||||
|
|
||||||
const { xAxis, yAxis } = useMemo(() => {
|
const { xAxis, yAxis } = useMemo(() => {
|
||||||
const [min, max] = yScale.domain()
|
const [min, max] = yScale.domain()
|
||||||
|
@ -218,6 +217,8 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||||
return { xAxis, yAxis }
|
return { xAxis, yAxis }
|
||||||
}, [w, h, pct, xScale, yScale])
|
}, [w, h, pct, xScale, yScale])
|
||||||
|
|
||||||
|
const onMouseOver = useEvent(betAtPointSelector(data, xScale))
|
||||||
|
|
||||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||||
if (ev.selection) {
|
if (ev.selection) {
|
||||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||||
|
@ -229,14 +230,6 @@ export const SingleValueHistoryChart = <P extends HistoryPoint>(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 (
|
return (
|
||||||
<SVGChart
|
<SVGChart
|
||||||
w={w}
|
w={w}
|
||||||
|
|
|
@ -17,7 +17,12 @@ import clsx from 'clsx'
|
||||||
|
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
|
||||||
export type Point<X, Y, T = unknown> = { x: X; y: Y; datum?: T }
|
export type Point<X, Y, T = unknown> = { x: X; y: Y; obj?: T }
|
||||||
|
|
||||||
|
export interface ContinuousScale<T> extends AxisScale<T> {
|
||||||
|
invert(n: number): T
|
||||||
|
}
|
||||||
|
|
||||||
export type XScale<P> = P extends Point<infer X, infer _> ? AxisScale<X> : never
|
export type XScale<P> = P extends Point<infer X, infer _> ? AxisScale<X> : never
|
||||||
export type YScale<P> = P extends Point<infer _, infer Y> ? AxisScale<Y> : never
|
export type YScale<P> = P extends Point<infer _, infer Y> ? AxisScale<Y> : never
|
||||||
|
|
||||||
|
@ -118,18 +123,18 @@ export const AreaWithTopStroke = <P,>(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SVGChart = <X, Y, P extends Point<X, Y>>(props: {
|
export const SVGChart = <X, TT>(props: {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
w: number
|
w: number
|
||||||
h: number
|
h: number
|
||||||
xAxis: Axis<X>
|
xAxis: Axis<X>
|
||||||
yAxis: Axis<number>
|
yAxis: Axis<number>
|
||||||
onSelect?: (ev: D3BrushEvent<any>) => void
|
onSelect?: (ev: D3BrushEvent<any>) => void
|
||||||
onMouseOver?: (mouseX: number, mouseY: number) => P | undefined
|
onMouseOver?: (mouseX: number, mouseY: number) => TT | undefined
|
||||||
Tooltip?: TooltipComponent<P>
|
Tooltip?: TooltipComponent<X, TT>
|
||||||
}) => {
|
}) => {
|
||||||
const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props
|
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 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
|
||||||
|
@ -148,7 +153,7 @@ export const SVGChart = <X, Y, P extends Point<X, Y>>(props: {
|
||||||
if (!justSelected.current) {
|
if (!justSelected.current) {
|
||||||
justSelected.current = true
|
justSelected.current = true
|
||||||
onSelect(ev)
|
onSelect(ev)
|
||||||
setMouseState(undefined)
|
setMouse(undefined)
|
||||||
if (overlayRef.current) {
|
if (overlayRef.current) {
|
||||||
select(overlayRef.current).call(brush.clear)
|
select(overlayRef.current).call(brush.clear)
|
||||||
}
|
}
|
||||||
|
@ -168,26 +173,32 @@ export const SVGChart = <X, Y, P extends Point<X, Y>>(props: {
|
||||||
|
|
||||||
const onPointerMove = (ev: React.PointerEvent) => {
|
const onPointerMove = (ev: React.PointerEvent) => {
|
||||||
if (ev.pointerType === 'mouse' && onMouseOver) {
|
if (ev.pointerType === 'mouse' && onMouseOver) {
|
||||||
const [mouseX, mouseY] = pointer(ev)
|
const [x, y] = pointer(ev)
|
||||||
const p = onMouseOver(mouseX, mouseY)
|
const data = onMouseOver(x, y)
|
||||||
if (p != null) {
|
if (data !== undefined) {
|
||||||
const pos = getTooltipPosition(mouseX, mouseY, innerW, innerH)
|
setMouse({ x, y, data })
|
||||||
setMouseState({ pos, p })
|
|
||||||
} else {
|
} else {
|
||||||
setMouseState(undefined)
|
setMouse(undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPointerLeave = () => {
|
const onPointerLeave = () => {
|
||||||
setMouseState(undefined)
|
setMouse(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{mouseState && Tooltip && (
|
{mouse && Tooltip && (
|
||||||
<TooltipContainer pos={mouseState.pos}>
|
<TooltipContainer
|
||||||
<Tooltip xScale={xAxis.scale()} p={mouseState.p} />
|
pos={getTooltipPosition(mouse.x, mouse.y, innerW, innerH)}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
xScale={xAxis.scale()}
|
||||||
|
mouseX={mouse.x}
|
||||||
|
mouseY={mouse.y}
|
||||||
|
data={mouse.data}
|
||||||
|
/>
|
||||||
</TooltipContainer>
|
</TooltipContainer>
|
||||||
)}
|
)}
|
||||||
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
||||||
|
@ -243,8 +254,13 @@ export const getTooltipPosition = (
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TooltipProps<P> = { p: P; xScale: XScale<P> }
|
export type TooltipProps<X, T> = {
|
||||||
export type TooltipComponent<P> = React.ComponentType<TooltipProps<P>>
|
mouseX: number
|
||||||
|
mouseY: number
|
||||||
|
xScale: ContinuousScale<X>
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
export type TooltipComponent<X, T> = React.ComponentType<TooltipProps<X, T>>
|
||||||
export const TooltipContainer = (props: {
|
export const TooltipContainer = (props: {
|
||||||
pos: TooltipPosition
|
pos: TooltipPosition
|
||||||
className?: string
|
className?: string
|
||||||
|
|
Loading…
Reference in New Issue
Block a user