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:
parent
f587e0256d
commit
59cdc9f776
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user