Update FR colors, consolidate non-top answers into "Other" (#1031)

* Update FR colors, consolidate non-top answers into "Other"

* Fix answer panel coloration to not be weird and work on Firefox
This commit is contained in:
Marshall Polaris 2022-10-11 23:59:11 -07:00 committed by GitHub
parent f587e0256d
commit 59cdc9f776
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 99 deletions

View File

@ -23,7 +23,7 @@ import { Linkify } from 'web/components/linkify'
import { Button } from 'web/components/button' import { Button } from 'web/components/button'
import { useAdmin } from 'web/hooks/use-admin' import { useAdmin } from 'web/hooks/use-admin'
import { needsAdminToResolve } from 'web/pages/[username]/[contractSlug]' import { needsAdminToResolve } from 'web/pages/[username]/[contractSlug]'
import { CATEGORY_COLORS } from '../charts/contract/choice' import { CHOICE_ANSWER_COLORS } from '../charts/contract/choice'
import { useChartAnswers } from '../charts/contract/choice' import { useChartAnswers } from '../charts/contract/choice'
export function AnswersPanel(props: { export function AnswersPanel(props: {
@ -190,7 +190,10 @@ function OpenAnswer(props: {
const probPercent = formatPercent(prob) const probPercent = formatPercent(prob)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const color = const color =
colorIndex != undefined ? CATEGORY_COLORS[colorIndex] : '#B1B1C7' colorIndex != undefined && colorIndex < CHOICE_ANSWER_COLORS.length
? CHOICE_ANSWER_COLORS[colorIndex] + '55' // semi-transparent
: '#B1B1C755'
const colorWidth = 100 * Math.max(prob, 0.01)
return ( return (
<Col className="my-1 px-2"> <Col className="my-1 px-2">
@ -206,9 +209,12 @@ function OpenAnswer(props: {
<Col <Col
className={clsx( className={clsx(
'bg-greyscale-1 relative w-full rounded-lg transition-all', 'relative w-full rounded-lg transition-all',
tradingAllowed(contract) ? 'text-greyscale-7' : 'text-greyscale-5' tradingAllowed(contract) ? 'text-greyscale-7' : 'text-greyscale-5'
)} )}
style={{
background: `linear-gradient(to right, ${color} ${colorWidth}%, #FBFBFF ${colorWidth}%)`,
}}
> >
<Row className="z-20 -mb-1 justify-between gap-2 py-2 px-3"> <Row className="z-20 -mb-1 justify-between gap-2 py-2 px-3">
<Row> <Row>
@ -236,11 +242,6 @@ function OpenAnswer(props: {
)} )}
</Row> </Row>
</Row> </Row>
<hr
color={color}
className="absolute z-0 h-full w-full rounded-l-lg border-none opacity-30"
style={{ width: `${100 * Math.max(prob, 0.01)}%` }}
/>
</Col> </Col>
</Col> </Col>
) )

View File

@ -1,5 +1,5 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { last, sum, sortBy, groupBy } from 'lodash' import { last, range, sum, sortBy, groupBy } from 'lodash'
import { scaleTime, scaleLinear } from 'd3-scale' import { scaleTime, scaleLinear } from 'd3-scale'
import { curveStepAfter } from 'd3-shape' import { curveStepAfter } from 'd3-shape'
@ -19,83 +19,36 @@ import { MultiPoint, MultiValueHistoryChart } from '../generic-charts'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
export const CATEGORY_COLORS = [ type ChoiceContract = FreeResponseContract | MultipleChoiceContract
'#7eb0d5',
'#fd7f6f', export const CHOICE_ANSWER_COLORS = [
'#b2e061', '#97C1EB',
'#bd7ebe', '#F39F83',
'#ffb55a', '#F9EBA5',
'#ffee65', '#FFC7D2',
'#beb9db', '#C7ECFF',
'#fdcce5', '#8CDEC7',
'#8bd3c7', '#DBE96F',
'#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',
] ]
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 = { top: 20, right: 10, bottom: 20, left: 40 }
const MARGIN_X = MARGIN.left + MARGIN.right const MARGIN_X = MARGIN.left + MARGIN.right
const MARGIN_Y = MARGIN.top + MARGIN.bottom const MARGIN_Y = MARGIN.top + MARGIN.bottom
const getTrackedAnswers = ( const getAnswers = (contract: ChoiceContract) => {
contract: FreeResponseContract | MultipleChoiceContract, const { answers, outcomeType } = contract
topN: number const validAnswers = answers.filter(
) => { (answer) => answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE'
const { answers, outcomeType, totalBets } = contract
const validAnswers = answers.filter((answer) => {
return (
(answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE') &&
totalBets[answer.id] > 0.000000001
) )
})
return sortBy( return sortBy(
validAnswers, validAnswers,
(answer) => -1 * getOutcomeProbability(contract, answer.id) (answer) => -1 * getOutcomeProbability(contract, answer.id)
).slice(0, topN) )
} }
const getBetPoints = (answers: Answer[], bets: Bet[]) => { const getBetPoints = (answers: Answer[], bets: Bet[], topN?: number) => {
const sortedBets = sortBy(bets, (b) => b.createdTime) const sortedBets = sortBy(bets, (b) => b.createdTime)
const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome) const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome)
const sharesByOutcome = Object.fromEntries( const sharesByOutcome = Object.fromEntries(
@ -109,11 +62,14 @@ const getBetPoints = (answers: Answer[], bets: Bet[]) => {
const sharesSquared = sum( const sharesSquared = sum(
Object.values(sharesByOutcome).map((shares) => shares ** 2) Object.values(sharesByOutcome).map((shares) => shares ** 2)
) )
points.push({ const probs = answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared)
x: new Date(bet.createdTime),
y: answers.map((a) => sharesByOutcome[a.id] ** 2 / sharesSquared), if (topN != null && answers.length > topN) {
obj: bet, 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 return points
} }
@ -141,17 +97,12 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => {
) )
} }
export function useChartAnswers( export function useChartAnswers(contract: ChoiceContract) {
contract: FreeResponseContract | MultipleChoiceContract return useMemo(() => getAnswers(contract), [contract])
) {
return useMemo(
() => getTrackedAnswers(contract, CATEGORY_COLORS.length),
[contract]
)
} }
export const ChoiceContractChart = (props: { export const ChoiceContractChart = (props: {
contract: FreeResponseContract | MultipleChoiceContract contract: ChoiceContract
bets: Bet[] bets: Bet[]
width: number width: number
height: number height: number
@ -160,18 +111,33 @@ export const ChoiceContractChart = (props: {
const { contract, bets, width, height, onMouseOver } = props const { contract, bets, width, height, onMouseOver } = props
const [start, end] = getDateRange(contract) const [start, end] = getDateRange(contract)
const answers = useChartAnswers(contract) const answers = useChartAnswers(contract)
const betPoints = useMemo(() => getBetPoints(answers, bets), [answers, bets]) const topN = Math.min(CHOICE_ANSWER_COLORS.length, answers.length)
const data = useMemo( const betPoints = useMemo(
() => [ () => getBetPoints(answers, bets, topN),
{ x: new Date(start), y: answers.map((_) => 0) }, [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, ...betPoints,
{ {
x: new Date(end ?? Date.now() + DAY_MS), x: new Date(end ?? Date.now() + DAY_MS),
y: answers.map((a) => getOutcomeProbability(contract, a.id)), y: endY,
}, },
], ]
[answers, contract, betPoints, start, end] }, [answers.length, topN, betPoints, endProbs, start, end])
)
const rightmostDate = getRightmostVisibleDate( const rightmostDate = getRightmostVisibleDate(
end, end,
last(betPoints)?.x?.getTime(), last(betPoints)?.x?.getTime(),
@ -188,8 +154,8 @@ export const ChoiceContractChart = (props: {
const d = xScale.invert(x) const d = xScale.invert(x)
const legendItems = sortBy( const legendItems = sortBy(
data.y.map((p, i) => ({ data.y.map((p, i) => ({
color: CATEGORY_COLORS[i], color: CHOICE_ALL_COLORS[i],
label: answers[i].text, label: i === CHOICE_ANSWER_COLORS.length ? 'Other' : answers[i].text,
value: formatPct(p), value: formatPct(p),
p, p,
})), })),
@ -221,7 +187,7 @@ export const ChoiceContractChart = (props: {
yScale={yScale} yScale={yScale}
yKind="percent" yKind="percent"
data={data} data={data}
colors={CATEGORY_COLORS} colors={CHOICE_ALL_COLORS}
curve={curveStepAfter} curve={curveStepAfter}
onMouseOver={onMouseOver} onMouseOver={onMouseOver}
Tooltip={ChoiceTooltip} Tooltip={ChoiceTooltip}