diff --git a/web/components/charts/contract/choice.tsx b/web/components/charts/contract/choice.tsx index 416b3557..875adefa 100644 --- a/web/components/charts/contract/choice.tsx +++ b/web/components/charts/contract/choice.tsx @@ -1,6 +1,6 @@ import { useMemo, useRef } from 'react' import { sum, sortBy, groupBy } from 'lodash' -import { scaleTime, scaleLinear, schemeCategory10 } from 'd3' +import { scaleTime, scaleLinear } from 'd3' import { Bet } from 'common/bet' import { FreeResponseContract, MultipleChoiceContract } from 'common/contract' @@ -10,6 +10,64 @@ import { MARGIN_X, MARGIN_Y, getDateRange } from '../helpers' import { MultiPoint, MultiValueHistoryChart } from '../generic-charts' import { useElementWidth } from 'web/hooks/use-element-width' +// thanks to https://observablehq.com/@jonhelfman/optimal-orders-for-choosing-categorical-colors +const CATEGORY_COLORS = [ + '#00b8dd', + '#eecafe', + '#874c62', + '#6457ca', + '#f773ba', + '#9c6bbc', + '#a87744', + '#af8a04', + '#bff9aa', + '#f3d89d', + '#c9a0f5', + '#ff00e5', + '#9dc6f7', + '#824475', + '#d973cc', + '#bc6808', + '#056e70', + '#677932', + '#00b287', + '#c8ab6c', + '#a2fb7a', + '#f8db68', + '#14675a', + '#8288f4', + '#fe1ca0', + '#ad6aff', + '#786306', + '#9bfbaf', + '#b00cf7', + '#2f7ec5', + '#4b998b', + '#42fa0e', + '#5b80a1', + '#962d9d', + '#3385ff', + '#48c5ab', + '#b2c873', + '#4cf9a4', + '#00ffff', + '#3cca73', + '#99ae17', + '#7af5cf', + '#52af45', + '#fbb80f', + '#29971b', + '#187c9a', + '#00d539', + '#bbfa1a', + '#61f55c', + '#cabc03', + '#ff9000', + '#779100', + '#bcfd6f', + '#70a560', +] + const getMultiChartData = ( contract: FreeResponseContract | MultipleChoiceContract, bets: Bet[], @@ -22,12 +80,8 @@ const getMultiChartData = ( const sortedBets = sortBy(bets, (b) => b.createdTime) const betsByOutcome = groupBy(sortedBets, (bet) => bet.outcome) const validAnswers = answers.filter((answer) => { - const maxProb = Math.max( - ...betsByOutcome[answer.id].map((bet) => bet.probAfter) - ) return ( (answer.id !== '0' || outcomeType === 'MULTIPLE_CHOICE') && - maxProb > 0.02 && totalBets[answer.id] > 0.000000001 ) }) @@ -82,7 +136,7 @@ export const ChoiceContractChart = (props: { const { contract, bets } = props const [start, end] = useMemo(() => getDateRange(contract), [contract]) const data = useMemo( - () => getMultiChartData(contract, bets, start, end, 6), + () => getMultiChartData(contract, bets, start, end, CATEGORY_COLORS.length), [contract, bets, start, end] ) const isMobile = useIsMobile(800) @@ -100,7 +154,7 @@ export const ChoiceContractChart = (props: { xScale={xScale} yScale={yScale} data={data.points} - colors={schemeCategory10} + colors={CATEGORY_COLORS} labels={data.labels} pct /> diff --git a/web/components/charts/generic-charts.tsx b/web/components/charts/generic-charts.tsx index c7b21c21..44c2f81c 100644 --- a/web/components/charts/generic-charts.tsx +++ b/web/components/charts/generic-charts.tsx @@ -13,7 +13,7 @@ import { ScaleContinuousNumeric, SeriesPoint, } from 'd3' -import { range } from 'lodash' +import { range, sortBy } from 'lodash' import dayjs from 'dayjs' import { @@ -203,7 +203,7 @@ export const MultiValueHistoryChart = (props: { const py1 = useCallback((p: SP) => yScale(p[1]), [yScale]) const xBisector = bisector((p: MultiPoint) => p[0]) - const { fmtX, fmtY, xAxis, yAxis, series } = useMemo(() => { + const { fmtX, fmtY, xAxis, yAxis } = useMemo(() => { const [start, end] = xScale.domain() const fmtX = getFormatterForDateRange(start, end) const fmtY = (n: number) => (pct ? formatPct(n, 0) : formatLargeNumber(n)) @@ -215,13 +215,16 @@ export const MultiValueHistoryChart = (props: { .tickValues(tickValues) .tickFormat(fmtY) + return { fmtX, fmtY, xAxis, yAxis } + }, [h, pct, xScale, yScale]) + + const series = useMemo(() => { const d3Stack = stack() .keys(range(0, labels.length)) .value(([_date, probs], o) => probs[o]) .order(stackOrderReverse) - const series = d3Stack(data) - return { fmtX, fmtY, xAxis, yAxis, series } - }, [h, pct, xScale, yScale, data, labels.length]) + return d3Stack(data) + }, [data, labels.length]) const onSelect = useEvent((ev: D3BrushEvent) => { if (ev.selection) { @@ -247,19 +250,23 @@ export const MultiValueHistoryChart = (props: { setMouseState(undefined) }) + const mouseProbs = mouseState?.p[1] ?? [] + const legendItems = sortBy( + mouseProbs.map((p, i) => ({ + color: colors[i], + label: labels[i], + value: fmtY(p), + p, + })), + (item) => -item.p + ).slice(0, 10) + return (
{mouseState && ( {fmtX(mouseState.p[0])} - ({ - color: colors[i], - label: labels[i], - value: fmtY(p), - }))} - /> + )}