Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4624e0a
shell of `MyPlots` feature
emprzy Mar 9, 2026
e144d76
grid background, directory re-route
emprzy Mar 9, 2026
bf09647
basic implementation of `Add to My Plots` button
emprzy Mar 12, 2026
8125919
add more detail to `plotData` object
emprzy Mar 13, 2026
d8f2166
test storage implementation
emprzy Mar 13, 2026
157c34d
proof that storage method works
emprzy Mar 13, 2026
5829167
data piped in successfully
emprzy Mar 16, 2026
f11498e
add animation for "Add to My Plots" button
emprzy Mar 16, 2026
9951ac5
match button animation between 'share view' and 'add to my plots'
emprzy Mar 16, 2026
1e2ac2a
add ability to delete plots from My Plots tab
emprzy Mar 16, 2026
50feb4a
add `dates` as a feature of `plotData`
emprzy Mar 17, 2026
954f7ba
add dates to storage and to `MyPlots.jsx`
emprzy Mar 17, 2026
fdb2a89
capture advanced controls in `plotData`
emprzy Mar 25, 2026
b45165e
outsource visualization logic to `MiniPlot.jsx`
emprzy Mar 25, 2026
3287587
basic visualization implemented
emprzy Mar 25, 2026
900c3a0
fix NHSN visualization
emprzy Mar 26, 2026
0651a96
add date padding for forecast view and NHSN
emprzy Mar 26, 2026
d076297
adjust y axis scaling
emprzy Mar 26, 2026
50e6311
fix NHSN % column yaxis display
emprzy Mar 30, 2026
81711cd
fix yaxis number displays for `MiniPlot.jsx`
emprzy Mar 30, 2026
acee75f
Add hover label for `PLOT INFO`, remove temporary info display
emprzy Mar 31, 2026
2568cac
move location to the top of each little card
emprzy Mar 31, 2026
cb5dd3f
map to better view display name
emprzy Mar 31, 2026
9207dbe
fix bug for default columns per NHSN "target"
emprzy Mar 31, 2026
3e61d6b
map slugs to longform display names
emprzy Mar 31, 2026
b965ae6
change name to `NHSN Surveillance Data` instead of `NHSN Respiratory …
emprzy Mar 31, 2026
43d6514
flexible expansion of hover text info
emprzy Mar 31, 2026
e2698e8
badge-ify plot info
emprzy Mar 31, 2026
c082bb8
add % character to y axis when applicable
emprzy Apr 1, 2026
e96fde8
enable scaling and zooming with mouse
emprzy Apr 1, 2026
3f48d47
linting in `extractPlotDataFromURL.js`
emprzy Apr 1, 2026
74fa18e
linting fix and updates in `plotStorage.js`
emprzy Apr 1, 2026
fb34a2a
remove 'Add to My Plots' button from peaks view
emprzy Apr 1, 2026
903de54
change height of plots
emprzy Apr 1, 2026
e3e247d
Merge pull request #97 from ACCIDDA/my-plots
emprzy Apr 1, 2026
7adb7e5
column naming updates (NHSN)
emprzy Apr 1, 2026
d0b0168
fix Info Overlay respilens logo being cut off
emprzy Apr 1, 2026
85a82d6
fix no columns selected in NHSN view bug
emprzy Apr 2, 2026
0fbc7e0
fix NHSN infinite re-load bug that made its way onto staging
emprzy Apr 2, 2026
0a59ff4
add "no forecast data available for the current selection" message to…
emprzy Apr 2, 2026
a425472
Merge pull request #98 from ACCIDDA/updates
emprzy Apr 2, 2026
6589914
update `locations.csv`
emprzy Apr 6, 2026
31eff42
remove dependency on flusight, covid19, rsv forecast hubs `locations.…
emprzy Apr 6, 2026
251e4ee
Merge pull request #99 from ACCIDDA/remove-loccsv-dep
emprzy Apr 6, 2026
db52856
remove granular hover label for My Plots
emprzy Apr 6, 2026
984e3f1
Add a banner for `myplots`
emprzy Apr 6, 2026
8cf0d03
add alpha notice to My Plots
emprzy Apr 6, 2026
8e72cf5
Add a banner for `myplots`
emprzy Apr 6, 2026
398bb29
add alpha notice to My Plots
emprzy Apr 6, 2026
649759d
Merge branch 'myplots-updates' of https://github.com/ACCIDDA/RespiLen…
emprzy Apr 6, 2026
1288deb
put back granular hover label
emprzy Apr 6, 2026
a29fb3a
Merge pull request #100 from ACCIDDA/myplots-updates
emprzy Apr 6, 2026
069e874
hubs now all use `target_end_date` in their timeseries target data
emprzy Apr 21, 2026
4ee364f
remove `ground_truth_date_col` as a config for hubverse
emprzy Apr 21, 2026
cfacac1
Merge pull request #102 from ACCIDDA/gt_date_column
emprzy Apr 21, 2026
27c33ab
fix space typo
emprzy Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HelmetProvider } from "react-helmet-async";
import { ViewProvider } from "./contexts/ViewContext";
import { useView } from "./hooks/useView";
import DataVisualizationContainer from "./components/DataVisualizationContainer";
import MyPlots from "./components/myplots/MyPlots";
import NarrativeBrowser from "./components/narratives/NarrativeBrowser";
import SlideNarrativeViewer from "./components/narratives/SlideNarrativeViewer";
import ForecastleGame from "./components/forecastle/ForecastleGame";
Expand All @@ -20,7 +21,6 @@ import Documentation from "./components/Documentation";
import ReportingDelayPage from "./components/reporting/ReportingDelayPage";
import ToolsPage from "./components/tools/ToolsPage";
import { Center, Text } from "@mantine/core";
// import ShutdownBanner from './components/ShutdownBanner';, no longer necessary

