From 26a497f2b1673697083e4f873ee4860e6d0abf8a Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Wed, 26 Mar 2025 14:19:28 +0100
Subject: [PATCH 1/4] fail in no data

---
 src/data.py | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/src/data.py b/src/data.py
index 487550d..06485da 100644
--- a/src/data.py
+++ b/src/data.py
@@ -19,8 +19,14 @@ def load_from_thredds(
 ) -> (pd.DataFrame, dict[int, str]):  # type: ignore
     """
     Load multiple netCDF files from a THREDS TDS into a single DataFrame.
+
+    Raises:
+        ValueError: If 'ACN' not found in dataset.
+        ValueError: If no datasets are found.
     """
+
     log.debug("begin load data from THREDDS...")
+
     dataframes = []
     airports = {}
 
@@ -54,7 +60,11 @@ def load_from_thredds(
 
         dataframes.append(df)
 
+    if len(dataframes) == 0:
+        raise ValueError("No datasets found!")
+
     log.debug("Data loaded from THREDDS successfully")
+
     return (pd.concat(dataframes), airports)
 
 
@@ -63,6 +73,10 @@ def load_from_disk(
 ) -> (pd.DataFrame, dict[int, str]):  # type: ignore
     """
     Load multiple netCDF files into a single DataFrame.
+
+    Raises:
+        ValueError: If 'ACN' not found in dataset.
+        ValueError: If no datasets are found.
     """
     dataframes = []
     airports = {}
@@ -99,5 +113,9 @@ def load_from_disk(
 
         dataframes.append(df)
 
+    if len(dataframes) == 0:
+        raise ValueError("No datasets found!")
+
     log.info("Data loaded from disk successfully")
+
     return (pd.concat(dataframes), airports)
-- 
GitLab


From bb887fbfa0881fe9403c67ea25317b03ccc1c30f Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Wed, 26 Mar 2025 14:20:28 +0100
Subject: [PATCH 2/4] add second y plot

---
 main.py       | 58 +++++++++++++++++++++++++++++++++++++++------------
 src/layout.py | 41 ++++++++++++++++++++++++++++++++++--
 2 files changed, 84 insertions(+), 15 deletions(-)

diff --git a/main.py b/main.py
index ce8527c..9c5d293 100644
--- a/main.py
+++ b/main.py
@@ -12,6 +12,7 @@ import pandas as pd
 import plotly.graph_objects as go
 from dash import Dash
 from dash.dependencies import Input, Output
+from plotly.subplots import make_subplots
 
 from src.config import DotDict, get_latest_semantic_version_tag
 from src.data import load_from_disk, load_from_thredds
@@ -43,7 +44,7 @@ data, ap_info = (
     else load_from_disk(Path("./testdata").glob("*.nc"), appconfig, airportsconfig, True)  # type: ignore
 )
 log.debug(f"Data loaded. Used THREDDS: {appconfig.USE_THREDDS}")
-log.debug(f"Data size: {data.info()}")
+# log.debug(f"Data size: {data.info()}")
 
 app.layout = create_layout(data, appconfig)
 server = app.server  # for deployment via gunicorn / WSGI server
@@ -55,13 +56,27 @@ server = app.server  # for deployment via gunicorn / WSGI server
         Output("fig-ts", "figure"),
         Output("flight-dep-dest", "children"),
         Output("var-info", "children"),
+        Output("second-var-info", "children"),
+    ],
+    [
+        Input("flight-dropdown", "value"),
+        Input("variable-dropdown", "value"),
+        Input("second-variable-dropdown", "value"),
     ],
-    [Input("flight-dropdown", "value"), Input("variable-dropdown", "value")],
 )
-def update_plots(selected_flight: int, selected_variable: str):
+def update_plots(selected_flight: int, primary_variable: str, secondary_variable: str):
     filtered_df = data[data["flight_number"] == selected_flight]
