From 4a98b85aaa7e29883cbd33df0bbe3e1eb937525e Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Mon, 31 Mar 2025 16:39:50 +0200
Subject: [PATCH 01/17] next

---
 pyproject.toml | 2 +-
 uv.lock        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index a39da93..5cb11bc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "caribic-dash"
-version = "0.0.18"
+version = "0.0.19"
 description = "IRISCC dashboard with CARIBIC data"
 readme = "README.md"
 requires-python = ">=3.12"
diff --git a/uv.lock b/uv.lock
index a1b94f1..703fdae 100644
--- a/uv.lock
+++ b/uv.lock
@@ -80,7 +80,7 @@ wheels = [
 
 [[package]]
 name = "caribic-dash"
-version = "0.0.18"
+version = "0.0.19"
 source = { virtual = "." }
 dependencies = [
     { name = "dash" },
-- 
GitLab


From 83a8035588e5ae3c0190ae9cf37ac8ad697c9c3f Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Mon, 31 Mar 2025 17:09:25 +0200
Subject: [PATCH 02/17] add traj data info

---
 CHANGELOG.md  |  6 +++++-
 src/layout.py | 22 ++++++++++++++++++----
 2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f99247..a7cf880 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,7 @@ Types of changes
 
 ## TODOs
 
-- set log level via config
+- disable the "show trajectories" checkbox if no data
 - add sources for BB tagging and ACN background
 - investigate if data caching is necessary (load-from-thredds)?
 - scatter-plot: plot as line if sufficient data is available?
@@ -35,6 +35,10 @@ Types of changes
 
 ## [Unreleased]
 
+### Added
+
+- trajectory data info
+
 ## v0.0.18, 2025-03-31
 
 ### Fixed
diff --git a/src/layout.py b/src/layout.py
index b76dca3..58d9a7f 100644
--- a/src/layout.py
+++ b/src/layout.py
@@ -263,10 +263,24 @@ def create(df: pd.DataFrame, config: DotDict) -> list:
                         ),
                     ),
                 ),
-                html.Label(
-                    "BB flagging: BB influence if ACN > (145 ppt + 3*ACN_prc)",
-                    id="flagging-info",
-                    style={"margin-left": "15px", "font-size": "12px"},
+                html.Div(
+                    children=[
+                        html.Div(
+                            html.Label(
+                                "BB flagging: BB influence if ACN > (145 ppt + 3*ACN_prc)",
+                                id="flagging-info",
+                                style={"margin-left": "15px", "font-size": "12px"},
+                            )
+                        ),
+                        html.Div(
+                            html.Label(
+                                "Trajectories: KNMI Trajectory Model TRAJKS, driven by ECMWF re-analysis data",
+                                id="trajectory-info",
+                                style={"margin-left": "15px", "font-size": "12px"},
+                            )
+                        ),
+                    ],
+                    style={"padding-top": "10px"},
                 ),
             ]
         ),
-- 
GitLab


From 360dbdc4284c83b61f24324f54d4053053735cfd Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Mon, 31 Mar 2025 17:09:28 +0200
Subject: [PATCH 03/17] cleanup

---
 src/callbacks.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/callbacks.py b/src/callbacks.py
