diff --git a/web/components/answers/answers-panel.tsx b/web/components/answers/answers-panel.tsx
index 6b35f74e..a665a921 100644
--- a/web/components/answers/answers-panel.tsx
+++ b/web/components/answers/answers-panel.tsx
@@ -23,7 +23,7 @@ import { Linkify } from 'web/components/linkify'
import { Button } from 'web/components/button'
import { useAdmin } from 'web/hooks/use-admin'
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'
export function AnswersPanel(props: {
@@ -190,7 +190,10 @@ function OpenAnswer(props: {
const probPercent = formatPercent(prob)
const [open, setOpen] = useState(false)
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 (
@@ -206,9 +209,12 @@ function OpenAnswer(props: {
@@ -236,11 +242,6 @@ function OpenAnswer(props: {
)}
-
)
diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx
index 0355b4b5..31ca9b47 100644
--- a/web/components/charts/contract/choice.tsx
+++ b/web/components/charts/contract/choice.tsx
@@ -1,5 +1,5 @@
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 { curveStepAfter } from 'd3-shape'
@@ -19,83 +19,36 @@ 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',
+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 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
- )
- })
+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)
- ).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 betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome)
const sharesByOutcome = Object.fromEntries(
@@ -109,11 +62,14 @@ const getBetPoints = (answers: Answer[], bets: Bet[]) => {
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,
- })
+ 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
}
@@ -141,17 +97,12 @@ const Legend = (props: { className?: string; items: LegendItem[] }) => {
)
}
-export function useChartAnswers(
- contract: FreeResponseContract | MultipleChoiceContract
-) {
- return useMemo(
- () => getTrackedAnswers(contract, CATEGORY_COLORS.length),
- [contract]
- )
+export function useChartAnswers(contract: ChoiceContract) {
+ return useMemo(() => getAnswers(contract), [contract])
}
export const ChoiceContractChart = (props: {
- contract: FreeResponseContract | MultipleChoiceContract
+ contract: ChoiceContract
bets: Bet[]
width: number
height: number
@@ -160,18 +111,33 @@ export const ChoiceContractChart = (props: {
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) },
+ 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: answers.map((a) => getOutcomeProbability(contract, a.id)),
+ y: endY,
},
- ],
- [answers, contract, betPoints, start, end]
- )
+ ]
+ }, [answers.length, topN, betPoints, endProbs, start, end])
+
const rightmostDate = getRightmostVisibleDate(
end,
last(betPoints)?.x?.getTime(),
@@ -188,8 +154,8 @@ export const ChoiceContractChart = (props: {
const d = xScale.invert(x)
const legendItems = sortBy(
data.y.map((p, i) => ({
- color: CATEGORY_COLORS[i],
- label: answers[i].text,
+ color: CHOICE_ALL_COLORS[i],
+ label: i === CHOICE_ANSWER_COLORS.length ? 'Other' : answers[i].text,
value: formatPct(p),
p,
})),
@@ -221,7 +187,7 @@ export const ChoiceContractChart = (props: {
yScale={yScale}
yKind="percent"
data={data}
- colors={CATEGORY_COLORS}
+ colors={CHOICE_ALL_COLORS}
curve={curveStepAfter}
onMouseOver={onMouseOver}
Tooltip={ChoiceTooltip}