-    valid_indices = filtered_df[selected_variable].notna()
-    unit = varconfig.loc[selected_variable, :].Unit
+    valid_indices = filtered_df[primary_variable].notna()
+    unit_primary = varconfig.loc[primary_variable, :].Unit
+    lname_primary = (varconfig.loc[primary_variable, :].Long_Name,)
+    unit_secondary = (
+        varconfig.loc[secondary_variable, :].Unit if secondary_variable in varconfig.index else "-"
+    )
+    lname_secondary = (
+        varconfig.loc[secondary_variable, :].Long_Name
+        if secondary_variable in varconfig.index
+        else ""
+    )
 
     fig_map = go.Figure(
         go.Scattermap(
@@ -69,17 +84,17 @@ def update_plots(selected_flight: int, selected_variable: str):
             lat=filtered_df.loc[valid_indices, "lat"],
             mode="markers",
             marker={
-                "color": filtered_df.loc[valid_indices, selected_variable],
+                "color": filtered_df.loc[valid_indices, primary_variable],
                 "colorscale": "Rainbow",
                 "colorbar": {
                     "x": 0.995,
-                    "title": {"text": f"{selected_variable} {unit}", "side": "right"},
+                    "title": {"text": f"{primary_variable} {unit_primary}", "side": "right"},
                 },
                 "showscale": True,
             },
             connectgaps=False,
             hoverinfo="text",
-            hovertext=[f"{v:.1f}" for v in filtered_df[selected_variable]],
+            hovertext=[f"{v:.1f}" for v in filtered_df[primary_variable]],
             name="Flight Path",
         )
     )
@@ -117,11 +132,11 @@ def update_plots(selected_flight: int, selected_variable: str):
         showlegend=False,
     )
 
