From 66e1b45705ef5e365191109180e3db98526dd550 Mon Sep 17 00:00:00 2001 From: Emily Przykucki Date: Fri, 25 Sep 2026 11:47:22 -0400 Subject: [PATCH] combine flu standard view and flu detailed view into one now there is a plot controls option to add the histogram (toggle on/off; default is off) --- app/src/components/ChartHeader.jsx | 6 ++++++ app/src/components/ViewSelector.jsx | 6 +----- .../controls/ForecastChartControls.jsx | 17 ++++++++++++++++ app/src/components/views/FluView.jsx | 18 ++++++++++------- app/src/contexts/ViewContext.jsx | 20 +++++++++++++++++++ app/src/utils/urlManager.js | 11 ++++++++++ 6 files changed, 66 insertions(+), 12 deletions(-) diff --git a/app/src/components/ChartHeader.jsx b/app/src/components/ChartHeader.jsx index 74fa7dcb..dfe788ba 100644 --- a/app/src/components/ChartHeader.jsx +++ b/app/src/components/ChartHeader.jsx @@ -58,6 +58,8 @@ const ChartHeader = ({ setShowLegend, showOtherGroundTruthSeasons, setShowOtherGroundTruthSeasons, + showHistogram, + setShowHistogram, } = useView(); const about = useContext(ChartAboutContext); @@ -148,6 +150,10 @@ const ChartHeader = ({ setShowOtherGroundTruthSeasons={setShowOtherGroundTruthSeasons} disableOtherGroundTruthSeasons={isSurveillance} showIntervals={!isSurveillance} + showHistogram={showHistogram} + setShowHistogram={ + viewType === "flu_forecasts" ? setShowHistogram : null + } /> diff --git a/app/src/components/ViewSelector.jsx b/app/src/components/ViewSelector.jsx index aa12e721..d67fb510 100644 --- a/app/src/components/ViewSelector.jsx +++ b/app/src/components/ViewSelector.jsx @@ -21,10 +21,6 @@ const ViewSelector = ({ showActive = true, onNavigate }) => { label: "Standard view", value: DATASETS.flu.defaultView, }, - { - label: "Detailed view", - value: "fludetailed", - }, { label: "Peak forecasts", value: "flu_peak", @@ -63,7 +59,7 @@ const ViewSelector = ({ showActive = true, onNavigate }) => { ]; const fluViewValues = new Set(fluViews.map((view) => view.value)); - const isFluActive = fluViewValues.has(viewType); + const isFluActive = fluViewValues.has(viewType) || viewType === "fludetailed"; useEffect(() => { if (isFluActive) { diff --git a/app/src/components/controls/ForecastChartControls.jsx b/app/src/components/controls/ForecastChartControls.jsx index 2b3297ef..adb2d03f 100644 --- a/app/src/components/controls/ForecastChartControls.jsx +++ b/app/src/components/controls/ForecastChartControls.jsx @@ -32,6 +32,8 @@ const ForecastChartControls = ({ disableOtherGroundTruthSeasons = false, showIntervals = true, intervalOptions = INTERVAL_OPTIONS, + showHistogram = false, + setShowHistogram = null, }) => { const selectedIntervals = intervalOptions .filter((option) => intervalVisibility?.[option.value]) @@ -92,6 +94,21 @@ const ForecastChartControls = ({ offLabel="Off" /> + {typeof setShowHistogram === "function" && ( + + + Histogram + + setShowHistogram(event.currentTarget.checked)} + size="sm" + onLabel="On" + offLabel="Off" + /> + + )} {typeof setShowOtherGroundTruthSeasons === "function" && ( { @@ -90,14 +94,14 @@ const FluView = ({ ]); const extraTraces = useMemo(() => { - if (viewType !== "fludetailed") return []; + if (!histogramVisible) return []; return rateChangeData.map((trace) => ({ ...trace, orientation: "h", xaxis: "x2", yaxis: "y2", })); - }, [rateChangeData, viewType]); + }, [rateChangeData, histogramVisible]); const activeModels = useMemo(() => { const activeModelSet = new Set(); @@ -127,7 +131,7 @@ const FluView = ({ } } - if (viewType === "fludetailed") { + if (histogramVisible) { const rateChangeSet = forecastsForDate["wk flu hosp rate change"]; if (rateChangeSet) { Object.keys(rateChangeSet).forEach((model) => @@ -138,7 +142,7 @@ const FluView = ({ }); return activeModelSet; - }, [forecasts, selectedDates, selectedTarget, viewType]); + }, [forecasts, selectedDates, selectedTarget, viewType, histogramVisible]); const forecastTarget = viewType === "flu" || viewType === "flu_forecasts" @@ -154,7 +158,7 @@ const FluView = ({ (baseLayout) => { const baseXAxis = { ...baseLayout.xaxis, - domain: viewType === "fludetailed" ? [0, 0.8] : baseLayout.xaxis.domain, + domain: histogramVisible ? [0, 0.8] : baseLayout.xaxis.domain, }; const nextLayout = { @@ -163,7 +167,7 @@ const FluView = ({ xaxis: baseXAxis, }; - if (viewType !== "fludetailed") { + if (!histogramVisible) { return nextLayout; } @@ -207,7 +211,7 @@ const FluView = ({ }, }; }, - [viewType, lastSelectedDate, colorScheme], + [histogramVisible, lastSelectedDate, colorScheme], ); if (viewType === "flu_peak") { diff --git a/app/src/contexts/ViewContext.jsx b/app/src/contexts/ViewContext.jsx index 81b976e2..275a272b 100644 --- a/app/src/contexts/ViewContext.jsx +++ b/app/src/contexts/ViewContext.jsx @@ -401,6 +401,9 @@ export const ViewProvider = ({ children }) => { ); const [showOtherGroundTruthSeasons, setShowOtherGroundTruthSeasons] = useState(() => urlManager.getAdvancedParams().showOtherGroundTruthSeasons); + const [showHistogram, setShowHistogram] = useState( + () => urlManager.getAdvancedParams().showHistogram, + ); const { data, @@ -875,6 +878,7 @@ export const ViewProvider = ({ children }) => { intervalVisibility: urlIntervals, showLegend: urlLegend, showOtherGroundTruthSeasons: urlShowOtherGroundTruthSeasons, + showHistogram: urlShowHistogram, } = urlManager.getAdvancedParams(); if (isSurveillanceView && urlShowOtherGroundTruthSeasons) { @@ -899,6 +903,9 @@ export const ViewProvider = ({ children }) => { if (urlShowOtherGroundTruthSeasons !== showOtherGroundTruthSeasons) { setShowOtherGroundTruthSeasons(urlShowOtherGroundTruthSeasons); } + if (urlShowHistogram !== showHistogram) { + setShowHistogram(urlShowHistogram); + } }, [ searchParams, location.pathname, @@ -909,6 +916,7 @@ export const ViewProvider = ({ children }) => { intervalVisibility, showLegend, showOtherGroundTruthSeasons, + showHistogram, ]); const setChartScaleWithUrl = useCallback( @@ -956,6 +964,16 @@ export const ViewProvider = ({ children }) => { [urlManager, isForecastPage], ); + const setShowHistogramWithUrl = useCallback( + (nextValue) => { + setShowHistogram(nextValue); + if (isForecastPage) { + urlManager.updateAdvancedParams({ showHistogram: nextValue }); + } + }, + [urlManager, isForecastPage], + ); + const contextValue = { selectedLocation, locationMessage, @@ -1018,6 +1036,8 @@ export const ViewProvider = ({ children }) => { setShowLegend: setShowLegendWithUrl, showOtherGroundTruthSeasons, setShowOtherGroundTruthSeasons: setShowOtherGroundTruthSeasonsWithUrl, + showHistogram, + setShowHistogram: setShowHistogramWithUrl, }; return ( diff --git a/app/src/utils/urlManager.js b/app/src/utils/urlManager.js index 688d16b3..2a21e48a 100644 --- a/app/src/utils/urlManager.js +++ b/app/src/utils/urlManager.js @@ -72,6 +72,7 @@ export class URLParameterManager { const intervalsParam = this.searchParams.get("intervals"); const legendParam = this.searchParams.get("legend"); const otherGtParam = this.searchParams.get("other_gt"); + const histogramParam = this.searchParams.get("histogram"); const chartScale = normalizeChartScale(scaleParam || DEFAULT_CHART_SCALE); let intervalVisibility = { ...DEFAULT_INTERVAL_VISIBILITY }; @@ -110,6 +111,7 @@ export class URLParameterManager { intervalVisibility, showLegend, showOtherGroundTruthSeasons, + showHistogram: histogramParam === "1" || histogramParam === "true", }; } @@ -142,6 +144,7 @@ export class URLParameterManager { intervalVisibility, showLegend, showOtherGroundTruthSeasons, + showHistogram, }) { const updatedParams = new URLSearchParams(this.searchParams); @@ -182,6 +185,14 @@ export class URLParameterManager { } } + if (typeof showHistogram === "boolean") { + if (showHistogram) { + updatedParams.set("histogram", "1"); + } else { + updatedParams.delete("histogram"); + } + } + if (updatedParams.toString() !== this.searchParams.toString()) { this.setSearchParams(updatedParams, { replace: true }); }