import { useMemo } from 'react' import { last, 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' export const CATEGORY_COLORS = [ '#7eb0d5', '#fd7f6f', '#b2e061', '#bd7ebe', '#ffb55a', '#ffee65', '#beb9db', '#fdcce5', '#8bd3c7', '#bddfb7', '#e2e3f3', '#fafafa', '#9fcdeb', '#d3d3d3', '#b1a296', '#e1bdb6', '#f2dbc0', '#fae5d3', '#c5e0ec', '#e0f0ff', '#ffddcd', '#fbd5e2', '#f2e7e5', '#ffe7ba', '#eed9c4', '#ea9999', '#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9', '#9fc5e8', '#b4a7d6', '#d5a6bd', '#e06666', '#f6b26b', '#ffd966', '#93c47d', '#76a5af', '#6fa8dc', '#8e7cc3', '#c27ba0', '#cc0000', '#e69138', '#f1c232', '#6aa84f', '#45818e', '#3d85c6', '#674ea7', '#a64d79', '#990000', '#b45f06', '#bf9000', ] 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 getTrackedAnswers = ( contract: FreeResponseContract | MultipleChoiceContract, topN: number ) => { const { answers, outcomeType, totalBets } = contract const validAnswers = answers.filter((answer) => { return ( (answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE') && totalBets[answer.id] > 0.000000001 ) }) return sortBy( validAnswers, (answer) => -1 * getOutcomeProbability(contract, answer.id) ).slice(0, topN) } 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[] = [] for (const bet of sortedBets) { const { outcome, shares } = bet sharesByOutcome[outcome] += shares const sharesSquared = sum( Object.values(sharesByOutcome).map((shares) => shares ** 2) ) points.push({ x: new Date(bet.createdTime), y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared), 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: FreeResponseContract | MultipleChoiceContract ) { return useMemo( () => getTrackedAnswers(contract, CATEGORY_COLORS.length), [contract] ) } export const ChoiceContractChart = (props: { contract: FreeResponseContract | MultipleChoiceContract 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 betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets]) const data = useMemo( () => [ { x: new Date(start), y: answers.map((_) => 0) }, ...betPoints, { x: new Date(end ?? Date.now() + DAY_MS), y: answers.map((a) => getOutcomeProbability(contract, a.id)), }, ], [answers, contract, betPoints, 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: CATEGORY_COLORS[i], label: answers[i].text, value: formatPct(p), p, })), (item) => -item.p ).slice(0, 10) return ( <> {data.obj && ( )} {formatDateInRange(d, start, end)} ) }, [answers] ) return ( ) }