diff --git a/web/components/answers/answers-graph.tsx b/web/components/answers/answers-graph.tsx new file mode 100644 index 00000000..6082227d --- /dev/null +++ b/web/components/answers/answers-graph.tsx @@ -0,0 +1,177 @@ +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 ? 75 : 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 ( +