diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_group/forecast_maker_group_continuous.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_group/forecast_maker_group_continuous.tsx index 366f4f0b3c..143d960b2a 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_group/forecast_maker_group_continuous.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_group/forecast_maker_group_continuous.tsx @@ -242,6 +242,7 @@ const ForecastMakerGroupContinuous: FC = ({ value={activeTableOption} options={groupOptions} onChange={setActiveTableOption} + questions={questions} /> {groupOptions.map((option) => { const dataset = getNumericForecastDataset( diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/group_forecast_table.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/group_forecast_table.tsx index 409a483d71..69b8d22768 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/group_forecast_table.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/group_forecast_table.tsx @@ -13,13 +13,16 @@ import { import ResolutionIcon from "@/components/icons/resolution"; import { MultiSliderValue } from "@/components/sliders/multi_slider"; import RadioButton from "@/components/ui/radio_button"; -import { Resolution } from "@/types/post"; import { - Bounds, + Resolution, +} from "@/types/post"; +import { Quartiles, + Question, QuestionWithNumericForecasts, } from "@/types/question"; import { formatResolution } from "@/utils/questions"; +import { getDisplayValue } from "@/utils/charts"; export type ConditionalTableOption = { id: number; @@ -38,9 +41,15 @@ type Props = { value: number | null; options: ConditionalTableOption[]; onChange: (id: number) => void; + questions: QuestionWithNumericForecasts[]; }; -const GroupForecastTable: FC = ({ options, value, onChange }) => { +const GroupForecastTable: FC = ({ + options, + value, + onChange, + questions, +}) => { const t = useTranslations(); const locale = useLocale(); @@ -155,8 +164,18 @@ const GroupForecastTable: FC = ({ options, value, onChange }) => { })} > question.id === option.id + ) as Question + )} + userValue={getDisplayValue( + option.userQuartiles?.lower25, + questions.find( + (question) => question.id === option.id + ) as Question + )} isDirty={option.isDirty} /> @@ -166,8 +185,18 @@ const GroupForecastTable: FC = ({ options, value, onChange }) => { })} > question.id === option.id + ) as Question + )} + userValue={getDisplayValue( + option.userQuartiles?.median, + questions.find( + (question) => question.id === option.id + ) as Question + )} isDirty={option.isDirty} /> @@ -179,8 +208,18 @@ const GroupForecastTable: FC = ({ options, value, onChange }) => {
question.id === option.id + ) as Question + )} + userValue={getDisplayValue( + option.userQuartiles?.upper75, + questions.find( + (question) => question.id === option.id + ) as Question + )} isDirty={option.isDirty} />
@@ -197,9 +236,9 @@ const GroupForecastTable: FC = ({ options, value, onChange }) => { }; const PredictionCell: FC<{ - communityValue: number; + communityValue: number | string; isDirty: boolean; - userValue?: number; + userValue?: number | string; }> = ({ communityValue, isDirty, userValue }) => (
diff --git a/front_end/src/components/charts/fan_chart.tsx b/front_end/src/components/charts/fan_chart.tsx index 6ead6938da..768edaeeb0 100644 --- a/front_end/src/components/charts/fan_chart.tsx +++ b/front_end/src/components/charts/fan_chart.tsx @@ -18,7 +18,7 @@ import { METAC_COLORS } from "@/constants/colors"; import useAppTheme from "@/hooks/use_app_theme"; import useContainerSize from "@/hooks/use_container_size"; import { Area, FanOption, Line } from "@/types/charts"; -import { Quartiles } from "@/types/question"; +import { Quartiles, QuestionWithNumericForecasts } from "@/types/question"; const TOOLTIP_WIDTH = 150; @@ -58,8 +58,16 @@ const FanChart: FC = ({ const tooltipItems = useMemo( () => - options.reduce>( - (acc, el) => ({ ...acc, [el.name]: el.quartiles }), + options.reduce< + Record< + string, + { quartiles: Quartiles; question: QuestionWithNumericForecasts } + > + >( + (acc, el) => ({ + ...acc, + [el.name]: { quartiles: el.quartiles, question: el.question }, + }), {} ), [options] diff --git a/front_end/src/components/charts/primitives/chart_fan_tooltip.tsx b/front_end/src/components/charts/primitives/chart_fan_tooltip.tsx index 620ef88e10..fe1c2ef068 100644 --- a/front_end/src/components/charts/primitives/chart_fan_tooltip.tsx +++ b/front_end/src/components/charts/primitives/chart_fan_tooltip.tsx @@ -2,12 +2,16 @@ import { useTranslations } from "next-intl"; import { ComponentProps, FC } from "react"; import { VictoryLabel } from "victory"; -import { Quartiles } from "@/types/question"; +import { Quartiles, QuestionWithNumericForecasts } from "@/types/question"; +import { getDisplayValue } from "@/utils/charts"; const HEIGHT = 70; type Props = ComponentProps & { - items: Record; + items: Record< + string, + { quartiles: Quartiles; question: QuestionWithNumericForecasts } + >; width: number; chartHeight: number; }; @@ -27,11 +31,14 @@ const ChartFanTooltip: FC = ({ return null; } - const quartiles = items[option]; - if (!quartiles) { + const activeItem = items[option]; + if (!activeItem) { return null; } + const quartiles = items[option].quartiles; + const question = items[option].question; + const padding = 10; const position = y + padding + HEIGHT > chartHeight ? "top" : "bottom"; @@ -46,15 +53,15 @@ const ChartFanTooltip: FC = ({
@@ -62,7 +69,7 @@ const ChartFanTooltip: FC = ({ ); }; -const TooltipItem: FC<{ label: string; value: number }> = ({ +const TooltipItem: FC<{ label: string; value: string }> = ({ label, value, }) => ( diff --git a/front_end/src/types/charts.ts b/front_end/src/types/charts.ts index a86ecffa69..4db729d4e8 100644 --- a/front_end/src/types/charts.ts +++ b/front_end/src/types/charts.ts @@ -1,4 +1,4 @@ -import { Quartiles } from "@/types/question"; +import { Quartiles, QuestionWithNumericForecasts } from "@/types/question"; export type TickFormat = ( value: number, @@ -31,6 +31,7 @@ export type FanOption = { name: string; quartiles: Quartiles; resolved: boolean; + question: QuestionWithNumericForecasts; }; export enum TimelineChartZoomOption { diff --git a/front_end/src/utils/charts.ts b/front_end/src/utils/charts.ts index fb262b9243..7721f63d3e 100644 --- a/front_end/src/utils/charts.ts +++ b/front_end/src/utils/charts.ts @@ -435,12 +435,14 @@ export function getFanOptionsFromNumericGroup( cdf: q.aggregations.recency_weighted.latest?.forecast_values ?? [], resolvedAt: new Date(q.scheduled_resolve_time), resolved: q.resolution !== null, + question: q, })) .sort((a, b) => differenceInMilliseconds(a.resolvedAt, b.resolvedAt)) - .map(({ name, cdf, resolved }) => ({ + .map(({ name, cdf, resolved, question }) => ({ name, quartiles: computeQuartilesFromCDF(cdf), resolved, + question, })); }