import { useMemo } from 'react' import { last, range, sum, sortBy, groupBy } from 'lodash' import { scaleTime, scaleLinear } from 'd3-scale' import { curveStepAfter } from 'd3-shape' import { Bet } from 'common/bet' import { Answer } from 'common/answer' import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' import { getOutcomeProbability } from 'common/calculate' import { DAY_MS } from 'common/util/time' import { TooltipProps, getDateRange, getRightmostVisibleDate, formatPct, formatDateInRange, } from '../helpers' import { MultiPoint, MultiValueHistoryChart } from '../generic-charts' import { Row } from 'web/components/layout/row' import { Avatar } from 'web/components/avatar' type ChoiceContract = FreeResponseContract | MultipleChoiceContract export const CHOICE_ANSWER_COLORS = [ '#97C1EB', '#F39F83', '#F9EBA5', '#FFC7D2', '#C7ECFF', '#8CDEC7', '#DBE96F', ] export const CHOICE_OTHER_COLOR = '#CCC' export const CHOICE_ALL_COLORS = [...CHOICE_ANSWER_COLORS, CHOICE_OTHER_COLOR] const MARGIN = { top: 20, right: 10, bottom: 20, left: 40 } const MARGIN_X = MARGIN.left + MARGIN.right const MARGIN_Y = MARGIN.top + MARGIN.bottom const getAnswers = (contract: ChoiceContract) => { const { answers, outcomeType } = contract const validAnswers = answers.filter( (answer) => answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE' ) return sortBy( validAnswers, (answer) => -1 * getOutcomeProbability(contract, answer.id) ) } const getBetPoints = (answers: Answer[], bets: Bet[], topN?: number) => { 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[] = [] for (const bet of sortedBets) { const { outcome, shares } = bet sharesByOutcome[outcome] += shares const sharesSquared = sum( Object.values(sharesByOutcome).map((shares) => shares ** 2) ) const probs = answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared) if (topN != null && answers.length > topN) { const y = [...probs.slice(0, topN), sum(probs.slice(topN))] points.push({ x: new Date(bet.createdTime), y, obj: bet }) } else { points.push({ x: new Date(bet.createdTime), y: probs, obj: bet }) } } return points } type LegendItem = { color: string; label: string; value?: string } const Legend = (props: { className?: string; items: LegendItem[] }) => { const { items, className } = props return (
    {items.map((item) => (
  1. {item.label} {item.value}
  2. ))}
) } export function useChartAnswers(contract: ChoiceContract) { return useMemo(() => getAnswers(contract), [contract]) } export const ChoiceContractChart = (props: { contract: ChoiceContract bets: Bet[] width: number height: number onMouseOver?: (p: MultiPoint | undefined) => void }) => { const { contract, bets, width, height, onMouseOver } = props const [start, end] = getDateRange(contract) const answers = useChartAnswers(contract) const topN = Math.min(CHOICE_ANSWER_COLORS.length, answers.length) const betPoints = useMemo( () => getBetPoints(answers, bets, topN), [answers, bets, topN] ) const endProbs = useMemo( () => answers.map((a) => getOutcomeProbability(contract, a.id)), [answers, contract] ) const data = useMemo(() => { const yCount = answers.length > topN ? topN + 1 : topN const startY = range(0, yCount).map((_) => 0) const endY = answers.length > topN ? [...endProbs.slice(0, topN), sum(endProbs.slice(topN))] : endProbs return [ { x: new Date(start), y: startY }, ...betPoints, { x: new Date(end ?? Date.now() + DAY_MS), y: endY, }, ] }, [answers.length, topN, betPoints, endProbs, start, end]) const rightmostDate = getRightmostVisibleDate( end, last(betPoints)?.x?.getTime(), Date.now() ) const visibleRange = [start, rightmostDate] const xScale = scaleTime(visibleRange, [0, width - MARGIN_X]) const yScale = scaleLinear([0, 1], [height - MARGIN_Y, 0]) const ChoiceTooltip = useMemo( () => (props: TooltipProps>) => { const { data, x, xScale } = props const [start, end] = xScale.domain() const d = xScale.invert(x) const legendItems = sortBy( data.y.map((p, i) => ({ color: CHOICE_ALL_COLORS[i], label: i === CHOICE_ANSWER_COLORS.length ? 'Other' : answers[i].text, value: formatPct(p), p, })), (item) => -item.p ).slice(0, 10) return ( <> {data.obj && ( )} {formatDateInRange(d, start, end)} ) }, [answers] ) return ( ) }