index e0779e9..0dd0b31 100644
--- a/src/callbacks.py
+++ b/src/callbacks.py
@@ -68,8 +68,6 @@ def register_map_plot(
             # try to find the trajectory file for selected flight
             if trajs := trajectory_data.get(selected_flight):
                 # make plots if trajectory file found
-                print(trajs["latitude"].dtype.byteorder)
-                print(trajs["longitude"].dtype.byteorder)
                 for i in range(trajs.trajectory.size):
                     try:
                         fig_map.add_trace(
-- 
GitLab


From c0ed357ebe0f26a58a89fcc160d6cdfba1a63ebc Mon Sep 17 00:00:00 2001
From: FObersteiner <f.obersteiner@posteo.de>
Date: Mon, 31 Mar 2025 19:48:59 +0200
Subject: [PATCH 04/17] add author

---
 pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyproject.toml b/pyproject.toml
index 5cb11bc..3d11354 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,7 @@
 name = "caribic-dash"
 version = "0.0.19"
 description = "IRISCC dashboard with CARIBIC data"
+authors = [{ name = "Florian Obersteiner", email = "f.obersteiner@kit.edu" }]
 readme = "README.md"
 requires-python = ">=3.12"
 classifiers = [
-- 
GitLab


From 810330c83b015f2508a01a586aada16087185633 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 08:34:55 +0200
Subject: [PATCH 05/17] show static pressure as secondary var by default

---
 CHANGELOG.md  | 7 +++++++
 src/layout.py | 6 ++++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7cf880..76f66d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,9 +16,11 @@ Types of changes
 ## TODOs
 
 - disable the "show trajectories" checkbox if no data
+- put trajectory info and checkbox below map plot?
 - add sources for BB tagging and ACN background
 - investigate if data caching is necessary (load-from-thredds)?
 - scatter-plot: plot as line if sufficient data is available?
+- prevent footer from hiding BB flagging info
 
 ## v0.1.0 release
 
@@ -26,6 +28,7 @@ Types of changes
 
 ## Backlog
 
+- containerize & auto-deploy
 - use a polars dataframe or [fireducks](https://fireducks-dev.github.io/) for better performance - if that turns out to be a bottleneck. (!) Keep this in mind when using pandas-specific functions.
 - add tests
 - add pre-commit
@@ -39,6 +42,10 @@ Types of changes
 
 - trajectory data info
 
+### Changed
+
+- select a secondary parameter by default
+
 ## v0.0.18, 2025-03-31
 
 ### Fixed
diff --git a/src/layout.py b/src/layout.py
index 58d9a7f..71da70c 100644
--- a/src/layout.py
+++ b/src/layout.py
@@ -9,7 +9,7 @@ from dash import dcc, html
 from .config import DotDict
 
 
-def create(df: pd.DataFrame, config: DotDict) -> list:
+def create(df: pd.DataFrame, config: DotDict) -> list[html.Header]:
     """
     Create the layout for the dashboard.
 
@@ -40,6 +40,8 @@ def create(df: pd.DataFrame, config: DotDict) -> list:
         if vo["label"] == "ACN":
             var_sel_idx = idx
 
+    var2_sel_idx = 3
+
     return [
         html.Header(
             [
@@ -171,7 +173,7 @@ def create(df: pd.DataFrame, config: DotDict) -> list:
                                 dcc.Dropdown(
                                     id="second-variable-dropdown",
                                     options=([{"value": "None", "label": "(None)"}] + var_opts),  # type: ignore
-                                    value="None",  # type: ignore
+                                    value=var_opts[var2_sel_idx]["value"],  # type: ignore
                                     clearable=False,
                                     searchable=True,
                                     style={
-- 
GitLab


From a80327037e71730bd10926ec0e41526b41717bb7 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 10:02:46 +0200
Subject: [PATCH 06/17] cosmetics

---
 CHANGELOG.md     | 3 ++-
 src/callbacks.py | 3 ++-
 src/layout.py    | 2 +-
 uv.lock          | 6 +++---
 4 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 76f66d9..dda235e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,7 +20,7 @@ Types of changes
 - add sources for BB tagging and ACN background
 - investigate if data caching is necessary (load-from-thredds)?
 - scatter-plot: plot as line if sufficient data is available?
-- prevent footer from hiding BB flagging info
+- prevent footer from hiding BB flagging info?
 
 ## v0.1.0 release
 
@@ -45,6 +45,7 @@ Types of changes
 ### Changed
 
 - select a secondary parameter by default
+- same bg color for both plots
 
 ## v0.0.18, 2025-03-31
 
diff --git a/src/callbacks.py b/src/callbacks.py
index 0dd0b31..69024eb 100644
--- a/src/callbacks.py
+++ b/src/callbacks.py
@@ -143,6 +143,7 @@ def register_map_plot(
             },
             margin={"l": 0, "r": 10, "t": 0, "b": 0},
             showlegend=False,
+            paper_bgcolor="#fbf8f3",
         )
 
         return [fig_map, show_trajdata_warn]
@@ -218,7 +219,7 @@ def register_timeseries_plot(app: Dash, timeseries_data: pd.DataFrame, var_info:
             )
 
         fig_ts.update_layout(
-            paper_bgcolor="#e8e8e8",
+            paper_bgcolor="#fbf8f3",
             margin={"l": 45, "r": 10, "t": 20, "b": 50},
             legend={
                 "x": 0.01,
diff --git a/src/layout.py b/src/layout.py
index 71da70c..b8a711d 100644
--- a/src/layout.py
+++ b/src/layout.py
@@ -320,7 +320,7 @@ def create(df: pd.DataFrame, config: DotDict) -> list[html.Header]:
                         html.A(
                             [
                                 html.I(className="fab fa-gitlab", style={"marginRight": "5px"}),
-                                f"src ({config.VERSION})",
+                                f"src {config.VERSION}",
                             ],
                             href=config.CODEURL,
                             target="_blank",
diff --git a/uv.lock b/uv.lock
index 703fdae..aebf8af 100644
--- a/uv.lock
+++ b/uv.lock
@@ -392,11 +392,11 @@ wheels = [
 
 [[package]]
 name = "narwhals"
-version = "1.32.0"
+version = "1.33.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c1/e5/aa97891440bf6bc4239e9b918d89fea58eb87dff1d8d14a69b45ef677e66/narwhals-1.32.0.tar.gz", hash = "sha256:bd0aa41434737adb4b26f8593f3559abc7d938730ece010fe727b58bc363580d", size = 258915 }
+sdist = { url = "https://files.pythonhosted.org/packages/85/fd/484aa8bb557f97a1781f38c78b79f795a2fa320e4165c4230f679937d1e8/narwhals-1.33.0.tar.gz", hash = "sha256:6233d2457debf4b5fe4a1da54530c6fe2d84326f4a8e3bca35bbbff580a347cb", size = 262554 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/e9/96/79a6168dc7a5098066e097c01a45d01608c8df6552dfb92a2676ce623186/narwhals-1.32.0-py3-none-any.whl", hash = "sha256:8bdbf3f76155887412eea04b0b06303856ac1aa3d9e8bda5b5e54612855fa560", size = 320073 },
+    { url = "https://files.pythonhosted.org/packages/41/c1/e9bc6b67c774e7c1f939c91ea535f18f7644fedc61b20d6baa861ad52b34/narwhals-1.33.0-py3-none-any.whl", hash = "sha256:f653319112fd121a1f1c18a40cf70dada773cdacfd53e62c2aa0afae43c17129", size = 322750 },
 ]
 
 [[package]]
-- 
GitLab


From 7ac9447f9667f3390479691a02a3b49cc89e1509 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 12:05:05 +0200
Subject: [PATCH 07/17] show map source attribution on top of map

---
 assets/custom.css | 190 +++++++++++++++++++++++++++-------------------
 1 file changed, 113 insertions(+), 77 deletions(-)

diff --git a/assets/custom.css b/assets/custom.css
index 5d16dcf..bb16e74 100644
--- a/assets/custom.css
+++ b/assets/custom.css
@@ -1,124 +1,160 @@
 :root {
-    --neon-pink: #ff71ce;
-    --neon-blue: #01cdfe;
-    --neon-green: #05ff81;
-    --neon-purple: #b967ff;
-    --neon-yellow: #fdfb96;
-    --grey-bg: #404040;
-    --grey-bg-bright: #d4d4d4;
-    --dark-bg: #1a1a1a;
-    --darker-bg: #121212;
-    --grid-color: rgba(33, 33, 99, 0.8);
+  --neon-pink: #ff71ce;
+  --neon-blue: #01cdfe;
+  --neon-green: #05ff81;
+  --neon-purple: #b967ff;
+  --neon-yellow: #fdfb96;
+  --grey-bg: #404040;
+  --grey-bg-bright: #d4d4d4;
+  --dark-bg: #1a1a1a;
+  --darker-bg: #121212;
+  --grid-color: rgba(33, 33, 99, 0.8);
 }
 
 body {
-    background-color: var(--dark-bg);
-    background-image:
-        linear-gradient(0deg, var(--grid-color) 1px, transparent 1px),
-        linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
-    background-size: 20px 20px;
-    color: white;
-    font-family: 'VT323', 'Courier New', monospace;
-    margin-top: 70px;
-    padding: 15px;
+  background-color: var(--dark-bg);
+  background-image: linear-gradient(
+      0deg,
+      var(--grid-color) 1px,
+      transparent 1px
+    ),
+    linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
+  background-size: 20px 20px;
+  color: white;
+  font-family: "VT323", "Courier New", monospace;
+  margin-top: 70px;
+  padding: 15px;
 }
 
 .title-bar {
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-    background-color: var(--grey-bg);
-    padding: 10px 20px;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    border-bottom: 1px solid var(--grid-color);
-    z-index: 1000;
-    box-sizing: border-box;
-    height: 60px;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  background-color: var(--grey-bg);
+  padding: 10px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid var(--grid-color);
+  z-index: 1000;
+  box-sizing: border-box;
+  height: 60px;
 }
 
 .title-text {
-    color: white;
-    font-size: 1.4rem;
-    font-weight: bold;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    max-width: 70%;
+  color: white;
+  font-size: 1.4rem;
+  font-weight: bold;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 70%;
 }
 
 .logo-container {
-    display: flex;
-    align-items: center;
+  display: flex;
+  align-items: center;
 }
 
 .header-logo {
-    height: 40px;
-    background-color: var(--grey-bg-bright);
-    border-radius: 4px;
+  height: 40px;
+  background-color: var(--grey-bg-bright);
+  border-radius: 4px;
 }
 
 /* Logo wrapper to create the background box */
 .logo-wrapper {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    background-color: var(--grey-bg-bright);
-    border-radius: 0.4rem;
-    padding: 0.3rem 0.7rem 0.3rem 0.7rem;
-    margin-left: 0.5rem;
-    border: 1px solid var(--grid-color);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: var(--grey-bg-bright);
+  border-radius: 0.4rem;
+  padding: 0.3rem 0.7rem 0.3rem 0.7rem;
+  margin-left: 0.5rem;
+  border: 1px solid var(--grid-color);
 }
 
 /* Responsive adjustments */
 @media (max-width: 600px) {
-    .logo-wrapper {
-        padding: 0.3rem;
-    }
+  .logo-wrapper {
+    padding: 0.3rem;
+  }
 
-    .header-logo {
-        height: 2rem;
-    }
+  .header-logo {
+    height: 2rem;
+  }
 }
 
 /* ----- drop boxes ----- */
 .Select-control {
-    background-color: var(--dark-bg);
+  background-color: var(--dark-bg);
 }
 
 .Select-control:hover {
-    background-color: var(--grey-bg);
+  background-color: var(--grey-bg);
 }
 
 .Select-value-label {
-    color: white !important;
+  color: white !important;
 }
 
 .Select-menu-outer {
-    background-color: var(--dark-bg);
-    color: white;
+  background-color: var(--dark-bg);
+  color: white;
 }
 
+/* The following styles ensure the attribution tag floats above the map and should be removed if the issue is fixed in a future plotly release. */
+.maplibregl-ctrl-bottom-right {
+  bottom: 5px;
+  left: 10px;
+}
+
+.maplibregl-ctrl-bottom-left,
+.maplibregl-ctrl-bottom-right,
+.maplibregl-ctrl-top-left,
+.maplibregl-ctrl-top-right {
+  pointer-events: none;
+  position: absolute;
+  z-index: 2;
+}
+
+.maplibregl-map {
+  font:
+    12px/20px Helvetica Neue,
+    Arial,
+    Helvetica,
+    sans-serif;
+  font-family:
+    Helvetica Neue,
+    Arial,
+    Helvetica,
+    sans-serif;
+  color: var(--grey-bg);
+}
+
+/*This is necessary to remove spacing below the canvas.*/
+.maplibregl-canvas {
+  display: block;
+}
 
 .footer {
-    position: fixed;
-    bottom: 0;
-    left: 0;
-    width: 100%;
-    background-color: var(--dark-bg);
-    padding: 10px 20px;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    border-top: 1px solid var(--grid-color);
-    z-index: 1000;
-    box-sizing: border-box;
-    font-size: 0.9rem;
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  background-color: var(--dark-bg);
+  padding: 10px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-top: 1px solid var(--grid-color);
+  z-index: 1000;
+  box-sizing: border-box;
+  font-size: 0.9rem;
 }
 
 /* Add some bottom margin to the main content to prevent overlap with footer */
 .main-content {
-    margin-bottom: 20px;
+  margin-bottom: 20px;
 }
-- 
GitLab


From 4214f4fa569b501c9ab3b32e4516b7c1732c1d2b Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 13:39:47 +0200
Subject: [PATCH 08/17] add ci script

---
 .gitlab-ci.yml | 17 +++++++++++++++++
 CHANGELOG.md   |  1 +
 2 files changed, 18 insertions(+)
 create mode 100644 .gitlab-ci.yml

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..6f47bd3
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,17 @@
+stages:
+  - test
+
+# Only run the pipeline for tag pushes matching the pattern "v*"
+workflow:
+  rules:
+    - if: $CI_COMMIT_TAG =~ /^v.*-pre/
+
+# Run tests for your Python application
+test:
+  stage: test
+  image: python:3.9
+  script:
+    - pip install -r requirements.txt
+    - pytest -v
+  only:
+    - tags
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dda235e..0596cb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,6 +46,7 @@ Types of changes
 
 - select a secondary parameter by default
 - same bg color for both plots
+- show map attribution on top of map instead of hiding it below timeseries plot
 
 ## v0.0.18, 2025-03-31
 
-- 
GitLab


From 2b5abf8866450f4bbbc63f0f22b2c0067c778a94 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 13:49:29 +0200
Subject: [PATCH 09/17] uv ?

---
 .gitlab-ci.yml | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6f47bd3..9227d2d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,14 @@
 stages:
   - test
 
+variables:
+  UV_VERSION: 0.6
+  PYTHON_VERSION: 3.12
+  BASE_LAYER: bookworm-slim
+  # GitLab CI creates a separate mountpoint for the build directory,
+  # so we need to copy instead of using hard links.
+  UV_LINK_MODE: copy
+
 # Only run the pipeline for tag pushes matching the pattern "v*"
 workflow:
   rules:
@@ -9,9 +17,10 @@ workflow:
 # Run tests for your Python application
 test:
   stage: test
-  image: python:3.9
+  # image: python:3.9
+  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
   script:
-    - pip install -r requirements.txt
-    - pytest -v
+    - uv pip install -r requirements.txt
+    - uv run pytest . -v
   only:
     - tags
-- 
GitLab


From db9e9cd12a298d6775b0f04590b81c1cc8eb3c28 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 13:52:25 +0200
Subject: [PATCH 10/17] update requirements.txt

---
 requirements.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index 3944ec7..ae8e7bd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -36,7 +36,7 @@ markupsafe==3.0.2
     # via
     #   jinja2
     #   werkzeug
-narwhals==1.32.0
+narwhals==1.33.0
     # via plotly
 nest-asyncio==1.6.0
     # via dash
@@ -63,7 +63,7 @@ plotly==6.0.1
     # via
     #   caribic-dash (pyproject.toml)
     #   dash
-protobuf==6.30.1
+protobuf==6.30.2
     # via siphon
 python-dateutil==2.9.0.post0
     # via pandas
@@ -99,7 +99,7 @@ werkzeug==3.0.6
     # via
     #   dash
     #   flask
-xarray==2025.3.0
+xarray==2025.3.1
     # via caribic-dash (pyproject.toml)
 zipp==3.21.0
     # via importlib-metadata
-- 
GitLab


From ee56d3b3553ce90c4267f0770e7a004c103fc70f Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 13:55:52 +0200
Subject: [PATCH 11/17] py 3.12

---
 .gitlab-ci.yml | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9227d2d..76fd1c6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -17,10 +17,14 @@ workflow:
 # Run tests for your Python application
 test:
   stage: test
-  # image: python:3.9
-  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
+  image: python:3.12
   script:
-    - uv pip install -r requirements.txt
-    - uv run pytest . -v
+    - pip install pip --upgrade
+    - pip install -r requirements.txt
+    - pytest . -v
+  # image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
+  # script:
+  #   - uv pip install -r requirements.txt
+  #   - uv run pytest . -v
   only:
     - tags
-- 
GitLab


From 0b87c098d44e9189c4106bbc8f11b99590c8c74e Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 14:22:20 +0200
Subject: [PATCH 12/17] update layout

---
 src/layout.py | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/src/layout.py b/src/layout.py
index b8a711d..7141063 100644
--- a/src/layout.py
+++ b/src/layout.py
@@ -249,6 +249,18 @@ def create(df: pd.DataFrame, config: DotDict) -> list[html.Header]:
                         )
                     ),
                 ),
+                html.Div(
+                    children=[
+                        html.Div(
+                            html.Label(
+                                "Trajectories: KNMI Trajectory Model TRAJKS, driven by ECMWF re-analysis data",
+                                id="trajectory-info",
+                                style={"margin-left": "15px", "font-size": "12px"},
+                            )
+                        ),
+                    ],
+                    style={"padding-top": "5px", "padding-bottom": "10px"},
+                ),
                 dcc.Loading(
                     id="loading-ts",
                     type="circle",  # Options: "graph", "cube", "circle", "dot", or "default"
@@ -274,15 +286,8 @@ def create(df: pd.DataFrame, config: DotDict) -> list[html.Header]:
                                 style={"margin-left": "15px", "font-size": "12px"},
                             )
                         ),
-                        html.Div(
-                            html.Label(
-                                "Trajectories: KNMI Trajectory Model TRAJKS, driven by ECMWF re-analysis data",
-                                id="trajectory-info",
-                                style={"margin-left": "15px", "font-size": "12px"},
-                            )
-                        ),
                     ],
-                    style={"padding-top": "10px"},
+                    style={"padding-top": "5px", "padding-bottom": "10px"},
                 ),
             ]
         ),
-- 
GitLab


From cac6c2379d4d62c22da6781deb4bd8ff8bc3c928 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 14:24:53 +0200
Subject: [PATCH 13/17] uv retry

---
 .gitlab-ci.yml | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 76fd1c6..b5d3687 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -17,14 +17,14 @@ workflow:
 # Run tests for your Python application
 test:
   stage: test
-  image: python:3.12
-  script:
-    - pip install pip --upgrade
-    - pip install -r requirements.txt
-    - pytest . -v
-  # image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
+  # image: python:3.12
   # script:
-  #   - uv pip install -r requirements.txt
-  #   - uv run pytest . -v
+  #   - pip install pip --upgrade
+  #   - pip install -r requirements.txt
+  #   - pytest . -v
+  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
+  script:
+    - uv pip install -r requirements.txt
+    - uv run pytest . -v
   only:
     - tags
-- 
GitLab


From a969d64df973c2f13eb031789744d4fef015242b Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 14:27:20 +0200
Subject: [PATCH 14/17] uv sync

---
 .gitlab-ci.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b5d3687..e4b973d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,7 +24,8 @@ test:
   #   - pytest . -v
   image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
   script:
-    - uv pip install -r requirements.txt
+    - uv sync
     - uv run pytest . -v
+    - uv cache prune --ci
   only:
     - tags
-- 
GitLab


From 7ad6b5ae3b029fca22348c1d1c7507002b011c9a Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 14:38:08 +0200
Subject: [PATCH 15/17] prepare deploy

---
 .gitlab-ci.yml | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e4b973d..1287b3c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,6 @@
 stages:
   - test
+  # - build_and_deploy
 
 variables:
   UV_VERSION: 0.6
@@ -9,19 +10,19 @@ variables:
   # so we need to copy instead of using hard links.
   UV_LINK_MODE: copy
 
-# Only run the pipeline for tag pushes matching the pattern "v*"
+# Only run the pipeline for tag pushes matching the pattern "v*-pre"
 workflow:
   rules:
     - if: $CI_COMMIT_TAG =~ /^v.*-pre/
 
-# Run tests for your Python application
+# Run tests...
 test:
   stage: test
   # image: python:3.12
   # script:
   #   - pip install pip --upgrade
   #   - pip install -r requirements.txt
-  #   - pytest . -v
+  #   - python -m pytest . -v
   image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
   script:
     - uv sync
@@ -29,3 +30,26 @@ test:
     - uv cache prune --ci
   only:
     - tags
+#
+#
+# # Build and deploy the Docker container directly on the server
+# build_and_deploy:
+#   stage: build_and_deploy
+#   script:
+#     - echo "Starting build and deployment of $APP_NAME version $CI_COMMIT_TAG"
+#     # Build the Docker image locally
+#     - docker build -t $APP_NAME:$CI_COMMIT_TAG .
+#     # Stop and remove existing container if it exists
+#     - |
+#       if docker ps -a | grep -q $APP_NAME; then
+#         docker stop $APP_NAME
+#         docker rm $APP_NAME
+#       fi
+#     # Start the new container
+#     - docker run -d --name $APP_NAME -p $HOST_PORT:$CONTAINER_PORT --restart unless-stopped $APP_NAME:$CI_COMMIT_TAG
+#     - echo "Build and deployment complete!"
+#   only:
+#     - tags
+#   # This ensures the job runs on the specific runner on your server
+#   tags:
+#     - deployment-server
-- 
GitLab


From cfce3ce0282d3513586b2ace242c8b5e2093ff29 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 14:55:44 +0200
Subject: [PATCH 16/17] traj checkbox below map plot

---
 CHANGELOG.md  |  3 +--
 src/layout.py | 48 +++++++++++++++++++++++++++---------------------
 2 files changed, 28 insertions(+), 23 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0596cb7..f3da1d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,11 +16,9 @@ Types of changes
 ## TODOs
 
 - disable the "show trajectories" checkbox if no data
-- put trajectory info and checkbox below map plot?
 - add sources for BB tagging and ACN background
 - investigate if data caching is necessary (load-from-thredds)?
 - scatter-plot: plot as line if sufficient data is available?
-- prevent footer from hiding BB flagging info?
 
 ## v0.1.0 release
 
@@ -47,6 +45,7 @@ Types of changes
 - select a secondary parameter by default
 - same bg color for both plots
 - show map attribution on top of map instead of hiding it below timeseries plot
+- put 'show trajectories' button below map plot
 
 ## v0.0.18, 2025-03-31
 
diff --git a/src/layout.py b/src/layout.py
index 7141063..ffa7f03 100644
--- a/src/layout.py
+++ b/src/layout.py
@@ -202,26 +202,6 @@ def create(df: pd.DataFrame, config: DotDict) -> list[html.Header]:
                                 "vertical-align": "middle",
                             },
                         ),
-                        html.Div(
-                            [
-                                html.Label(
-                                    "Show trajectories",
-                                    style={"margin-right": "10px", "vertical-align": "middle"},
-                                ),
-                                dcc.Checklist(
-                                    id="trajectories-checkbox",
-                                    options=[{"label": "", "value": 1}],
-                                    inline=True,
-                                    style={"display": "inline-block", "vertical-align": "middle"},
-                                ),
-                            ],
-                            style={
-                                "width": "25%",
-                                "display": "inline-block",
-                                "text-align": "right",
-                                "vertical-align": "middle",
-                            },
-                        ),
                     ],
                     style={
                         "display": "flex",
@@ -258,8 +238,34 @@ def create(df: pd.DataFrame, config: DotDict) -> list[html.Header]:
                                 style={"margin-left": "15px", "font-size": "12px"},
                             )
                         ),
+                        html.Div(
+                            [
+                                html.Label(
+                                    "Show trajectories",
+                                    style={"margin-right": "10px"},
+                                ),
+                                dcc.Checklist(
+                                    id="trajectories-checkbox",
+                                    options=[{"label": "", "value": 1}],
+                                    inline=True,
+                                    style={"display": "inline-block", "vertical-align": "right"},
+                                ),
+                            ],
+                            style={
+                                "text-align": "right",
+                                "vertical-align": "right",
+                            },
+                        ),
                     ],
-                    style={"padding-top": "5px", "padding-bottom": "10px"},
+                    style={
+                        "display": "flex",
+                        "justify-content": "space-between",
+                        "align-items": "center",
+                        "padding-top": "5px",
+                        "padding-bottom": "10px",
+                        "padding-left": "5px",
+                        "padding-right": "15px",
+                    },
                 ),
                 dcc.Loading(
                     id="loading-ts",
-- 
GitLab


From db37ce751f205d5706709003effe1cb240b17320 Mon Sep 17 00:00:00 2001
From: FObersteiner <florian.obersteiner@kit.edu>
Date: Tue, 1 Apr 2025 16:51:02 +0200
Subject: [PATCH 17/17] add containerconfig

---
 .dockerignore | 16 ++++++++++++++++
 CHANGELOG.md  |  1 +
 Dockerfile    | 42 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 59 insertions(+)
 create mode 100644 .dockerignore
 create mode 100644 Dockerfile

diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..ab996e1
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+Dockerfile
+.dockerignore
+.git
+.gitignore
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+venv/
+.venv/
+env/
+*.env
+*.db
+*.sqlite3
+instance/
+experiments/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3da1d3..03a3804 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@ Types of changes
 ### Added
 
 - trajectory data info
+- dockerfile and dockerignore
 
 ### Changed
 
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..84a6d16
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,42 @@
+# ###
+# generated with Gemini 2.5 pro/experimental
+# ###
+#
+# Use an official Python runtime as a parent image
+# Choose a specific version for reproducibility, -slim versions are smaller
+FROM python:3.12-slim
+
+# Set environment variables
+# Prevents Python from writing pyc files to disc (optional)
+ENV PYTHONDONTWRITEBYTECODE=1
+# Ensures Python output is sent straight to terminal (useful for logs)
+ENV PYTHONUNBUFFERED=1
+
+# Set the working directory in the container
+WORKDIR /app
+
+# Copy the requirements file into the container at /app
+COPY requirements.txt .
+
+# Install any needed packages specified in requirements.txt
+# --no-cache-dir reduces image size
+# --upgrade pip ensures you have the latest pip
+# RUN pip install --no-cache-dir --upgrade pip && \
+#     pip install --no-cache-dir -r requirements.txt
+
+# uv variant:
+RUN pip install --no-cache-dir uv
+RUN uv pip install --no-cache -r requirements.txt --system
+
+# Copy the rest of the application code into the container at /app
+# Assumes your Dockerfile is in the root of your project directory
+COPY . .
+
+# Make port 19000 available to the world outside this container
+EXPOSE 19000
+
+# Define the command to run your app using Gunicorn
+# Binds Gunicorn to 0.0.0.0 so it's accessible from outside the container
+# 'app:server' assumes your Dash app instance is 'app' in 'app.py'
+# Gunicorn needs the underlying Flask server instance, typically 'app.server'
+CMD ["gunicorn", "-b", ":19000", "main:server", "-w", "2"]
-- 
GitLab