Improve typing for chart tooltip stuff (#962)
This commit is contained in:
parent
9fc1e855ff
commit
8929b2e6ba
|
@ -8,6 +8,7 @@ import { BinaryContract } from 'common/contract'
|
|||
import { DAY_MS } from 'common/util/time'
|
||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||
import {
|
||||
TooltipProps,
|
||||
MARGIN_X,
|
||||
MARGIN_Y,
|
||||
getDateRange,
|
||||
|
@ -15,10 +16,7 @@ import {
|
|||
formatDateInRange,
|
||||
formatPct,
|
||||
} from '../helpers'
|
||||
import {
|
||||
SingleValueHistoryTooltipProps,
|
||||
SingleValueHistoryChart,
|
||||
} from '../generic-charts'
|
||||
import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
|
@ -31,7 +29,7 @@ const getBetPoints = (bets: Bet[]) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const BinaryChartTooltip = (props: SingleValueHistoryTooltipProps<Bet>) => {
|
||||
const BinaryChartTooltip = (props: TooltipProps<HistoryPoint<Bet>>) => {
|
||||
const { p, xScale } = props
|
||||
const { x, y, datum } = p
|
||||
const [start, end] = xScale.domain()
|
||||
|
|
|
@ -10,6 +10,7 @@ import { useIsMobile } from 'web/hooks/use-is-mobile'
|
|||
import { DAY_MS } from 'common/util/time'
|
||||
import {
|
||||
Legend,
|
||||
TooltipProps,
|
||||
MARGIN_X,
|
||||
MARGIN_Y,
|
||||
getDateRange,
|
||||
|
@ -17,11 +18,7 @@ import {
|
|||
formatPct,
|
||||
formatDateInRange,
|
||||
} from '../helpers'
|
||||
import {
|
||||
MultiPoint,
|
||||
MultiValueHistoryChart,
|
||||
MultiValueHistoryTooltipProps,
|
||||
} from '../generic-charts'
|
||||
import { MultiPoint, MultiValueHistoryChart } from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
|
@ -161,7 +158,7 @@ export const ChoiceContractChart = (props: {
|
|||
const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0])
|
||||
|
||||
const ChoiceTooltip = useMemo(
|
||||
() => (props: MultiValueHistoryTooltipProps<Bet>) => {
|
||||
() => (props: TooltipProps<MultiPoint<Bet>>) => {
|
||||
const { p, xScale } = props
|
||||
const { x, y, datum } = p
|
||||
const [start, end] = xScale.domain()
|
||||
|
|
|
@ -7,11 +7,8 @@ import { getDpmOutcomeProbabilities } from 'common/calculate-dpm'
|
|||
import { NumericContract } from 'common/contract'
|
||||
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||
import { MARGIN_X, MARGIN_Y, formatPct } from '../helpers'
|
||||
import {
|
||||
SingleValueDistributionChart,
|
||||
SingleValueDistributionTooltipProps,
|
||||
} from '../generic-charts'
|
||||
import { TooltipProps, MARGIN_X, MARGIN_Y, formatPct } from '../helpers'
|
||||
import { DistributionPoint, DistributionChart } from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
|
||||
const getNumericChartData = (contract: NumericContract) => {
|
||||
|
@ -24,9 +21,8 @@ const getNumericChartData = (contract: NumericContract) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const NumericChartTooltip = (props: SingleValueDistributionTooltipProps) => {
|
||||
const { p } = props
|
||||
const { x, y } = p
|
||||
const NumericChartTooltip = (props: TooltipProps<DistributionPoint>) => {
|
||||
const { x, y } = props.p
|
||||
return (
|
||||
<span className="text-sm">
|
||||
<strong>{formatPct(y, 2)}</strong> {formatLargeNumber(x)}
|
||||
|
@ -51,7 +47,7 @@ export const NumericContractChart = (props: {
|
|||
return (
|
||||
<div ref={containerRef}>
|
||||
{width > 0 && (
|
||||
<SingleValueDistributionChart
|
||||
<DistributionChart
|
||||
w={width}
|
||||
h={height}
|
||||
xScale={xScale}
|
||||
|
|
|
@ -10,16 +10,14 @@ import { PseudoNumericContract } from 'common/contract'
|
|||
import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants'
|
||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||
import {
|
||||
TooltipProps,
|
||||
MARGIN_X,
|
||||
MARGIN_Y,
|
||||
getDateRange,
|
||||
getRightmostVisibleDate,
|
||||
formatDateInRange,
|
||||
} from '../helpers'
|
||||
import {
|
||||
SingleValueHistoryChart,
|
||||
SingleValueHistoryTooltipProps,
|
||||
} from '../generic-charts'
|
||||
import { HistoryPoint, SingleValueHistoryChart } from '../generic-charts'
|
||||
import { useElementWidth } from 'web/hooks/use-element-width'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
|
@ -43,9 +41,7 @@ const getBetPoints = (bets: Bet[], scaleP: (p: number) => number) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const PseudoNumericChartTooltip = (
|
||||
props: SingleValueHistoryTooltipProps<Bet>
|
||||
) => {
|
||||
const PseudoNumericChartTooltip = (props: TooltipProps<HistoryPoint<Bet>>) => {
|
||||
const { p, xScale } = props
|
||||
const { x, y, datum } = p
|
||||
const [start, end] = xScale.domain()
|
||||
|
|
|
@ -16,28 +16,29 @@ import {
|
|||
SVGChart,
|
||||
AreaPath,
|
||||
AreaWithTopStroke,
|
||||
TooltipContent,
|
||||
Point,
|
||||
TooltipComponent,
|
||||
formatPct,
|
||||
} from './helpers'
|
||||
import { useEvent } from 'web/hooks/use-event'
|
||||
|
||||
export type MultiPoint<T = never> = { x: Date; y: number[]; datum?: T }
|
||||
export type HistoryPoint<T = never> = { x: Date; y: number; datum?: T }
|
||||
export type DistributionPoint<T = never> = { x: number; y: number; datum?: T }
|
||||
export type MultiPoint<T = unknown> = Point<Date, number[], T>
|
||||
export type HistoryPoint<T = unknown> = Point<Date, number, T>
|
||||
export type DistributionPoint<T = unknown> = Point<number, number, T>
|
||||
|
||||
const getTickValues = (min: number, max: number, n: number) => {
|
||||
const step = (max - min) / (n - 1)
|
||||
return [min, ...range(1, n - 1).map((i) => min + step * i), max]
|
||||
}
|
||||
|
||||
export const SingleValueDistributionChart = <T,>(props: {
|
||||
data: DistributionPoint<T>[]
|
||||
export const DistributionChart = <P extends DistributionPoint>(props: {
|
||||
data: P[]
|
||||
w: number
|
||||
h: number
|
||||
color: string
|
||||
xScale: ScaleContinuousNumeric<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
Tooltip?: TooltipContent<SingleValueDistributionTooltipProps<T>>
|
||||
Tooltip?: TooltipComponent<P>
|
||||
}) => {
|
||||
const { color, data, yScale, w, h, Tooltip } = props
|
||||
|
||||
|
@ -45,10 +46,10 @@ export const SingleValueDistributionChart = <T,>(props: {
|
|||
useState<ScaleContinuousNumeric<number, number>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
||||
const px = useCallback((p: DistributionPoint<T>) => xScale(p.x), [xScale])
|
||||
const px = useCallback((p: P) => xScale(p.x), [xScale])
|
||||
const py0 = yScale(yScale.domain()[0])
|
||||
const py1 = useCallback((p: DistributionPoint<T>) => yScale(p.y), [yScale])
|
||||
const xBisector = bisector((p: DistributionPoint<T>) => p.x)
|
||||
const py1 = useCallback((p: P) => yScale(p.y), [yScale])
|
||||
const xBisector = bisector((p: P) => p.x)
|
||||
|
||||
const { xAxis, yAxis } = useMemo(() => {
|
||||
const xAxis = axisBottom<number>(xScale).ticks(w / 100)
|
||||
|
@ -56,7 +57,7 @@ export const SingleValueDistributionChart = <T,>(props: {
|
|||
return { xAxis, yAxis }
|
||||
}, [w, xScale, yScale])
|
||||
|
||||
const onSelect = useEvent((ev: D3BrushEvent<DistributionPoint<T>>) => {
|
||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||
if (ev.selection) {
|
||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||
setViewXScale(() =>
|
||||
|
@ -75,7 +76,7 @@ export const SingleValueDistributionChart = <T,>(props: {
|
|||
// so your queryX is out of bounds
|
||||
return
|
||||
}
|
||||
return { x: queryX, y: item.y, datum: item.datum }
|
||||
return { ...item, x: queryX }
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -100,19 +101,14 @@ export const SingleValueDistributionChart = <T,>(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export type SingleValueDistributionTooltipProps<T = unknown> = {
|
||||
p: DistributionPoint<T>
|
||||
xScale: React.ComponentProps<typeof SingleValueDistributionChart<T>>['xScale']
|
||||
}
|
||||
|
||||
export const MultiValueHistoryChart = <T,>(props: {
|
||||
data: MultiPoint<T>[]
|
||||
export const MultiValueHistoryChart = <P extends MultiPoint>(props: {
|
||||
data: P[]
|
||||
w: number
|
||||
h: number
|
||||
colors: readonly string[]
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
Tooltip?: TooltipContent<MultiValueHistoryTooltipProps<T>>
|
||||
Tooltip?: TooltipComponent<P>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { colors, data, yScale, w, h, Tooltip, pct } = props
|
||||
|
@ -120,11 +116,11 @@ export const MultiValueHistoryChart = <T,>(props: {
|
|||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
||||
type SP = SeriesPoint<MultiPoint<T>>
|
||||
type SP = SeriesPoint<P>
|
||||
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: MultiPoint<T>) => p.x)
|
||||
const xBisector = bisector((p: P) => p.x)
|
||||
|
||||
const { xAxis, yAxis } = useMemo(() => {
|
||||
const [min, max] = yScale.domain()
|
||||
|
@ -139,14 +135,14 @@ export const MultiValueHistoryChart = <T,>(props: {
|
|||
}, [w, h, pct, xScale, yScale])
|
||||
|
||||
const series = useMemo(() => {
|
||||
const d3Stack = stack<MultiPoint<T>, number>()
|
||||
const d3Stack = stack<P, number>()
|
||||
.keys(range(0, Math.max(...data.map(({ y }) => y.length))))
|
||||
.value(({ y }, o) => y[o])
|
||||
.order(stackOrderReverse)
|
||||
return d3Stack(data)
|
||||
}, [data])
|
||||
|
||||
const onSelect = useEvent((ev: D3BrushEvent<MultiPoint<T>>) => {
|
||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||
if (ev.selection) {
|
||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||
setViewXScale(() =>
|
||||
|
@ -165,7 +161,7 @@ export const MultiValueHistoryChart = <T,>(props: {
|
|||
// so your queryX is out of bounds
|
||||
return
|
||||
}
|
||||
return { x: queryX, y: item.y, datum: item.datum }
|
||||
return { ...item, x: queryX }
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -193,19 +189,14 @@ export const MultiValueHistoryChart = <T,>(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export type MultiValueHistoryTooltipProps<T = unknown> = {
|
||||
p: MultiPoint<T>
|
||||
xScale: React.ComponentProps<typeof MultiValueHistoryChart<T>>['xScale']
|
||||
}
|
||||
|
||||
export const SingleValueHistoryChart = <T,>(props: {
|
||||
data: HistoryPoint<T>[]
|
||||
export const SingleValueHistoryChart = <P extends HistoryPoint>(props: {
|
||||
data: P[]
|
||||
w: number
|
||||
h: number
|
||||
color: string
|
||||
xScale: ScaleTime<number, number>
|
||||
yScale: ScaleContinuousNumeric<number, number>
|
||||
Tooltip?: TooltipContent<SingleValueHistoryTooltipProps<T>>
|
||||
Tooltip?: TooltipComponent<P>
|
||||
pct?: boolean
|
||||
}) => {
|
||||
const { color, data, pct, yScale, w, h, Tooltip } = props
|
||||
|
@ -213,10 +204,10 @@ export const SingleValueHistoryChart = <T,>(props: {
|
|||
const [viewXScale, setViewXScale] = useState<ScaleTime<number, number>>()
|
||||
const xScale = viewXScale ?? props.xScale
|
||||
|
||||
const px = useCallback((p: HistoryPoint<T>) => xScale(p.x), [xScale])
|
||||
const px = useCallback((p: P) => xScale(p.x), [xScale])
|
||||
const py0 = yScale(yScale.domain()[0])
|
||||
const py1 = useCallback((p: HistoryPoint<T>) => yScale(p.y), [yScale])
|
||||
const xBisector = bisector((p: HistoryPoint<T>) => p.x)
|
||||
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()
|
||||
|
@ -230,7 +221,7 @@ export const SingleValueHistoryChart = <T,>(props: {
|
|||
return { xAxis, yAxis }
|
||||
}, [w, h, pct, xScale, yScale])
|
||||
|
||||
const onSelect = useEvent((ev: D3BrushEvent<HistoryPoint<T>>) => {
|
||||
const onSelect = useEvent((ev: D3BrushEvent<P>) => {
|
||||
if (ev.selection) {
|
||||
const [mouseX0, mouseX1] = ev.selection as [number, number]
|
||||
setViewXScale(() =>
|
||||
|
@ -249,7 +240,7 @@ export const SingleValueHistoryChart = <T,>(props: {
|
|||
// so your queryX is out of bounds
|
||||
return
|
||||
}
|
||||
return { x: queryX, y: item.y, datum: item.datum }
|
||||
return { ...item, x: queryX }
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -273,8 +264,3 @@ export const SingleValueHistoryChart = <T,>(props: {
|
|||
</SVGChart>
|
||||
)
|
||||
}
|
||||
|
||||
export type SingleValueHistoryTooltipProps<T = unknown> = {
|
||||
p: HistoryPoint<T>
|
||||
xScale: React.ComponentProps<typeof SingleValueHistoryChart<T>>['xScale']
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
useState,
|
||||
} from 'react'
|
||||
import { pointer, select } from 'd3-selection'
|
||||
import { Axis } from 'd3-axis'
|
||||
import { Axis, AxisScale } from 'd3-axis'
|
||||
import { brushX, D3BrushEvent } from 'd3-brush'
|
||||
import { area, line, curveStepAfter, CurveFactory } from 'd3-shape'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
@ -18,6 +18,10 @@ import clsx from 'clsx'
|
|||
import { Contract } from 'common/contract'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
|
||||
export type Point<X, Y, T = unknown> = { x: X; y: Y; datum?: T }
|
||||
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 const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 }
|
||||
export const MARGIN_X = MARGIN.right + MARGIN.left
|
||||
export const MARGIN_Y = MARGIN.top + MARGIN.bottom
|
||||
|
@ -113,15 +117,15 @@ export const AreaWithTopStroke = <P,>(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export const SVGChart = <X, Y, P, XS>(props: {
|
||||
export const SVGChart = <X, Y, P extends Point<X, Y>>(props: {
|
||||
children: ReactNode
|
||||
w: number
|
||||
h: number
|
||||
xAxis: Axis<X>
|
||||
yAxis: Axis<Y>
|
||||
yAxis: Axis<number>
|
||||
onSelect?: (ev: D3BrushEvent<any>) => void
|
||||
onMouseOver?: (mouseX: number, mouseY: number) => P | undefined
|
||||
Tooltip?: TooltipContent<{ xScale: XS } & { p: P }>
|
||||
Tooltip?: TooltipComponent<P>
|
||||
}) => {
|
||||
const { children, w, h, xAxis, yAxis, onMouseOver, onSelect, Tooltip } = props
|
||||
const [mouseState, setMouseState] = useState<TooltipPosition & { p: P }>()
|
||||
|
@ -181,7 +185,7 @@ export const SVGChart = <X, Y, P, XS>(props: {
|
|||
<div className="relative">
|
||||
{mouseState && Tooltip && (
|
||||
<TooltipContainer top={mouseState.top} left={mouseState.left}>
|
||||
<Tooltip xScale={xAxis.scale() as XS} p={mouseState.p} />
|
||||
<Tooltip xScale={xAxis.scale()} p={mouseState.p} />
|
||||
</TooltipContainer>
|
||||
)}
|
||||
<svg className="w-full" width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
|
||||
|
@ -210,7 +214,8 @@ export const SVGChart = <X, Y, P, XS>(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export type TooltipContent<P> = React.ComponentType<P>
|
||||
export type TooltipProps<P> = { p: P; xScale: XScale<P> }
|
||||
export type TooltipComponent<P> = React.ComponentType<TooltipProps<P>>
|
||||
export type TooltipPosition = { top: number; left: number }
|
||||
export const TooltipContainer = (
|
||||
props: TooltipPosition & { className?: string; children: React.ReactNode }
|
||||
|
|
Loading…
Reference in New Issue
Block a user