diff --git a/web/components/charts/contract/binary.tsx b/web/components/charts/contract/binary.tsx index d640dacb..6703ed82 100644 --- a/web/components/charts/contract/binary.tsx +++ b/web/components/charts/contract/binary.tsx @@ -20,6 +20,7 @@ const getBetPoints = (bets: Bet[]) => { return sortBy(bets, (b) => b.createdTime).map((b) => ({ x: new Date(b.createdTime), y: b.probAfter, + datum: b, })) } diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx index ffe6d979..ac39992c 100644 --- a/web/components/charts/contract/choice.tsx +++ b/web/components/charts/contract/choice.tsx @@ -92,28 +92,13 @@ const getTrackedAnswers = ( ).slice(0, topN) } -const getStartPoint = (answers: Answer[], start: Date) => { - return { x: start, y: answers.map((_) => 0) } -} - -const getEndPoint = ( - answers: Answer[], - contract: FreeResponseContract | MultipleChoiceContract, - end: Date -) => { - return { - x: end, - y: answers.map((a) => getOutcomeProbability(contract, a.id)), - } -} - const getBetPoints = (answers: Answer[], bets: Bet[]) => { const sortedBets = sortBy(bets, (b) => b.createdTime) const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome) const sharesByOutcome = Object.fromEntries( Object.keys(betsByOutcome).map((outcome) => [outcome, 0]) ) - const points: MultiPoint[] = [] + const points: MultiPoint[] = [] for (const bet of sortedBets) { const { outcome, shares } = bet sharesByOutcome[outcome] += shares @@ -124,6 +109,7 @@ const getBetPoints = (answers: Answer[], bets: Bet[]) => { points.push({ x: new Date(bet.createdTime), y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared), + datum: bet, }) } return points @@ -135,7 +121,7 @@ export const ChoiceContractChart = (props: { height?: number }) => { const { contract, bets } = props - const [contractStart, contractEnd] = getDateRange(contract) + const [start, end] = getDateRange(contract) const answers = useMemo( () => getTrackedAnswers(contract, CATEGORY_COLORS.length), [contract] @@ -143,18 +129,21 @@ export const ChoiceContractChart = (props: { const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets]) const data = useMemo( () => [ - getStartPoint(answers, contractStart), + { x: start, y: answers.map((_) => 0) }, ...betPoints, - getEndPoint(answers, contract, contractEnd ?? MAX_DATE), + { + x: end ?? MAX_DATE, + y: answers.map((a) => getOutcomeProbability(contract, a.id)), + }, ], - [answers, contract, betPoints, contractStart, contractEnd] + [answers, contract, betPoints, start, end] ) const rightmostDate = getRightmostVisibleDate( - contractEnd, + end, last(betPoints)?.x, new Date(Date.now()) ) - const visibleRange = [contractStart, rightmostDate] + const visibleRange = [start, rightmostDate] const isMobile = useIsMobile(800) const containerRef = useRef(null) const width = useElementWidth(containerRef) ?? 0 diff --git a/web/components/charts/contract/pseudo-numeric.tsx b/web/components/charts/contract/pseudo-numeric.tsx index 43fa8d15..4bb5d469 100644 --- a/web/components/charts/contract/pseudo-numeric.tsx +++ b/web/components/charts/contract/pseudo-numeric.tsx @@ -32,6 +32,7 @@ const getBetPoints = (bets: Bet[], scaleP: (p: number) => number) => { return sortBy(bets, (b) => b.createdTime).map((b) => ({ x: new Date(b.createdTime), y: scaleP(b.probAfter), + datum: b, })) } diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx index fc540226..dae59a0d 100644 --- a/web/components/charts/generic-charts.tsx +++ b/web/components/charts/generic-charts.tsx @@ -25,10 +25,11 @@ import { formatLargeNumber } from 'common/util/format' import { useEvent } from 'web/hooks/use-event' import { Row } from 'web/components/layout/row' -export type MultiPoint = { x: Date; y: number[] } -export type HistoryPoint = { x: Date; y: number } -export type DistributionPoint = { x: number; y: number } -export type PositionValue

= TooltipPosition & { p: P } +export type MultiPoint = { x: Date; y: number[]; datum?: T } +export type HistoryPoint = { x: Date; y: number; datum?: T } +export type DistributionPoint = { x: number; y: number; datum?: T } + +type PositionValue

= TooltipPosition & { p: P } const formatPct = (n: number, digits?: number) => { return `${(n * 100).toFixed(digits ?? 0)}%` @@ -100,8 +101,8 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => { ) } -export const SingleValueDistributionChart = (props: { - data: DistributionPoint[] +export const SingleValueDistributionChart = (props: { + data: DistributionPoint[] w: number h: number color: string @@ -115,13 +116,13 @@ export const SingleValueDistributionChart = (props: { const [viewXScale, setViewXScale] = useState>() const [mouseState, setMouseState] = - useState>() + useState>>() const xScale = viewXScale ?? props.xScale - const px = useCallback((p: DistributionPoint) => xScale(p.x), [xScale]) + const px = useCallback((p: DistributionPoint) => xScale(p.x), [xScale]) const py0 = yScale(yScale.domain()[0]) - const py1 = useCallback((p: DistributionPoint) => yScale(p.y), [yScale]) - const xBisector = bisector((p: DistributionPoint) => p.x) + const py1 = useCallback((p: DistributionPoint) => yScale(p.y), [yScale]) + const xBisector = bisector((p: DistributionPoint) => p.x) const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { const fmtX = (n: number) => formatLargeNumber(n) @@ -131,7 +132,7 @@ export const SingleValueDistributionChart = (props: { return { fmtX, fmtY, xAxis, yAxis } }, [w, xScale, yScale]) - const onSelect = useEvent((ev: D3BrushEvent) => { + const onSelect = useEvent((ev: D3BrushEvent>) => { if (ev.selection) { const [mouseX0, mouseX1] = ev.selection as [number, number] setViewXScale(() => @@ -154,7 +155,7 @@ export const SingleValueDistributionChart = (props: { // so your queryX is out of bounds return } - const p = { x: queryX, y: item.y } + const p = { x: queryX, y: item.y, datum: item.datum } setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) } }) @@ -192,8 +193,8 @@ export const SingleValueDistributionChart = (props: { ) } -export const MultiValueHistoryChart = (props: { - data: MultiPoint[] +export const MultiValueHistoryChart = (props: { + data: MultiPoint[] w: number h: number labels: readonly string[] @@ -205,14 +206,14 @@ export const MultiValueHistoryChart = (props: { const { colors, data, yScale, labels, w, h, pct } = props const [viewXScale, setViewXScale] = useState>() - const [mouseState, setMouseState] = useState>() + const [mouseState, setMouseState] = useState>>() const xScale = viewXScale ?? props.xScale - type SP = SeriesPoint + type SP = SeriesPoint> 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) => p.x) + const xBisector = bisector((p: MultiPoint) => p.x) const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { const [start, end] = xScale.domain() @@ -230,14 +231,14 @@ export const MultiValueHistoryChart = (props: { }, [w, h, pct, xScale, yScale]) const series = useMemo(() => { - const d3Stack = stack() + const d3Stack = stack, number>() .keys(range(0, labels.length)) .value(({ y }, o) => y[o]) .order(stackOrderReverse) return d3Stack(data) }, [data, labels.length]) - const onSelect = useEvent((ev: D3BrushEvent) => { + const onSelect = useEvent((ev: D3BrushEvent>) => { if (ev.selection) { const [mouseX0, mouseX1] = ev.selection as [number, number] setViewXScale(() => @@ -260,7 +261,7 @@ export const MultiValueHistoryChart = (props: { // so your queryX is out of bounds return } - const p = { x: queryX, y: item.y } + const p = { x: queryX, y: item.y, datum: item.datum } setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) } }) @@ -313,8 +314,8 @@ export const MultiValueHistoryChart = (props: { ) } -export const SingleValueHistoryChart = (props: { - data: HistoryPoint[] +export const SingleValueHistoryChart = (props: { + data: HistoryPoint[] w: number h: number color: string @@ -325,13 +326,13 @@ export const SingleValueHistoryChart = (props: { const { color, data, pct, yScale, w, h } = props const [viewXScale, setViewXScale] = useState>() - const [mouseState, setMouseState] = useState>() + const [mouseState, setMouseState] = useState>>() const xScale = viewXScale ?? props.xScale - const px = useCallback((p: HistoryPoint) => xScale(p.x), [xScale]) + const px = useCallback((p: HistoryPoint) => xScale(p.x), [xScale]) const py0 = yScale(yScale.domain()[0]) - const py1 = useCallback((p: HistoryPoint) => yScale(p.y), [yScale]) - const xBisector = bisector((p: HistoryPoint) => p.x) + const py1 = useCallback((p: HistoryPoint) => yScale(p.y), [yScale]) + const xBisector = bisector((p: HistoryPoint) => p.x) const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { const [start, end] = xScale.domain() @@ -347,7 +348,7 @@ export const SingleValueHistoryChart = (props: { return { fmtX, fmtY, xAxis, yAxis } }, [w, h, pct, xScale, yScale]) - const onSelect = useEvent((ev: D3BrushEvent) => { + const onSelect = useEvent((ev: D3BrushEvent>) => { if (ev.selection) { const [mouseX0, mouseX1] = ev.selection as [number, number] setViewXScale(() => @@ -370,7 +371,7 @@ export const SingleValueHistoryChart = (props: { // so your queryX is out of bounds return } - const p = { x: queryX, y: item.y } + const p = { x: queryX, y: item.y, datum: item.datum } setMouseState({ top: mouseY - 10, left: mouseX + 60, p }) } }) @@ -383,7 +384,7 @@ export const SingleValueHistoryChart = (props: {

{mouseState && ( - {fmtY(mouseState.p.y)} {fmtX(mouseState.p.x)} + {fmtY(mouseState.p.y)} {fmtX(mouseState.p.x)}{' '} )}