Skip to content

Commit

Permalink
needle (#2736)
Browse files Browse the repository at this point in the history
* needle
  • Loading branch information
ingawei committed Jul 24, 2024
1 parent af833af commit 504e60c
Show file tree
Hide file tree
Showing 5 changed files with 426 additions and 55 deletions.
37 changes: 27 additions & 10 deletions web/components/answers/answer-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,9 @@ export const AddComment = (props: { onClick: () => void }) => {
export const MultiBettor = (props: {
answer: Answer
contract: CPMMMultiContract
buttonClassName?: string
}) => {
const { answer, contract } = props
const { answer, contract, buttonClassName } = props
const [outcome, setOutcome] = useState<'YES' | 'NO' | undefined>(undefined)

return (
Expand All @@ -228,7 +229,7 @@ export const MultiBettor = (props: {
<Button
size="2xs"
color="indigo-outline"
className="bg-primary-50"
className={clsx('bg-primary-50', buttonClassName)}
onClick={(e) => {
e.stopPropagation()
track('bet intent', { location: 'answer panel' })
Expand Down Expand Up @@ -426,20 +427,30 @@ export const OpenProb = (props: {
</Row>
)
}
export const ClosedProb = (props: { prob: number; resolvedProb?: number }) => {
const { prob, resolvedProb: resolveProb } = props
export const ClosedProb = (props: {
prob: number
resolvedProb?: number
className?: string
}) => {
const { prob, resolvedProb: resolveProb, className } = props
return (
<>
{!!resolveProb && (
<span className="dark:text-ink-900 text-lg text-purple-500">
<span
className={clsx(
'dark:text-ink-900 text-lg text-purple-500',
className
)}
>
{Math.round(resolveProb * 100)}%
</span>
)}
<span
className={clsx(
'text-ink-500 whitespace-nowrap text-lg',
resolveProb != undefined &&
'inline-block min-w-[40px] text-right line-through'
'inline-block min-w-[40px] text-right line-through',
className
)}
>
{formatPercent(prob)}
Expand All @@ -452,8 +463,9 @@ export const AnswerStatus = (props: {
contract: MultiContract
answer: Answer
noNewIcon?: boolean
className?: string
}) => {
const { contract, answer } = props
const { contract, answer, className } = props
const { resolutions } = contract

const answerResolution =
Expand All @@ -474,7 +486,7 @@ export const AnswerStatus = (props: {

if (answerResolution) {
return (
<Row className="items-center gap-1.5 font-semibold">
<Row className={clsx('items-center gap-1.5 font-semibold', className)}>
<div className={'text-ink-800 text-base'}>Resolved</div>
{answerResolution === 'MKT' && 'resolutionProbability' in answer ? (
<ProbPercentLabel
Expand All @@ -487,9 +499,14 @@ export const AnswerStatus = (props: {
)
}
return isOpen ? (
<OpenProb contract={contract} answer={answer} noNewIcon />
<OpenProb
className={className}
contract={contract}
answer={answer}
noNewIcon
/>
) : (
<ClosedProb prob={prob} resolvedProb={resolvedProb} />
<ClosedProb className={className} prob={prob} resolvedProb={resolvedProb} />
)
}
export const BetButtons = (props: {
Expand Down
32 changes: 6 additions & 26 deletions web/components/elections-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import clsx from 'clsx'
import { ChoiceMiniGraph } from './us-elections/contracts/choice-mini-graph'
import { ElectionsPageProps } from 'web/public/data/elections-data'
import { Carousel } from './widgets/carousel'
import { ProbabilityNeedle } from './us-elections/probability-needle'

export function USElectionsPage(props: ElectionsPageProps) {
const {
Expand Down Expand Up @@ -89,6 +90,11 @@ export function USElectionsPage(props: ElectionsPageProps) {
className="-mt-4"
/>

<PoliticsCard
contract={electionPartyContract as MultiContract}
viewType="PARTY"
customTitle="Which party will win the Presidential Election?"
/>
<Col className="gap-2">
<Row className="items-center gap-2">
<div className="bg-ink-600 flex h-[1px] grow flex-row" />
Expand Down Expand Up @@ -138,32 +144,6 @@ export function USElectionsPage(props: ElectionsPageProps) {
houseContract={houseContract as MultiContract}
/>

<PoliticsCard
contract={electionPartyContract as MultiContract}
viewType="PARTY"
customTitle="Which party will win the Presidential Election?"
>
{partyPoints && afterTime && (
<SizedContainer
className={clsx('h-[50px] w-full pb-4 pr-10 sm:h-[100px]')}
>
{(w, h) => (
<ChoiceMiniGraph
width={w}
height={h}
multiPoints={partyPoints}
contract={electionPartyContract}
selectedAnswerIds={electionPartyContract?.answers
.filter((a) => a.text !== 'Other')
.map((a) => a.id)}
showMinimumYScale
startTime={afterTime}
/>
)}
</SizedContainer>
)}
</PoliticsCard>

<PoliticsCard
contract={democraticElectability as MultiContract}
viewType="CANDIDATE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { sumBy } from 'lodash'
import { floatingEqual } from 'common/util/math'
import { User } from 'common/user'
import { UserPosition } from './candidates-user-position'
import { Spacer } from 'web/components/layout/spacer'

export function removeTextInParentheses(input: string): string {
return input.replace(/\s*\([^)]*\)/g, '')
Expand Down Expand Up @@ -143,7 +144,9 @@ export function PercentChangeToday(props: {
}) {
const { className, threshold = 0.02, probChange } = props
const percentChangeToday = getPercent(probChange)
if (Math.abs(probChange) < threshold) return null
if (Math.abs(probChange) < threshold) {
return null
}
if (percentChangeToday > threshold) {
return (
<div className={clsx('text-teal-700', className)}>
Expand All @@ -157,3 +160,37 @@ export function PercentChangeToday(props: {
</div>
)
}

export function BubblePercentChange(props: {
className?: string
threshold?: number
probChange: number
}) {
const { className, threshold = 0.02, probChange } = props
const percentChangeToday = getPercent(probChange)
if (Math.abs(probChange) < threshold) {
return null
}
if (percentChangeToday > threshold) {
return (
<div
className={clsx(
'h-fit w-fit rounded-full bg-teal-700/20 px-1.5 py-0.5 text-teal-700',
className
)}
>
+<b>{formatPercentShort(probChange)}</b>
</div>
)
}
return (
<div
className={clsx(
'text-scarlet-700 bg-scarlet-700/20 h-fit w-fit rounded-full px-2 py-1',
className
)}
>
<b>{formatPercentShort(probChange)}</b>
</div>
)
}
166 changes: 148 additions & 18 deletions web/components/us-elections/contracts/party-panel/party-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ import {
import { CreatorAndAnswerLabel } from 'web/components/answers/answer-components'
import { MultiBettor } from 'web/components/answers/answer-components'
import { CPMMMultiContract } from 'common/contract'
import { PercentChangeToday } from '../candidates-panel/candidate-bar'
import {
BubblePercentChange,
PercentChangeToday,
} from '../candidates-panel/candidate-bar'
import { useUserContractBets } from 'web/hooks/use-user-bets'
import { groupBy, sumBy } from 'lodash'
import { floatingEqual } from 'common/util/math'
import { UserPosition } from '../candidates-panel/candidates-user-position'
import { ProbabilityNeedle } from 'web/components/us-elections/probability-needle'
import { SizedContainer } from 'web/components/sized-container'
import { Spacer } from 'web/components/layout/spacer'

// just the bars
export function PartyPanel(props: {
Expand Down Expand Up @@ -59,25 +65,84 @@ export function PartyPanel(props: {
const userBets = useUserContractBets(user?.id, contract.id)
const userBetsByAnswer = groupBy(userBets, (bet) => bet.answerId)

// Calculate Republican to Democratic ratio
const republicanAnswer = answers.find((a) =>
a.text.includes('Republican Party')
)
const democraticAnswer = answers.find((a) =>
a.text.includes('Democratic Party')
)

const republicanProb = getAnswerProbability(contract, republicanAnswer!.id)
const democraticProb = getAnswerProbability(contract, democraticAnswer!.id)

let democratToRepublicanRatio = 0
if (republicanAnswer && democraticAnswer) {
const totalProb = republicanProb + democraticProb
democratToRepublicanRatio = democraticProb / totalProb
}

return (
<Col className="mx-[2px] gap-2">
{showNoAnswers ? (
<div className="text-ink-500 pb-4">No answers yet</div>
) : (
<>
{displayedAnswers.map((answer) => (
<PartyAnswer
key={answer.id}
answer={answer as Answer}
contract={contract}
color={getPartyColor(answer.text)}
user={user}
userBets={userBetsByAnswer[answer.id]}
<div className="mx-[2px] flex flex-col gap-2">
<div className="hidden md:flex md:items-center md:justify-between">
{!!republicanAnswer && (
<PartyAnswerSnippet
contract={contract}
answer={republicanAnswer}
color={getPartyColor(republicanAnswer.text)}
alignment="left"
userBets={userBetsByAnswer[republicanAnswer.id]}
user={user}
/>
)}
<SizedContainer className="h-[210px] w-1/2">
{(width, height) => (
<ProbabilityNeedle
percentage={democratToRepublicanRatio}
width={width}
height={height}
/>
))}
</>
)}
</Col>
)}
</SizedContainer>
{!!democraticAnswer && (
<PartyAnswerSnippet
contract={contract}
answer={democraticAnswer}
color={getPartyColor(democraticAnswer.text)}
alignment="right"
userBets={userBetsByAnswer[democraticAnswer.id]}
user={user}
/>
)}
</div>
<SizedContainer className="h-[210px] w-full md:hidden">
{(width, height) => (
<ProbabilityNeedle
percentage={democratToRepublicanRatio}
width={width}
height={height}
/>
)}
</SizedContainer>
<Col className="gap-2 md:hidden">
{showNoAnswers ? (
<div className="text-ink-500 pb-4">No answers yet</div>
) : (
<>
{displayedAnswers.map((answer) => (
<PartyAnswer
key={answer.id}
answer={answer as Answer}
contract={contract}
color={getPartyColor(answer.text)}
user={user}
userBets={userBetsByAnswer[answer.id]}
/>
))}
</>
)}
</Col>
</div>
)
}

Expand Down Expand Up @@ -118,6 +183,7 @@ function PartyAnswer(props: {
const hasBets = userBets && !floatingEqual(sharesSum, 0)

const isCpmm = contract.mechanism === 'cpmm-multi-1'

return (
<Col className={'w-full'}>
<AnswerBar
Expand Down Expand Up @@ -168,3 +234,67 @@ function PartyAnswer(props: {
</Col>
)
}

function PartyAnswerSnippet(props: {
contract: MultiContract
answer: Answer
color: string
userBets?: Bet[]
user?: User | null
className?: string
alignment: 'left' | 'right'
}) {
const { answer, contract, userBets, user, className, alignment } = props

const { resolution, resolutions } = contract
const sharesSum = sumBy(userBets, (bet) =>
bet.outcome === 'YES' ? bet.shares : -bet.shares
)

const hasBets = userBets && !floatingEqual(sharesSum, 0)

const isCpmm = contract.mechanism === 'cpmm-multi-1'

return (
<Col
className={clsx(
className,
alignment == 'right' ? 'items-end text-right' : ''
)}
>
<div className="text-ink-700">{answer.text}</div>
<Spacer h={1} />
<Row className={alignment == 'right' ? 'flex-row-reverse' : ''}>
<AnswerStatus
className="!text-5xl"
contract={contract}
answer={answer}
/>
<BubblePercentChange
probChange={answer.probChanges.day}
className="whitespace-nowrap text-sm"
/>
</Row>

<Spacer h={2} />
<div className="relative">
<MultiBettor
contract={contract as CPMMMultiContract}
answer={answer as Answer}
buttonClassName="w-20"
/>
{!resolution && hasBets && isCpmm && user && (
<UserPosition
contract={contract as CPMMMultiContract}
answer={answer as Answer}
userBets={userBets}
user={user}
className="text-ink-600 dark:text-ink-700 absolute -bottom-[22px] left-0 text-xs hover:underline"
greenArrowClassName="text-teal-600 dark:text-teal-300"
redArrowClassName="text-scarlet-600 dark:text-scarlet-400"
/>
)}
</div>
</Col>
)
}
Loading

0 comments on commit 504e60c

Please sign in to comment.