-    fig_ts = go.Figure()
+    fig_ts = make_subplots(specs=[[{"secondary_y": True}]])
     fig_ts.add_trace(
         go.Scatter(
             x=filtered_df.index[valid_indices & ~filtered_df["BB_flag"]],
-            y=filtered_df.loc[valid_indices & ~filtered_df["BB_flag"], selected_variable],
+            y=filtered_df.loc[valid_indices & ~filtered_df["BB_flag"], primary_variable],
             mode="markers",
             marker={
                 "size": 3,
@@ -134,7 +149,7 @@ def update_plots(selected_flight: int, selected_variable: str):
     fig_ts.add_trace(
         go.Scatter(
             x=filtered_df.index[valid_indices & filtered_df["BB_flag"]],
-            y=filtered_df.loc[valid_indices & filtered_df["BB_flag"], selected_variable],
+            y=filtered_df.loc[valid_indices & filtered_df["BB_flag"], primary_variable],
             mode="markers",
             marker={
                 "size": 3,
@@ -144,6 +159,21 @@ def update_plots(selected_flight: int, selected_variable: str):
             name="flagged 'biomass burning'",
         )
     )
+    if secondary_variable != "None":
+        fig_ts.add_trace(
+            go.Scatter(
+                x=filtered_df.index,
+                y=filtered_df[secondary_variable],
+                mode="markers",
+                marker={
+                    "size": 2,
+                    "color": "black",
+                    "showscale": False,
+                },
+                name=secondary_variable,
+            ),
+            secondary_y=True,
+        )
 
     fig_ts.update_layout(
         paper_bgcolor="#e8e8e8",
@@ -160,13 +190,15 @@ def update_plots(selected_flight: int, selected_variable: str):
     )
 
     fig_ts.update_xaxes(title="UTC")
-    fig_ts.update_yaxes(title=f"{selected_variable} {unit}")
+    fig_ts.update_yaxes(title=f"{primary_variable} {unit_primary}")
+    fig_ts.update_yaxes(title=f"{secondary_variable} {unit_secondary}", secondary_y=True)
 
     return [
         fig_map,
         fig_ts,
         ap_info[selected_flight],
-        varconfig.loc[selected_variable, :].Long_Name,
+        lname_primary,
+        lname_secondary,
     ]
 
 
diff --git a/src/layout.py b/src/layout.py
index 1e26254..8ab4fbe 100644
--- a/src/layout.py
+++ b/src/layout.py
@@ -34,6 +34,7 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
         for c in df.columns
         if c not in ["flight_number", "date", "BB_flag"]
     ]
+
     var_sel_idx = 0
     for idx, vo in enumerate(var_opts):
         if vo["label"] == "ACN":
@@ -90,7 +91,7 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
                     children=[
                         html.Label(
                             "Flight:",
-                            style={"font-weight": "bold", "margin-right": "29px"},
+                            style={"font-weight": "bold", "margin-right": "39px"},
                         ),
                         dcc.Dropdown(
                             id="flight-dropdown",
@@ -121,7 +122,7 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
                     children=[
                         html.Label(
                             "Variable:",
-                            style={"font-weight": "bold", "margin-right": "10px"},
+                            style={"font-weight": "bold", "margin-right": "20px"},
                         ),
                         dcc.Dropdown(
                             id="variable-dropdown",
@@ -147,6 +148,37 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
                         "align-items": "center",
                     },
                 ),
+                html.Div(
+                    id="second-variable-select",
+                    children=[
+                        html.Label(
+                            "Secondary:",
+                            style={"font-weight": "bold", "margin-right": "10px"},
+                        ),
+                        dcc.Dropdown(
+                            id="second-variable-dropdown",
+                            options=([{"value": "None", "label": "(None)"}] + var_opts),  # type: ignore
+                            value="None",  # type: ignore
+                            clearable=False,
+                            searchable=True,
+                            style={
+                                "display": "inline-block",
+                                "width": "190px",
+                            },
+                        ),
+                        html.Label(
+                            "",
+                            id="second-var-info",
+                            style={"margin-left": "29px", "font-size": "12px"},
+                        ),
+                    ],
+                    style={
+                        "display": "flex",
+                        "flex-direction": "row",
+                        "justify-content": "left",
+                        "align-items": "center",
+                    },
+                ),
                 dcc.Graph(
                     id="fig-map",
                     style={
@@ -165,6 +197,11 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
                         "margin-left": "10px",
                     },
                 ),
+                html.Label(
+                    "BB flagging: BB influence if ACN > (145 ppt + 3*ACN_prc)",
+                    id="flagging-info",
+                    style={"margin-left": "15px", "font-size": "12px"},
+                ),
             ]
         ),
         # --- ^^^ ---  main content
-- 
GitLab


From 4a6a4a58e3df219ba74292c0959815c29fe1d420 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Wed, 26 Mar 2025 14:20:35 +0100
Subject: [PATCH 3/4] do not fail if no data

---
 src/thredds.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/thredds.py b/src/thredds.py
index c522ef4..be9439d 100644
--- a/src/thredds.py
+++ b/src/thredds.py
@@ -15,9 +15,6 @@ def get_datasets(cfg: DotDict) -> list[Dataset]:
     )
     datasets = []
 
-    if len(catalog.datasets) == 0:
-        raise ValueError("No datasets found in the catalog")
-
     for i, ds in enumerate(catalog.datasets):
         if not (ds.startswith(cfg.FNAME_PREFIX) and ds.endswith(cfg.FNAME_SUFFIX)):
             continue
-- 
GitLab


From df879c688da4c5c0ad959d3698af75bf2559ce77 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Wed, 26 Mar 2025 14:20:39 +0100
Subject: [PATCH 4/4] update changelog

---
 CHANGELOG.md | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9430e8..66cfeaa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,8 +15,9 @@ Types of changes
 
 ## TODOs
 
+- add sources for BB tagging and ACN background
 - investigate if data caching is necessary (load-from-thredds)
-- add a secondary y axis to the timeseries plot; plot as line if sufficient data is available
+- scatter-plot: plot as line if sufficient data is available?
 
 ## Backlog
 
@@ -29,6 +30,19 @@ Types of changes
 
 ## [Unreleased]
 
+## v0.0.15 - 2025-03-26
+
+### Added
+
+- secondary variable to plot (optional)
+- info about BB flagging
+
+### Changed
+
+- app startup immediately fails if no datasets are found
+
+## v0.0.14 - 2025-03-26
+
 ### Changed
 
 - skip files that have no ACN data
-- 
GitLab