const ForecastApp = () => {
// This component uses the view context, so it must be inside the provider.
Expand Down Expand Up @@ -59,6 +59,7 @@ const AppLayout = () => {
<Route path="/narratives/:id" element={<SlideNarrativeViewer />} />
<Route path="/forecastle" element={<ForecastleGame />} />
<Route path="/epidemics10" element={<TournamentDashboard />} />
<Route path="/myplots" element={<MyPlots />} />
<Route path="/myrespilens" element={<MyRespiLensDashboard />} />
<Route path="/toolbox" element={<ToolsPage />} />
<Route path="/reporting-triangle" element={<ReportingDelayPage />} />
Expand Down
80 changes: 63 additions & 17 deletions app/src/components/DataVisualizationContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ import {
List,
} from "@mantine/core";
import { useView } from "../hooks/useView";
import { extractPlotData } from "../hooks/extractPlotDataFromURL";
import DateSelector from "./DateSelector";
import ViewSwitchboard from "./ViewSwitchboard";
import ErrorBoundary from "./ErrorBoundary";
import AboutHubOverlay from "./AboutHubOverlay";
import FrontPage from "./FrontPage";
import { IconShare, IconBrandGithub } from "@tabler/icons-react";
import {
IconShare,
IconBrandGithub,
IconChartScatter,
} from "@tabler/icons-react";
import { useClipboard } from "@mantine/hooks";

const DataVisualizationContainer = () => {
Expand Down Expand Up @@ -51,6 +56,17 @@ const DataVisualizationContainer = () => {
});
const clipboard = useClipboard({ timeout: 2000 });

const [isAdded, setIsAdded] = useState(false);
const handleSaveToMyPlots = () => {
const plotData = extractPlotData(viewType, window.location.href, data);
// visual cue to signal it has been added
setIsAdded(true);
// Reset text after 2 seconds
setTimeout(() => setIsAdded(false), 2000);
// Temporary console feedback for development
console.log("Saved the plot", plotData);
};

// Configuration for AboutHubOverlay based on viewType
const aboutHubConfig = {
covid_forecasts: {
Expand Down Expand Up @@ -552,22 +568,38 @@ const DataVisualizationContainer = () => {
</AboutHubOverlay>
)}
{windowSize.width <= 800 && (
<Tooltip
label={
clipboard.copied
? "Link copied"
: "Copy link to this view"
}
>
<Button
variant="light"
size="xs"
leftSection={<IconShare size={16} />}
onClick={handleShare}
<Group gap="xs">
{viewType !== "flu_peak" && (
<Button
variant="light"
size="xs"
mr="xs"
color={isAdded ? "green" : "blue"}
className={isAdded ? "added-text-pulse" : ""}
leftSection={<IconChartScatter size={16} />}
onClick={handleSaveToMyPlots}
>
{isAdded ? "Added!" : "Add to My Plots"}
</Button>
)}
<Tooltip
label={
clipboard.copied
? "Link copied"
: "Copy link to this view"
}
>
{clipboard.copied ? "URL Copied" : "Share View"}
</Button>
</Tooltip>
<Button
variant="light"
size="xs"
color={clipboard.copied ? "green" : "blue"}
leftSection={<IconShare size={16} />}
onClick={handleShare}
>
{clipboard.copied ? "URL Copied!" : "Share View"}
</Button>
</Tooltip>
</Group>
)}
</div>
{currentDataset?.hasDateSelector && windowSize.width > 800 && (
Expand All @@ -585,6 +617,19 @@ const DataVisualizationContainer = () => {
)}
{windowSize.width > 800 && (
<div style={{ display: "flex", justifyContent: "flex-end" }}>
{viewType !== "flu_peak" && (
<Button
variant="light"
size="xs"
mr="xs"
color={isAdded ? "green" : "blue"}
className={isAdded ? "added-text-pulse" : ""}
leftSection={<IconChartScatter size={16} />}
onClick={handleSaveToMyPlots}
>
{isAdded ? "Added!" : "Add to My Plots"}
</Button>
)}
<Tooltip
label={
clipboard.copied
Expand All @@ -595,10 +640,11 @@ const DataVisualizationContainer = () => {
<Button
variant="light"
size="xs"
color={clipboard.copied ? "green" : "blue"}
leftSection={<IconShare size={16} />}
onClick={handleShare}
>
{clipboard.copied ? "URL Copied" : "Share View"}
{clipboard.copied ? "URL Copied!" : "Share View"}
</Button>
</Tooltip>
</div>
Expand Down
35 changes: 32 additions & 3 deletions app/src/components/ForecastPlotView.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo, useRef, useCallback } from "react";
import { useMantineColorScheme, Stack, Text } from "@mantine/core";
import { useMantineColorScheme, Stack, Text, Box, Center } from "@mantine/core";
import Plot from "react-plotly.js";
import Plotly from "plotly.js/dist/plotly";
import ModelSelector from "./ModelSelector";
Expand Down Expand Up @@ -359,6 +359,7 @@ const ForecastPlotView = ({
return baseConfig;
}, [calculateYRange, configOverrides]);

const hasForecasts = projectionsData.length > 1;
if (requireTarget && !selectedTarget) {
return (
<Stack align="center" justify="center" style={{ height: "300px" }}>
Expand All @@ -374,12 +375,40 @@ const ForecastPlotView = ({
timestamp={metadata?.last_updated}
/>
<div
style={{ width: "100%", height: "min(800px, 60vh)", minHeight: 320 }}
style={{
width: "100%",
height: "min(800px, 60vh)",
minHeight: 320,
position: "relative", // Ensure the container is relative for absolute positioning
}}
>
{!hasForecasts && (
<Box
style={{
position: "absolute",
top: 80, // Adjusted slightly for the larger main view
left: 0,
right: 0,
zIndex: 1,
pointerEvents: "none",
}}
>
<Center>
<Text size="sm" c="dimmed" fs="italic">
No forecast data available for the current selection
</Text>
</Center>
</Box>
)}

<Plot
ref={plotRef}
useResizeHandler
style={{ width: "100%", height: "100%" }}
style={{
width: "100%",
height: "100%",
opacity: hasForecasts ? 1 : 0.6, // Dim the plot if no forecasts exist
}}
data={finalTraces}
layout={layout}
config={config}
Expand Down
24 changes: 24 additions & 0 deletions app/src/components/FrontPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ import NHSNOverviewGraph from "./NHSNOverviewGraph";
import Announcement from "./Announcement";
import { useView } from "../hooks/useView";

const MyPlotsLink = () => {
return (
<span>
Check out the new{" "}
<Anchor
href="/myplots"
fw={700}
c="blue.7"
style={{ fontSize: "inherit", verticalAlign: "baseline" }}
>
My Plots
</Anchor>{" "}
feature, where you can assemble your own dashboard of saved plots.
</span>
);
};

const MetroCastLink = () => {
const { setViewType } = useView();

Expand Down Expand Up @@ -41,6 +58,13 @@ const FrontPage = () => {
announcementType={"update"}
text={<MetroCastLink />}
/>
<Announcement
id="new-myplots-feature"
startDate="2026-04-06"
endDate="2026-06-30"
announcementType={"update"}
text={<MyPlotsLink />}
/>
<Announcement
id={"hub-seasonal-warning"}
startDate={"2026-05-31"}
Expand Down
31 changes: 17 additions & 14 deletions app/src/components/InfoOverlay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ const InfoOverlay = () => {
opened={opened}
onClose={close}
title={
<Group gap="md">
<Group gap="sm" wrap="nowrap" align="center">
<Image
src="respilens-logo.svg"
alt="RespiLens logo"
h={32}
w={32}
fit="contain"
/>
<Title order={2} c="blue">
<Title order={2} c="blue" style={{ lineHeight: 1 }}>
RespiLens
</Title>
</Group>
Expand All @@ -78,12 +79,14 @@ const InfoOverlay = () => {
<List.Item>
URL-shareable views for specific forecast settings
</List.Item>
<List.Item>Responsive and mobile-friendly site</List.Item>
<List.Item>Frequent and automatic site updates</List.Item>
<List.Item>
Responsive and mobile-friendly site with frequent and automatic
updates
</List.Item>
<List.Item>Multi date, target, and model comparison</List.Item>
<List.Item>the Forecastle game!</List.Item>
<List.Item>
MyRespiLens, a safe visualization tool for your own data
MyRespiLens: a safe visualization tool for your own data
</List.Item>
</List>

Expand All @@ -96,8 +99,8 @@ const InfoOverlay = () => {
<Anchor href="https://hubverse.io" target="_blank" rel="noopener">
Hubverse
</Anchor>{" "}
project which standardizes and consolidates forecast data formats.
For each of the hub displayed on RespiLens, the data, organization
project, which standardizes and consolidates forecast data formats.
For each of the hubs displayed on RespiLens, the data, organization,
and forecasts belong to their respective teams.{" "}
<strong>
RespiLens is only a visualization layer, and contains no original
Expand All @@ -106,7 +109,7 @@ const InfoOverlay = () => {
</div>

<Text>
You can find information and alternative visualization for each
You can find information (and alternative visualization) for each
pathogen at the following locations:
</Text>
<List spacing="xs" size="sm">
Expand All @@ -119,15 +122,15 @@ const InfoOverlay = () => {
>
official CDC page
</Anchor>{" "}
{" "}
|{" "}
<Anchor
href="https://reichlab.io/flusight-dashboard/"
target="_blank"
rel="noopener"
>
Hubverse dashboard
</Anchor>{" "}
{" "}
|{" "}
<Anchor
href="https://github.com/cdcepi/FluSight-forecast-hub"
target="_blank"
Expand Down Expand Up @@ -155,15 +158,15 @@ const InfoOverlay = () => {
>
official CDC page
</Anchor>{" "}
{" "}
|{" "}
<Anchor
href="https://reichlab.io/covidhub-dashboard"
target="_blank"
rel="noopener"
>
Hubverse dashboard
</Anchor>{" "}
 
| 
<Anchor
href="https://github.com/CDCgov/covid19-forecast-hub"
target="_blank"
Expand All @@ -181,15 +184,15 @@ const InfoOverlay = () => {
>
official dashboard
</Anchor>{" "}
{" "}
|{" "}
<Anchor
href="https://reichlab.io/metrocast-dashboard/"
target="_blank"
rel="noopener noreferrer"
>
site
</Anchor>{" "}
– 
|
<Anchor
href="https://github.com/reichlab/flu-metrocast"
target="_blank"
Expand Down
13 changes: 12 additions & 1 deletion app/src/components/layout/MainNavigation.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useLocation, Link } from "react-router-dom";
import { Group, Button, Image, Title, Anchor } from "@mantine/core";
import { IconChartLine, IconTarget, IconDashboard } from "@tabler/icons-react";
import {
IconChartLine,
IconTarget,
IconDashboard,
IconChartScatter,
} from "@tabler/icons-react";
import InfoOverlay from "../InfoOverlay";
import { useView } from "../../hooks/useView";

Expand Down Expand Up @@ -30,6 +35,12 @@ const MainNavigation = () => {
icon: IconDashboard,
active: isActive("/myrespilens"),
},
{
href: "/myplots",
label: "My Plots (α)",
icon: IconChartScatter,
active: isActive("/myplots"),
},
// { href: '/documentation', label: 'Documentation', icon: IconClipboard, active: isActive('/documentation')}
];

Expand Down
Loading
Loading