import { DatumValue } from '@nivo/core' import { ResponsiveLine } from '@nivo/line' import dayjs from 'dayjs' import _ from 'lodash' import { Bet } from '../../../common/bet' import { Contract } from '../../../common/contract' import { getOutcomeProbability } from '../../../common/calculate' import { useBets } from '../../hooks/use-bets' import { useWindowSize } from '../../hooks/use-window-size' export function AnswersGraph(props: { contract: Contract; bets: Bet[] }) { const { contract } = props const { resolutionTime, closeTime, answers, totalShares } = contract const bets = (useBets(contract.id) ?? props.bets).filter((bet) => !bet.sale) const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome( bets, totalShares ) const isClosed = !!closeTime && Date.now() > closeTime const latestTime = dayjs( resolutionTime && isClosed ? Math.min(resolutionTime, closeTime) : isClosed ? closeTime : resolutionTime ?? Date.now() ) const { width } = useWindowSize() const labelLength = !width || width > 800 ? 50 : 20 const colors = ['#2a81e3', '#c72ae3', '#b91111', '#f3ad28', '#11b981'] const maxProb = _.max( Object.values(probsByOutcome).map((probs) => Math.max(...probs)) ) const yMax = Math.min(100, Math.max(50, (maxProb ?? 0) * 100 * 1.2)) const times = _.sortBy([ contract.createdTime, ...bets.map((bet) => bet.createdTime), ]) const dateTimes = times.map((time) => new Date(time)) const data = sortedOutcomes.map((outcome, i) => { const probs = probsByOutcome[outcome] if (resolutionTime || isClosed) { dateTimes.push(latestTime.toDate()) probs.push(probs[probs.length - 1]) } else { // Add a fake datapoint in future so the line continues horizontally // to the right. dateTimes.push(latestTime.add(1, 'month').toDate()) probs.push(probs[probs.length - 1]) } const points = probs.map((prob, i) => ({ x: dateTimes[i], y: Math.round(prob * 100), })) const answer = answers?.find((answer) => answer.id === outcome)?.text ?? 'None' const answerText = answer.slice(0, labelLength) + (answer.length > labelLength ? '...' : '') const id = `#${outcome}: ${answerText}` return { id, data: points, color: colors[i] } }) data.reverse() const yTickValues = [0, 25, 50, 75, 100] const numXTickValues = !width || width < 800 ? 2 : 5 const hoursAgo = latestTime.subtract(5, 'hours') const startDate = dayjs(contract.createdTime).isBefore(hoursAgo) ? new Date(contract.createdTime) : hoursAgo.toDate() const lessThanAWeek = dayjs(startDate).add(1, 'week').isAfter(latestTime) return (
= 800 ? 350 : 225 }} > formatTime(+d.valueOf(), lessThanAWeek)} axisBottom={{ tickValues: numXTickValues, format: (time) => formatTime(+time, lessThanAWeek), }} colors={{ datum: 'color' }} pointSize={0} enableSlices="x" enableGridX={!!width && width >= 800} enableArea margin={{ top: 20, right: 28, bottom: 22, left: 40 }} />
) } function formatPercent(y: DatumValue) { return `${Math.round(+y.toString())}%` } function formatTime(time: number, includeTime: boolean) { const d = dayjs(time) if (d.isSame(Date.now(), 'day')) return d.format('ha') if (includeTime) return dayjs(time).format('MMM D, ha') return dayjs(time).format('MMM D') } const computeProbsByOutcome = ( bets: Bet[], totalShares: { [outcome: string]: number } ) => { const betsByOutcome = _.groupBy(bets, (bet) => bet.outcome) const outcomes = Object.keys(betsByOutcome).filter((outcome) => { const maxProb = Math.max( ...betsByOutcome[outcome].map((bet) => bet.probAfter) ) return outcome !== '0' && maxProb > 0.05 }) const trackedOutcomes = _.sortBy( outcomes, (outcome) => -1 * getOutcomeProbability(totalShares, outcome) ).slice(0, 5) const probsByOutcome = _.fromPairs( trackedOutcomes.map((outcome) => [outcome, [0]]) ) const sharesByOutcome = _.fromPairs( Object.keys(betsByOutcome).map((outcome) => [outcome, 0]) ) for (const bet of bets) { const { outcome, shares } = bet sharesByOutcome[outcome] += shares const sharesSquared = _.sumBy( Object.values(sharesByOutcome).map((shares) => shares ** 2) ) for (const outcome of trackedOutcomes) { probsByOutcome[outcome].push( sharesByOutcome[outcome] ** 2 / sharesSquared ) } } return { probsByOutcome, sortedOutcomes: trackedOutcomes } }