From 36810827e550d09f971cf7d7dab9f5564084b7e0 Mon Sep 17 00:00:00 2001 From: lsabor Date: Mon, 25 Nov 2024 14:01:36 -0800 Subject: [PATCH 1/5] add previous user forecast to continuous input --- front_end/messages/en.json | 2 + .../continuous_prediction_chart.tsx | 28 +++++++++++ .../forecast_maker/continuous_slider.tsx | 46 ++++++++++++++----- .../forecast_maker_continuous.tsx | 2 + .../charts/continuous_area_chart.tsx | 30 +++++++++--- front_end/src/types/charts.ts | 2 +- 6 files changed, 91 insertions(+), 19 deletions(-) diff --git a/front_end/messages/en.json b/front_end/messages/en.json index 353b40dda0..1b0032dfca 100644 --- a/front_end/messages/en.json +++ b/front_end/messages/en.json @@ -85,6 +85,8 @@ "secondQuartile": "median", "thirdQuartile": "upper 75%", "you": "you", + "youPrevious": "you (previous)", + "overlayPreviousForecast": "Overlay Previous Forecast", "resolutionDescriptionBinary": "Did this actually happen?", "resolutionDescriptionMultipleChoice": "Which of these actually happened?", "resolutionDescriptionContinuous": "What was the final result?", diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_prediction_chart.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_prediction_chart.tsx index f2eecf781c..7171478217 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_prediction_chart.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_prediction_chart.tsx @@ -15,6 +15,7 @@ import { cdfToPmf } from "@/utils/math"; type Props = { question: QuestionWithNumericForecasts; + overlayPreviousForecast?: boolean; dataset: { cdf: number[]; pmf: number[]; @@ -28,6 +29,7 @@ type Props = { const ContinuousPredictionChart: FC = ({ question, + overlayPreviousForecast, dataset, graphType, readOnly = false, @@ -59,6 +61,13 @@ const ContinuousPredictionChart: FC = ({ : graphType === "pmf" ? (hoverState.yData.user * 200).toFixed(3) : getForecastPctDisplayValue(hoverState.yData.user), + yUserPreviousLabel: readOnly + ? null + : !hoverState.yData.user_previous + ? null + : graphType === "pmf" + ? (hoverState.yData.user_previous * 200).toFixed(3) + : getForecastPctDisplayValue(hoverState.yData.user_previous), yCommunityLabel: !hoverState.yData.community ? null : graphType === "pmf" @@ -86,6 +95,14 @@ const ContinuousPredictionChart: FC = ({ }); } + if (overlayPreviousForecast && question.my_forecasts?.latest) { + charts.push({ + pmf: cdfToPmf(question.my_forecasts.latest.forecast_values), + cdf: question.my_forecasts.latest.forecast_values, + type: "user_previous", + }); + } + if (!readOnly || !!question.my_forecasts?.latest) { charts.push({ pmf: dataset.pmf, @@ -102,6 +119,7 @@ const ContinuousPredictionChart: FC = ({ readOnly, showCP, question.my_forecasts?.latest, + overlayPreviousForecast, ]); return ( @@ -136,6 +154,16 @@ const ContinuousPredictionChart: FC = ({ {")"} )} + {cursorDisplayData.yUserPreviousLabel !== null && ( + + + {cursorDisplayData.yUserPreviousLabel} + + {" ("} + {t("youPrevious")} + {")"} + + )} {showCP && cursorDisplayData.yCommunityLabel && ( diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx index 5c187cbd9a..e093d8a790 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx @@ -15,6 +15,7 @@ import { QuestionWithNumericForecasts } from "@/types/question"; import ContinuousPredictionChart from "./continuous_prediction_chart"; import { useHideCP } from "../cp_provider"; +import Checkbox from "@/components/ui/checkbox"; type Props = { forecast: MultiSliderValue[]; @@ -40,23 +41,46 @@ const ContinuousSlider: FC = ({ const { hideCP } = useHideCP(); const t = useTranslations(); const [graphType, setGraphType] = useState("pmf"); + const previousForecast = question.my_forecasts?.latest; + const [overlayPreviousForecast, setOverlayPreviousForecast] = + useState( + !!previousForecast?.forecast_values && !previousForecast.slider_values + ); return (
- - options={[ - { label: t("pdfLabel"), value: "pmf" }, - { label: t("cdfLabel"), value: "cdf" }, - ]} - defaultValue={graphType} - className="appearance-none border-none !p-0 text-sm" - onChange={(e) => - setGraphType(e.target.value as ContinuousAreaGraphType) - } - /> +
+ + options={[ + { label: t("pdfLabel"), value: "pmf" }, + { label: t("cdfLabel"), value: "cdf" }, + ]} + defaultValue={graphType} + className="appearance-none border-none !p-0 text-sm" + onChange={(e) => + setGraphType(e.target.value as ContinuousAreaGraphType) + } + /> + {previousForecast && ( +
+ setOverlayPreviousForecast(e.target.checked)} + className="mr-1" + /> + +
+ )} +
+ = ({ const [submitError, setSubmitError] = useState(); const withCommunityQuartiles = !user || !hideCP; const prevForecastValue = extractPrevNumericForecastValue(prevForecast); + const previousForecast = question.my_forecasts?.latest; const hasUserForecast = !!prevForecastValue.forecast; const t = useTranslations(); const [forecast, setForecast] = useState( @@ -80,6 +81,7 @@ const ForecastMakerContinuous: FC = ({ [forecast, question.open_lower_bound, question.open_upper_bound, weights] ); + const previousUserCdf = previousForecast?.forecast_values; const userCdf: number[] = dataset.cdf; const communityCdf: number[] | undefined = question.aggregations.recency_weighted.latest?.forecast_values; diff --git a/front_end/src/components/charts/continuous_area_chart.tsx b/front_end/src/components/charts/continuous_area_chart.tsx index 99710d654e..285d679831 100644 --- a/front_end/src/components/charts/continuous_area_chart.tsx +++ b/front_end/src/components/charts/continuous_area_chart.tsx @@ -35,10 +35,11 @@ import { computeQuartilesFromCDF } from "@/utils/math"; import LineCursorPoints from "./primitives/line_cursor_points"; -type ContinuousAreaColor = "orange" | "green"; +type ContinuousAreaColor = "orange" | "green" | "red"; const CHART_COLOR_MAP: Record = { community: "green", user: "orange", + user_previous: "orange", }; export type ContinuousAreaGraphInput = Array<{ @@ -159,7 +160,9 @@ const ContinuousAreaChart: FC = ({ color: getThemeColor( chart.color === "orange" ? METAC_COLORS.orange["800"] - : METAC_COLORS.olive["700"] + : chart.color === "green" + ? METAC_COLORS.olive["700"] + : METAC_COLORS.orange["500"] ), type: chart.type, }))} @@ -189,6 +192,7 @@ const ContinuousAreaChart: FC = ({ yData: { community: 0, user: 0, + user_previous: 0, }, } ); @@ -233,9 +237,15 @@ const ContinuousAreaChart: FC = ({ data: { fill: chart.color === "orange" - ? getThemeColor(METAC_COLORS.orange["700"]) - : undefined, - opacity: 0.3, + ? getThemeColor( + METAC_COLORS.orange[ + chart.type === "user" ? "700" : "400" + ] + ) + : chart.color === "green" + ? getThemeColor(METAC_COLORS.olive["500"]) + : undefined, + opacity: chart.type === "user_previous" ? 0.1 : 0.3, }, }} /> @@ -248,8 +258,14 @@ const ContinuousAreaChart: FC = ({ data: { stroke: chart.color === "orange" - ? getThemeColor(METAC_COLORS.orange["800"]) - : undefined, + ? getThemeColor( + METAC_COLORS.orange[ + chart.type === "user" ? "800" : "500" + ] + ) + : chart.color === "green" + ? getThemeColor(METAC_COLORS.olive["500"]) + : undefined, strokeDasharray: chart.color === "orange" ? "2,2" : undefined, }, }} diff --git a/front_end/src/types/charts.ts b/front_end/src/types/charts.ts index 12b71907a7..73f7dce3bb 100644 --- a/front_end/src/types/charts.ts +++ b/front_end/src/types/charts.ts @@ -41,7 +41,7 @@ export enum TimelineChartZoomOption { All = "all", } -export type ContinuousAreaType = "community" | "user"; +export type ContinuousAreaType = "community" | "user" | "user_previous"; export type ContinuousAreaHoverState = { x: number; From ac80b59bafce0f4ee2270df8bd95ce431ad9e26f Mon Sep 17 00:00:00 2001 From: lsabor Date: Mon, 25 Nov 2024 14:08:53 -0800 Subject: [PATCH 2/5] am simplify color dealings --- .../forecast_maker_question/forecast_maker_continuous.tsx | 2 -- front_end/src/components/charts/continuous_area_chart.tsx | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx index 3f9ee03d69..09657b4e73 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx @@ -54,7 +54,6 @@ const ForecastMakerContinuous: FC = ({ const [submitError, setSubmitError] = useState(); const withCommunityQuartiles = !user || !hideCP; const prevForecastValue = extractPrevNumericForecastValue(prevForecast); - const previousForecast = question.my_forecasts?.latest; const hasUserForecast = !!prevForecastValue.forecast; const t = useTranslations(); const [forecast, setForecast] = useState( @@ -81,7 +80,6 @@ const ForecastMakerContinuous: FC = ({ [forecast, question.open_lower_bound, question.open_upper_bound, weights] ); - const previousUserCdf = previousForecast?.forecast_values; const userCdf: number[] = dataset.cdf; const communityCdf: number[] | undefined = question.aggregations.recency_weighted.latest?.forecast_values; diff --git a/front_end/src/components/charts/continuous_area_chart.tsx b/front_end/src/components/charts/continuous_area_chart.tsx index 285d679831..b503ead834 100644 --- a/front_end/src/components/charts/continuous_area_chart.tsx +++ b/front_end/src/components/charts/continuous_area_chart.tsx @@ -35,7 +35,7 @@ import { computeQuartilesFromCDF } from "@/utils/math"; import LineCursorPoints from "./primitives/line_cursor_points"; -type ContinuousAreaColor = "orange" | "green" | "red"; +type ContinuousAreaColor = "orange" | "green"; const CHART_COLOR_MAP: Record = { community: "green", user: "orange", @@ -159,10 +159,8 @@ const ContinuousAreaChart: FC = ({ line: chart.graphLine, color: getThemeColor( chart.color === "orange" - ? METAC_COLORS.orange["800"] - : chart.color === "green" - ? METAC_COLORS.olive["700"] - : METAC_COLORS.orange["500"] + ? METAC_COLORS.orange[chart.type === "user" ? "800" : "500"] + : METAC_COLORS.olive["700"] ), type: chart.type, }))} From 30fa7caa2ca3dbda3e022ade732619ee8adad107 Mon Sep 17 00:00:00 2001 From: lsabor Date: Mon, 25 Nov 2024 14:28:57 -0800 Subject: [PATCH 3/5] add previous forecast to chart --- front_end/messages/en.json | 1 + .../forecast_maker/continuous_slider.tsx | 22 ++++---- .../forecast_maker_continuous.tsx | 22 ++++++++ .../forecast_maker/numeric_table.tsx | 54 +++++++++++++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/front_end/messages/en.json b/front_end/messages/en.json index 1b0032dfca..44840a24f3 100644 --- a/front_end/messages/en.json +++ b/front_end/messages/en.json @@ -72,6 +72,7 @@ "metaculusPredictionLabel": "Metaculus Prediction", "userPredictionLabel": "User Prediction", "myPrediction": "My Prediction", + "myPredictionPrevious": "My Prediction (previous)", "myPredictionValue": "My Prediction: {forecastValue}", "myCoverageLabel": "My Coverage", "predictionLabel": "Prediction", diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx index e093d8a790..8e58dffc28 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/continuous_slider.tsx @@ -25,6 +25,8 @@ type Props = { pmf: number[]; }; onChange: (forecast: MultiSliderValue[], weights: number[]) => void; + overlayPreviousForecast: boolean; + setOverlayPreviousForecast: (value: boolean) => void; question: QuestionWithNumericForecasts; disabled?: boolean; }; @@ -34,6 +36,8 @@ const ContinuousSlider: FC = ({ weights, dataset, onChange, + overlayPreviousForecast, + setOverlayPreviousForecast, question, disabled = false, }) => { @@ -42,10 +46,6 @@ const ContinuousSlider: FC = ({ const t = useTranslations(); const [graphType, setGraphType] = useState("pmf"); const previousForecast = question.my_forecasts?.latest; - const [overlayPreviousForecast, setOverlayPreviousForecast] = - useState( - !!previousForecast?.forecast_values && !previousForecast.slider_values - ); return (
@@ -63,16 +63,12 @@ const ContinuousSlider: FC = ({ /> {previousForecast && (
- setOverlayPreviousForecast(e.target.checked)} - className="mr-1" - /> - + onChange={(checked) => setOverlayPreviousForecast(checked)} + className={"text-sm"} + label={t("overlayPreviousForecast")} + >
)}
diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx index 09657b4e73..1411baa6d4 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx @@ -68,6 +68,11 @@ const ForecastMakerContinuous: FC = ({ const [weights, setWeights] = useState( prevForecastValue?.weights ?? [1] ); + const previousForecast = question.my_forecasts?.latest; + const [overlayPreviousForecast, setOverlayPreviousForecast] = + useState( + !!previousForecast?.forecast_values && !previousForecast.slider_values + ); const dataset = useMemo( () => @@ -81,6 +86,10 @@ const ForecastMakerContinuous: FC = ({ ); const userCdf: number[] = dataset.cdf; + const userPreviousCdf: number[] | undefined = + overlayPreviousForecast && previousForecast + ? previousForecast.forecast_values + : undefined; const communityCdf: number[] | undefined = question.aggregations.recency_weighted.latest?.forecast_values; @@ -131,6 +140,8 @@ const ForecastMakerContinuous: FC = ({ setWeights(weight); setIsDirty(true); }} + overlayPreviousForecast={overlayPreviousForecast} + setOverlayPreviousForecast={setOverlayPreviousForecast} question={question} disabled={!canPredict} /> @@ -176,6 +187,17 @@ const ForecastMakerContinuous: FC = ({ aboveUpper: 1 - userCdf[userCdf.length - 1], }} userQuartiles={userCdf ? computeQuartilesFromCDF(userCdf) : undefined} + userPreviousBounds={ + userPreviousCdf + ? { + belowLower: userPreviousCdf[0], + aboveUpper: 1 - userPreviousCdf[userPreviousCdf.length - 1], + } + : undefined + } + userPreviousQuartiles={ + userPreviousCdf ? computeQuartilesFromCDF(userPreviousCdf) : undefined + } communityBounds={ communityCdf ? { diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/numeric_table.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/numeric_table.tsx index fd41679e7c..b6b1ada23e 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/numeric_table.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/numeric_table.tsx @@ -12,6 +12,8 @@ type Props = { question: QuestionWithNumericForecasts; userBounds?: Bounds; userQuartiles?: Quartiles; + userPreviousBounds?: Bounds; + userPreviousQuartiles?: Quartiles; communityBounds?: Bounds; communityQuartiles?: Quartiles; withUserQuartiles?: boolean; @@ -24,6 +26,8 @@ const NumericForecastTable: FC = ({ question, userBounds, userQuartiles, + userPreviousBounds, + userPreviousQuartiles, withUserQuartiles = true, communityBounds, communityQuartiles, @@ -44,6 +48,13 @@ const NumericForecastTable: FC = ({
)} + {withUserQuartiles && userPreviousQuartiles && ( + <> +
+ {t("myPredictionPrevious")} +
+ + )} {withCommunityQuartiles && ( <> @@ -123,6 +134,49 @@ const NumericForecastTable: FC = ({ )} )} + {withUserQuartiles && userPreviousQuartiles && ( +
+ <> + {question.open_lower_bound && ( +
+ {userPreviousBounds && + (userPreviousBounds.belowLower * 100).toFixed(1)} + % +
+ )} +
+ {checkQuartilesOutOfBorders(userPreviousQuartiles?.lower25)} + {getDisplayValue( + userPreviousQuartiles?.lower25, + question.type, + question.scaling, + 4 + )} +
+
+ {checkQuartilesOutOfBorders(userPreviousQuartiles?.median)} + {getDisplayValue( + userPreviousQuartiles?.median, + question.type, + question.scaling, + 4 + )} +
+
+ {checkQuartilesOutOfBorders(userPreviousQuartiles?.upper75)} + {getDisplayValue( + userPreviousQuartiles?.upper75, + question.type, + question.scaling, + 4 + )} +
+ {question.open_upper_bound && ( +
{(userPreviousBounds!.aboveUpper * 100).toFixed(1)}%
+ )} + +
+ )} {withCommunityQuartiles && (
From 34ff7f060ea2ebf8dd37ffbbdfe36c8c20024ab8 Mon Sep 17 00:00:00 2001 From: lsabor Date: Mon, 25 Nov 2024 14:39:37 -0800 Subject: [PATCH 4/5] get it to work with continuous groups --- .../forecast_maker_group_continuous.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 bd98ac54d1..4ed6b75fe4 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 @@ -267,6 +267,12 @@ const ForecastMakerGroupContinuous: FC = ({ } }, [postId, questionsToSubmit]); + const previousForecast = activeGroupOption?.question.my_forecasts?.latest; + const [overlayPreviousForecast, setOverlayPreviousForecast] = + useState( + !!previousForecast?.forecast_values && !previousForecast.slider_values + ); + const userCdf: number[] | undefined = activeGroupOption && getNumericForecastDataset( @@ -275,6 +281,10 @@ const ForecastMakerGroupContinuous: FC = ({ activeGroupOption?.question.open_lower_bound!, activeGroupOption?.question.open_upper_bound! ).cdf; + const userPreviousCdf: number[] | undefined = + overlayPreviousForecast && previousForecast + ? previousForecast.forecast_values + : undefined; const communityCdf: number[] | undefined = activeGroupOption?.question.aggregations.recency_weighted.latest ?.forecast_values; @@ -312,6 +322,8 @@ const ForecastMakerGroupContinuous: FC = ({ @@ -387,6 +399,19 @@ const ForecastMakerGroupContinuous: FC = ({ } } userQuartiles={activeGroupOption.userQuartiles ?? undefined} + userPreviousBounds={ + userPreviousCdf + ? { + belowLower: userPreviousCdf[0], + aboveUpper: 1 - userPreviousCdf[userPreviousCdf.length - 1], + } + : undefined + } + userPreviousQuartiles={ + userPreviousCdf + ? computeQuartilesFromCDF(userPreviousCdf) + : undefined + } communityBounds={ communityCdf && { belowLower: communityCdf[0], From 99c578c4e4791be07950c488fbd522de2a3d759e Mon Sep 17 00:00:00 2001 From: lsabor Date: Mon, 25 Nov 2024 14:43:29 -0800 Subject: [PATCH 5/5] get it to work with conditional continuous --- .../forecast_maker_conditional_continuous.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx index 80abcd4c1d..43eb6ecbd6 100644 --- a/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx +++ b/front_end/src/app/(main)/questions/[id]/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx @@ -315,6 +315,12 @@ const ForecastMakerConditionalContinuous: FC = ({ } }; + const previousForecast = activeOptionData?.question.my_forecasts?.latest; + const [overlayPreviousForecast, setOverlayPreviousForecast] = + useState( + !!previousForecast?.forecast_values && !previousForecast.slider_values + ); + const userCdf: number[] | undefined = activeOptionData && getNumericForecastDataset( @@ -323,6 +329,10 @@ const ForecastMakerConditionalContinuous: FC = ({ activeOptionData.question.open_lower_bound!, activeOptionData.question.open_upper_bound! ).cdf; + const userPreviousCdf: number[] | undefined = + overlayPreviousForecast && previousForecast + ? previousForecast.forecast_values + : undefined; const communityCdf: number[] | undefined = activeOptionData?.question.aggregations.recency_weighted.latest ?.forecast_values; @@ -361,6 +371,8 @@ const ForecastMakerConditionalContinuous: FC = ({ question={option.question} forecast={option.sliderForecast} weights={option.weights} + overlayPreviousForecast={overlayPreviousForecast} + setOverlayPreviousForecast={setOverlayPreviousForecast} dataset={getNumericForecastDataset( option.sliderForecast, option.weights, @@ -450,6 +462,19 @@ const ForecastMakerConditionalContinuous: FC = ({ aboveUpper: 1 - communityCdf![communityCdf!.length - 1], } } + userPreviousBounds={ + userPreviousCdf + ? { + belowLower: userPreviousCdf[0], + aboveUpper: 1 - userPreviousCdf[userPreviousCdf.length - 1], + } + : undefined + } + userPreviousQuartiles={ + userPreviousCdf + ? computeQuartilesFromCDF(userPreviousCdf) + : undefined + } communityQuartiles={ communityCdf && computeQuartilesFromCDF(communityCdf) }