diff --git a/front_end/src/components/charts/fan_chart.tsx b/front_end/src/components/charts/fan_chart.tsx index 6ec2b3acba..2c3c409eeb 100644 --- a/front_end/src/components/charts/fan_chart.tsx +++ b/front_end/src/components/charts/fan_chart.tsx @@ -65,6 +65,7 @@ const FanChart: FC = ({ const { ref: chartContainerRef, width: chartWidth } = useContainerSize(); + const filteredOptions = [...options].filter((o) => o.resolved || o.quartiles); const { theme, getThemeColor } = useAppTheme(); const chartTheme = theme === "dark" ? darkTheme : lightTheme; const actualTheme = extraTheme @@ -79,7 +80,11 @@ const FanChart: FC = ({ [options] ); - const labels = adjustLabelsForDisplay(options, chartWidth, actualTheme); + const labels = adjustLabelsForDisplay( + filteredOptions, + chartWidth, + actualTheme + ); const yScale = generateScale({ displayType: options[0].question.type, axisLength: height, @@ -93,10 +98,13 @@ const FanChart: FC = ({ const tooltipItems = useMemo( () => - options.reduce< + filteredOptions.reduce< Record< string, - { quartiles: Quartiles; question: QuestionWithNumericForecasts } + { + quartiles: Quartiles | undefined; + question: QuestionWithNumericForecasts; + } > >( (acc, el) => ({ @@ -105,7 +113,7 @@ const FanChart: FC = ({ }), {} ), - [options] + [filteredOptions] ); const shouldDisplayChart = !!chartWidth; @@ -252,7 +260,7 @@ function buildChartData(options: FanOption[]) { []; const zeroPoints: number[] = []; options.forEach((option) => { - if (option.question.scaling.zero_point !== null) { + if (option.question.scaling.zero_point !== null && !!option.quartiles) { zeroPoints.push(option.question.scaling.zero_point); } }); @@ -282,20 +290,22 @@ function buildChartData(options: FanOption[]) { if (options[0].question.type === QuestionType.Binary) { for (const option of options) { - line.push({ - x: option.name, - y: option.quartiles.median, - }); - area.push({ - x: option.name, - y0: option.quartiles.lower25, - y: option.quartiles.upper75, - }); - points.push({ - x: option.name, - y: option.quartiles.median, - resolved: false, - }); + if (!!option.quartiles) { + line.push({ + x: option.name, + y: option.quartiles.median, + }); + area.push({ + x: option.name, + y0: option.quartiles.lower25, + y: option.quartiles.upper75, + }); + points.push({ + x: option.name, + y: option.quartiles.median, + resolved: false, + }); + } if (option.resolved) { resolutionPoints.push({ x: option.name, @@ -306,41 +316,46 @@ function buildChartData(options: FanOption[]) { } } else { for (const option of options) { - // scale up the values to nominal values - // then unscale by the derived scaling - const median = unscaleNominalLocation( - scaleInternalLocation(option.quartiles.median, option.question.scaling), - scaling - ); - const lower25 = unscaleNominalLocation( - scaleInternalLocation( - option.quartiles.lower25, - option.question.scaling - ), - scaling - ); - const upper75 = unscaleNominalLocation( - scaleInternalLocation( - option.quartiles.upper75, - option.question.scaling - ), - scaling - ); + if (!!option.quartiles) { + // scale up the values to nominal values + // then unscale by the derived scaling + const median = unscaleNominalLocation( + scaleInternalLocation( + option.quartiles.median, + option.question.scaling + ), + scaling + ); + const lower25 = unscaleNominalLocation( + scaleInternalLocation( + option.quartiles.lower25, + option.question.scaling + ), + scaling + ); + const upper75 = unscaleNominalLocation( + scaleInternalLocation( + option.quartiles.upper75, + option.question.scaling + ), + scaling + ); - line.push({ - x: option.name, - y: median, - }); - area.push({ - x: option.name, - y0: lower25, - y: upper75, - }); - points.push({ - x: option.name, - y: median, - resolved: false, - }); + line.push({ + x: option.name, + y: median, + }); + area.push({ + x: option.name, + y0: lower25, + y: upper75, + }); + points.push({ + x: option.name, + y: median, + resolved: false, + }); + } if (option.resolved) { resolutionPoints.push({ x: option.name, 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 4945b88e7b..ea3a9af41e 100644 --- a/front_end/src/components/charts/primitives/chart_fan_tooltip.tsx +++ b/front_end/src/components/charts/primitives/chart_fan_tooltip.tsx @@ -11,7 +11,7 @@ const HEIGHT = 70; type Props = ComponentProps & { items: Record< string, - { quartiles: Quartiles; question: QuestionWithNumericForecasts } + { quartiles: Quartiles | undefined; question: QuestionWithNumericForecasts } >; width: number; chartHeight: number; @@ -43,6 +43,9 @@ const ChartFanTooltip: FC = ({ const padding = 10; const position = y + padding + HEIGHT > chartHeight ? "top" : "bottom"; + if (!quartiles) { + return null; + } return ( diff --git a/front_end/src/types/charts.ts b/front_end/src/types/charts.ts index 12b71907a7..c62bb59050 100644 --- a/front_end/src/types/charts.ts +++ b/front_end/src/types/charts.ts @@ -29,7 +29,7 @@ export type NumericChartType = "date" | "numeric" | "binary"; export type FanOption = { name: string; - quartiles: Quartiles; + quartiles: Quartiles | undefined; resolved: boolean; question: QuestionWithNumericForecasts; }; diff --git a/front_end/src/utils/charts.ts b/front_end/src/utils/charts.ts index bcaadde0b0..2594cdc306 100644 --- a/front_end/src/utils/charts.ts +++ b/front_end/src/utils/charts.ts @@ -634,7 +634,7 @@ export function getFanOptionsFromContinuousGroup( .sort((a, b) => differenceInMilliseconds(a.resolvedAt, b.resolvedAt)) .map(({ name, cdf, resolved, question }) => ({ name, - quartiles: computeQuartilesFromCDF(cdf), + quartiles: cdf.length > 0 ? computeQuartilesFromCDF(cdf) : undefined, resolved, question, })); @@ -649,11 +649,13 @@ export function getFanOptionsFromBinaryGroup( const resolved = q.resolution !== null; return { name: q.label, - quartiles: { - median: aggregation?.centers?.[0] ?? 0, - lower25: aggregation?.interval_lower_bounds?.[0] ?? 0, - upper75: aggregation?.interval_upper_bounds?.[0] ?? 0, - }, + quartiles: !!aggregation + ? { + median: aggregation.centers?.[0] ?? 0, + lower25: aggregation.interval_lower_bounds?.[0] ?? 0, + upper75: aggregation.interval_upper_bounds?.[0] ?? 0, + } + : undefined, resolved, question: q, resolvedAt: new Date(q.scheduled_resolve_time),