From 7a7f3116a0e70fdab9be1f764e5d85d9aaad5df3 Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Wed, 10 Sep 2025 14:35:26 +1000
Subject: [PATCH 01/23] refactor(layout): move tabs links to collapsible
sidebar using dash-mantine-components - updated dash and
dash-mantine-components - moved the navigation and document, global, and IP
to the sidebar - modified the spec.cy.js - updated the tabs.css and
layout.css
---
Pipfile | 4 +-
Pipfile.lock | 61 ++---
assets/layout.css | 11 +
assets/tabs.css | 130 +++++++++++
main.py | 18 +-
pages/explorer.py | 103 ++++-----
pages/lib/global_element_ids.py | 19 ++
pages/lib/layout.py | 354 +++++++++++++++++++-----------
pages/lib/utils.py | 9 +-
pages/natural_ventilation.py | 37 ++--
pages/not_found_404.py | 2 +-
pages/outdoor.py | 33 +--
pages/psy-chart.py | 33 +--
pages/select.py | 27 ++-
pages/summary.py | 17 +-
pages/sun.py | 29 +--
pages/t_rh.py | 23 +-
pages/wind.py | 85 +++----
requirements.txt | 4 +-
tests/node/cypress/e2e/spec.cy.js | 34 ++-
20 files changed, 654 insertions(+), 379 deletions(-)
diff --git a/Pipfile b/Pipfile
index 91288833..0c01f3de 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,12 +4,12 @@ verify_ssl = true
name = "pypi"
[packages]
-dash = "==2.15"
+dash = "==3.2"
pvlib = "==0.9.1"
pythermalcomfort = "==2.9.1"
dash-bootstrap-components = "==1.2.0"
dash-extensions = "==1.0.7"
-dash-mantine-components = "==0.12.1"
+dash-mantine-components = "==2.2.1"
requests = "==2.32.4"
plotly = "==5.18.0"
pandas = "==2.2.0"
diff --git a/Pipfile.lock b/Pipfile.lock
index 15c208f9..8e475b6d 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "7214a1158f64483648ecff36a57b61b67020408e0f16770b77d7165a9d6f47e0"
+ "sha256": "4f09fe1d5fd82b15503fd3b0c1606e5e6139df983488fdc78c72555daf01e167"
},
"pipfile-spec": 6,
"requires": {
@@ -133,22 +133,14 @@
"markers": "python_version >= '3.10'",
"version": "==8.2.1"
},
- "colorama": {
- "hashes": [
- "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
- "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
- "version": "==0.4.6"
- },
"dash": {
"hashes": [
- "sha256:d38891337fc855d5673f75e5346354daa063c4ff45a8a6a21f25e858fcae41c2",
- "sha256:df1882bbf613e4ca4372281c8facbeb68e97d76720336b051bf84c75d2de8588"
+ "sha256:4c1819588d83bed2cbcf5807daa5c2380c8c85789a6935a733f018f04ad8a6a2",
+ "sha256:93300b9b99498f8b8ed267e61c455b4ee1282c7e4d4b518600eec87ce6ddea55"
],
"index": "pypi",
- "markers": "python_version >= '3.6'",
- "version": "==2.15.0"
+ "markers": "python_version >= '3.8'",
+ "version": "==3.2.0"
},
"dash-bootstrap-components": {
"hashes": [
@@ -159,13 +151,6 @@
"markers": "python_version >= '3.6' and python_version < '4'",
"version": "==1.2.0"
},
- "dash-core-components": {
- "hashes": [
- "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346",
- "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"
- ],
- "version": "==2.0.0"
- },
"dash-extensions": {
"hashes": [
"sha256:46c4ec7c7d3b42db63032005f7d258435bc0092d0fec6f6ce000be68befeb102",
@@ -175,13 +160,6 @@
"markers": "python_version >= '3.8' and python_version < '4'",
"version": "==1.0.7"
},
- "dash-html-components": {
- "hashes": [
- "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50",
- "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"
- ],
- "version": "==2.0.0"
- },
"dash-iconify": {
"hashes": [
"sha256:564774be6b11b0ac3a8999b7137c3d17a1d351d69b673aa313c7228eacc9d143",
@@ -192,18 +170,11 @@
},
"dash-mantine-components": {
"hashes": [
- "sha256:2630bca31cb96d96fb2c4f986e639b9f92d6319aba8cba02f76da6c0d8f5ca48",
- "sha256:c3dcbfd89813a1539654b8d016eb953dc5f67aafe1a77d45b5ec9faa6f25d3e7"
+ "sha256:a54acdb8b3e7a80251e9d68947a84b0a1d16285f5ca33363dbc29f287a13ebaa",
+ "sha256:cd3aefd3191be365db0435067196fe037e3803c996794c174f0f0409c24efac4"
],
"index": "pypi",
- "version": "==0.12.1"
- },
- "dash-table": {
- "hashes": [
- "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308",
- "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"
- ],
- "version": "==5.0.0"
+ "version": "==2.2.1"
},
"dataclass-wizard": {
"hashes": [
@@ -635,7 +606,7 @@
"sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274",
"sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.17.0"
},
"tenacity": {
@@ -648,11 +619,11 @@
},
"typing-extensions": {
"hashes": [
- "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36",
- "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"
+ "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
+ "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"
],
"markers": "python_version >= '3.9'",
- "version": "==4.14.1"
+ "version": "==4.15.0"
},
"tzdata": {
"hashes": [
@@ -672,11 +643,11 @@
},
"werkzeug": {
"hashes": [
- "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17",
- "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"
+ "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e",
+ "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"
],
- "markers": "python_version >= '3.8'",
- "version": "==3.0.6"
+ "markers": "python_version >= '3.9'",
+ "version": "==3.1.3"
},
"zipp": {
"hashes": [
diff --git a/assets/layout.css b/assets/layout.css
index e9089dbc..611c3a11 100644
--- a/assets/layout.css
+++ b/assets/layout.css
@@ -29,4 +29,15 @@
width: 100%; //or any percentage width you want
}
+/* begin displaying banner below the banner */
+.custom-sidebar .mantine-Drawer-content {
+ top: 80px !important;
+ height: calc(100vh - 80px) !important;
+ position: fixed !important;
+}
+
+.custom-sidebar .mantine-Drawer-overlay {
+ top: 80px !important;
+ height: calc(100vh - 80px) !important;
+}
diff --git a/assets/tabs.css b/assets/tabs.css
index 15590dff..7ba0edbc 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -285,4 +285,134 @@ p {
border-radius: 0.25rem;
border: 0.5px solid lightgrey;
z-index: 1000;
+}
+
+/* side bar */
+#sidebar {
+ background-color: #f8f9fa;
+ border-right: 1px solid #e9ecef;
+}
+
+/* 强制SegmentedControl适应容器宽度 */
+#sidebar .mantine-SegmentedControl-root {
+ width: 100% !important;
+}
+
+#sidebar .mantine-SegmentedControl-control {
+ flex: 1 !important;
+ min-width: 0 !important;
+}
+
+/* burger button */
+#burger-button {
+ transition: all 0.2s ease;
+}
+
+#burger-button:hover {
+ transform: scale(1.05);
+}
+
+.nav-link {
+ border-radius: 6px;
+ transition: all 0.2s ease;
+}
+
+.nav-link:hover {
+ background-color: #e3f2fd;
+}
+
+/* Active navigation link styles */
+.nav-link[data-active="true"] {
+ background-color: #1976d2 !important;
+ color: white !important;
+ font-weight: 600;
+}
+
+.nav-link[data-active="true"]:hover {
+ background-color: #1565c0 !important;
+ color: white !important;
+}
+
+/* response design */
+@media (max-width: 768px) {
+ #sidebar {
+ width: 280px !important;
+ }
+}
+
+/* burger button style */
+#burger-button {
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+}
+
+/* Alert style */
+.survey-alert {
+ position: fixed;
+ top: 25px;
+ right: 10px;
+ width: 400px;
+}
+
+/* Footer style */
+#footer-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: nowrap;
+ width: 100%;
+ min-height: auto;
+ padding: 10px 0;
+}
+
+/* Footer Logo style */
+.footer-logo-section {
+ flex: 0 0 33.333333%;
+ max-width: 33.333333%;
+ padding: 10px 15px 10px 25px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+/* Footer style */
+.footer-content-section {
+ flex: 0 0 66.666667%;
+ max-width: 66.666667%;
+ padding: 10px 15px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+/* Footer text style */
+.footer-text-content {
+ margin-top: 1rem;
+}
+
+/* Footer Markdown style */
+.footer-markdown-text {
+ font-size: 16px;
+ line-height: 1.5;
+ font-weight: 500;
+}
+
+/* Footer link style */
+.footer-links-group {
+ margin-top: 1rem;
+}
+
+.footer-link {
+ text-decoration: underline;
+ font-size: 16px;
+ font-weight: 500;
+}
+
+/* Banner style */
+#banner-title {
+ line-height: 1.1;
+}
+
+/* Documentation style */
+#nav-doc-link {
+ margin-top: 5px;
}
\ No newline at end of file
diff --git a/main.py b/main.py
index e5c23662..6fa4b598 100644
--- a/main.py
+++ b/main.py
@@ -1,22 +1,26 @@
import dash_bootstrap_components as dbc
from dash import html, dcc
from dash_extensions.enrich import Output, Input, callback
+import dash_mantine_components as dmc
from app import app
-from pages.lib.layout import banner, footer, build_tabs
+from pages.lib.layout import banner, footer, build_tabs, burger_button, sidebar
from config import AppConfig
from pages.lib.global_element_ids import ElementIds
server = app.server
app.title = AppConfig.TITLE
-app.layout = dbc.Container(
- fluid=True,
- style={"padding": "0"},
+app.layout = dmc.MantineProvider(
+ theme={
+ "colorScheme": "light",
+ "primaryColor": "blue"
+ },
children=[
- dcc.Location(id="url", refresh=False), # connected to callback below
+ dcc.Location(id=ElementIds.MAIN_URL, refresh=False),
+ sidebar(),
banner(),
- html.Div(id="page-content", children=build_tabs()),
+ dmc.Box(id=ElementIds.PAGE_CONTENT, children=build_tabs()),
footer(),
],
)
@@ -32,7 +36,7 @@ def display_alert(n):
if __name__ == "__main__":
- app.run_server(
+ app.run(
debug=AppConfig.DEBUG,
host=AppConfig.HOST,
port=AppConfig.PORT,
diff --git a/pages/explorer.py b/pages/explorer.py
index dde7df5c..8c27421d 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -1,5 +1,6 @@
import dash
from dash import dcc, html
+import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
from dash.exceptions import PreventUpdate
@@ -64,7 +65,7 @@
def section_one_inputs():
"""Return the inputs from section one."""
- return html.Div(
+ return dmc.Box(
className="container-row full-width row-center",
children=[
html.H4(className="text-next-to-input", children=["Select a variable: "]),
@@ -79,11 +80,11 @@ def section_one_inputs():
def section_one():
"""Return the graphs for section one"""
- return html.Div(
+ return dmc.Box(
className="container-col full-width",
children=[
section_one_inputs(),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Yearly chart",
id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
@@ -92,9 +93,9 @@ def section_one():
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.YEARLY_EXPLORE, className="full-width"),
+ children=dmc.Box(id=ElementIds.YEARLY_EXPLORE, className="full-width"),
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Daily chart",
id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
@@ -102,10 +103,10 @@ def section_one():
),
),
dcc.Loading(
- html.Div(className="full-width", id=ElementIds.QUERY_DAILY),
+ dmc.Box(className="full-width", id=ElementIds.QUERY_DAILY),
type="circle",
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Heatmap chart",
id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
@@ -113,20 +114,20 @@ def section_one():
),
),
dcc.Loading(
- html.Div(className="full-width", id=ElementIds.QUERY_HEATMAP),
+ dmc.Box(className="full-width", id=ElementIds.QUERY_HEATMAP),
type="circle",
),
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Descriptive statistics",
tooltip_text="count, mean, std, min, max, and percentiles",
id_button=IdButtons.TABLE_EXPLORE,
),
),
- html.Div(
+ dmc.Box(
className="container-row justify-content-center",
children=[
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -136,13 +137,13 @@ def section_one():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className=(
"container-row full-width justify-center mt-2"
),
children=[
html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.SEC1_MONTH_SLIDER,
min=1,
@@ -168,11 +169,11 @@ def section_one():
),
],
),
- html.Div(
+ dmc.Box(
className="container-row justify-center",
children=[
html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.SEC1_HOUR_SLIDER,
min=0,
@@ -202,7 +203,7 @@ def section_one():
),
],
),
- html.Div(
+ dmc.Box(
id=ElementIds.TABLE_DATA_EXPLORER,
),
],
@@ -211,22 +212,22 @@ def section_one():
def section_two_inputs():
"""Return all the input forms from section two."""
- return html.Div(
+ return dmc.Box(
children=[
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Customizable heatmap",
tooltip_text=None,
id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL,
),
),
- html.Div(
+ dmc.Box(
className="container-row full-width three-inputs-container",
children=[
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -243,7 +244,7 @@ def section_two_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -253,13 +254,13 @@ def section_two_inputs():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className=(
"container-row full-width justify-center mt-2"
),
children=[
html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.SEC2_MONTH_SLIDER,
min=1,
@@ -285,11 +286,11 @@ def section_two_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className="container-row justify-center",
children=[
html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.SEC2_HOUR_SLIDER,
min=0,
@@ -317,7 +318,7 @@ def section_two_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -327,7 +328,7 @@ def section_two_inputs():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -342,7 +343,7 @@ def section_two_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -358,7 +359,7 @@ def section_two_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -384,14 +385,14 @@ def section_two_inputs():
def section_two():
"""Return the two graphs in section two."""
- return html.Div(
+ return dmc.Box(
id=ElementIds.TAB6_SEC2_CONTAINER,
className="container-col justify-center full-width",
children=[
section_two_inputs(),
dcc.Loading(
type="circle",
- children=html.Div(className="full-width", id=ElementIds.CUSTOM_HEATMAP),
+ children=dmc.Box(className="full-width", id=ElementIds.CUSTOM_HEATMAP),
),
dbc.Checklist(
options=[
@@ -416,13 +417,13 @@ def section_two():
def section_three_inputs():
""""""
- return html.Div(
+ return dmc.Box(
className="container-row full-width three-inputs-container",
children=[
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(style={"flex": "30%"}, children=["X Variable:"]),
@@ -434,7 +435,7 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(style={"flex": "30%"}, children=["Y Variable:"]),
@@ -446,7 +447,7 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(style={"flex": "30%"}, children=["Color By:"]),
@@ -460,7 +461,7 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -470,11 +471,11 @@ def section_three_inputs():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className="container-row full-width justify-center",
children=[
html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
min=1,
@@ -500,11 +501,11 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className="container-row full-width justify-center",
children=[
html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
min=0,
@@ -532,7 +533,7 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -542,7 +543,7 @@ def section_three_inputs():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -556,7 +557,7 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(children=["Min Value:"], style={"flex": "30%"}),
@@ -571,7 +572,7 @@ def section_three_inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(children=["Max Value:"], style={"flex": "30%"}),
@@ -594,10 +595,10 @@ def section_three_inputs():
def section_three():
"""Return the two graphs in section three."""
- return html.Div(
+ return dmc.Box(
className="container-col full-width",
children=[
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="More charts",
tooltip_text=None,
@@ -606,11 +607,11 @@ def section_three():
),
section_three_inputs(),
dcc.Loading(
- html.Div(id=ElementIds.THREE_VAR),
+ dmc.Box(id=ElementIds.THREE_VAR),
type="circle",
),
dcc.Loading(
- html.Div(id=ElementIds.TWO_VAR),
+ dmc.Box(id=ElementIds.TWO_VAR),
type="circle",
),
],
@@ -619,7 +620,7 @@ def section_three():
def layout():
"""Return the contents of tab six."""
- return html.Div(
+ return dmc.Box(
className="justify-center",
children=[section_one(), section_two(), section_three()],
)
diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py
index 644dcd3e..8c597743 100644
--- a/pages/lib/global_element_ids.py
+++ b/pages/lib/global_element_ids.py
@@ -223,3 +223,22 @@ class ElementIds(str, Enum):
TABS = "tabs"
STORE_CONTAINER = "store-container"
TABS_CONTENT = "tabs-content"
+ BURGER_BUTTON = "burger-button"
+ SIDE_BAR = "sidebar"
+ NAV_GROUP_MAIN = "nav-group-main"
+ NAV_GROUP_CONTROLS = "nav-group-controls"
+ NAV_DOC_LINK = "nav-doc-link"
+ LAYOUT_URL = "url"
+ SELECT_URL = "url"
+ MAIN_URL = "url"
+ PAGE_CONTENT = "page-content"
+ NAV = "nav-"
+ NAV_SUMMARY = "nav-summary"
+ NAV_T_RH = "nav-t-rh"
+ NAV_SUN = "nav-sun"
+ NAV_WIND = "nav-wind"
+ NAV_PSY_CHART = "nav-psy-chart"
+ NAV_EXPLORER = "nav-explorer"
+ NAV_OUTDOOR = "nav-outdoor"
+ NAV_NATURAL_VENTILATION = "nav-natural-ventilation"
+ NAV_CHANGELOG = "nav-changelog"
\ No newline at end of file
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index f6cff013..c988bcb4 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -1,6 +1,6 @@
import dash_bootstrap_components as dbc
import dash
-from dash import dcc, html
+from dash import dcc, html, Input, Output, State, callback
import dash_mantine_components as dmc
from dash_iconify import DashIconify
from pages.lib.global_column_names import ColNames
@@ -8,19 +8,30 @@
from pages.lib.global_element_ids import ElementIds
+def burger_button():
+ """create burger button"""
+ return dmc.ActionIcon(
+ DashIconify(icon="radix-icons:hamburger-menu", width=20),
+ id=ElementIds.BURGER_BUTTON,
+ size="lg",
+ variant="filled",
+ color="blue",
+ )
+
+
def alert():
- """Alert for survey."""
- return html.Div(
- id=ElementIds.ALERT_CONTAINER,
+ """Survey toast + periodic timer."""
+ return dmc.Stack(
+ gap=0,
children=[
dbc.Toast(
[
"If you have a moment, help us improve Clima and take a ",
- html.A(
+ dmc.Anchor(
"quick user survey",
href="https://forms.gle/k289zP3R92jdu14M7",
- className="alert-link",
target="_blank",
+ className="alert-link",
),
"! ☀️",
],
@@ -30,12 +41,13 @@ def alert():
is_open=False,
dismissable=True,
className="survey-alert",
- style={"position": "fixed", "top": 25, "right": 10, "width": 400},
),
- dcc.Interval(
- id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
- interval=12 * 1000,
- n_intervals=0,
+ dmc.Box(
+ children=dcc.Interval(
+ id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
+ interval=12 * 1000,
+ n_intervals=0,
+ )
),
],
)
@@ -43,39 +55,37 @@ def alert():
def footer():
"""Build the footer at the bottom of the page."""
- return dbc.Row(
- align="center",
- justify="between",
+ return dmc.Box(
id=ElementIds.FOOTER_CONTAINER,
children=[
- dbc.Col(
+ dmc.Box(
children=[
- dbc.Row(
- html.A(
- children=[
- html.Img(
- src="assets/img/cbe-logo.png",
- )
- ],
- href="https://cbe.berkeley.edu/",
+ dmc.Anchor(
+ href="https://cbe.berkeley.edu/",
+ children=dmc.Image(
+ src="assets/img/cbe-logo.png",
+ alt="CBE Logo",
+ h=65,
+ w="auto",
+ fit="contain"
)
),
],
- width=12,
- md=4,
- style={"padding": "15px"},
+ className="footer-logo-section"
),
- dbc.Col(
+ dmc.Box(
children=[
- dbc.Row(
- [
+ dmc.Stack(
+ gap="xs",
+ children=[
dcc.Markdown(
"""
Please cite us:
- Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool:
- A free and open-source web application for climate analysis tailored to sustainable building design.
+ Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool:
+ A free and open-source web application for climate analysis tailored to sustainable building design.
Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5).
- """
+ """,
+ className="footer-markdown-text"
),
dmc.Group(
[
@@ -85,6 +95,7 @@ def footer():
underline=True,
c="white",
target="_blank",
+ className="footer-link"
),
dmc.Anchor(
"Contributors",
@@ -92,6 +103,7 @@ def footer():
underline=True,
c="white",
target="_blank",
+ className="footer-link"
),
dmc.Anchor(
"Report issues on GitHub",
@@ -99,6 +111,7 @@ def footer():
underline=True,
c="white",
target="_blank",
+ className="footer-link"
),
dmc.Anchor(
"Contact us",
@@ -106,6 +119,7 @@ def footer():
underline=True,
c="white",
target="_blank",
+ className="footer-link"
),
dmc.Anchor(
"Documentation",
@@ -113,6 +127,7 @@ def footer():
underline=True,
c="white",
target="_blank",
+ className="footer-link"
),
dmc.Anchor(
"License",
@@ -120,110 +135,181 @@ def footer():
underline=True,
c="white",
target="_blank",
+ className="footer-link"
),
],
- spacing="sm",
- style={"marginTop": "1rem"},
+ gap="sm",
+ className="footer-links-group"
),
],
- style={"marginTop": "1rem"},
+ className="footer-text-content"
),
],
- width=12,
- md=8,
+ className="footer-content-section"
),
],
)
def banner():
- """Build the banner at the top of the page."""
- return html.Div(
+ """Top banner rewritten with dash-mantine-components only."""
+ return dmc.Box(
id=ElementIds.BANNER,
children=[
dmc.Group(
- position="apart",
+ justify="space-between",
align="center",
+ wrap="nowrap",
children=[
dmc.Group(
align="center",
+ gap="md",
children=[
- html.A(
- href="/",
- children=[
- dmc.Image(
- src="assets/img/cbe-logo-small.png",
- height=40,
- width="auto",
- )
- ],
- ),
+ burger_button(),
+ dmc.Image(src="assets/img/cbe-logo-small.png", h=40, w="auto"),
dmc.Stack(
- spacing=0,
+ gap=2,
children=[
dmc.Title(
"CBE Clima Tool",
- order=1,
id=ElementIds.BANNER_TITLE,
- style={"fontSize": "2rem"},
+ order=2,
),
dmc.Text(
- "Current Location:",
+ "Current Location: N/A",
id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
size="sm",
+ opacity=0.85,
),
],
),
],
),
- dmc.Group(
- align="center",
- children=[
- html.A(
- dmc.Button(
- "Documentation",
- leftIcon=DashIconify(icon="bi:book-half", width=20),
- variant="filled",
- color="#5c7cfa",
- ),
- href=DocLinks.MAIN.value,
- target="_blank",
- style={"textDecoration": "none"},
- ),
- dmc.SegmentedControl(
- id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT,
- value="local",
- radius="md",
- data=[
- {"label": "Global Value Ranges", "value": "global"},
- {"label": "Local Value Ranges", "value": "local"},
- ],
- ),
- dmc.SegmentedControl(
- id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT,
- value=UnitSystem.SI,
- radius="md",
- data=[
- {
- "label": UnitSystem.SI.upper(),
- "value": UnitSystem.SI,
- },
- {
- "label": UnitSystem.IP.upper(),
- "value": UnitSystem.IP,
- },
- ],
- ),
- ],
- ),
],
)
],
)
+def sidebar():
+ """ create side bar """
+ return dmc.Drawer(
+ id=ElementIds.SIDE_BAR,
+ title=dmc.Group([
+ dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"),
+ dmc.Text("CBE Clima Tool", fw=600)
+ ]),
+ padding="md",
+ size="300px",
+ zIndex=999,
+ opened=False,
+ className="custom-sidebar",
+ styles={
+ "title": {"paddingRight": 30},
+ },
+ children=[
+ dmc.Stack(
+ gap="sm",
+ children=build_sidebar_nav_items()
+ )
+ ],
+ )
+
+
+# Pages Icon
+PAGE_ICON_MAP = {
+ "Select Weather File": "tabler:upload",
+ "Climate Summary": "tabler:chart-bar",
+ "Temperature and Humidity": "tabler:temperature",
+ "Sun and Clouds": "tabler:sun",
+ "Wind": "tabler:wind",
+ "Psychrometric Chart": "tabler:chart-dots",
+ "Natural Ventilation": "tabler:windmill",
+ "Outdoor Comfort": "tabler:thermometer",
+ "Data Explorer": "tabler:database",
+ "Changelog": "tabler:history"
+}
+
+def build_sidebar_nav_items():
+ # === Secondary Menu ===
+ sub_links = []
+ for page in dash.page_registry.values():
+ if page[ColNames.NAME] in ["404"]:
+ continue
+ icon = PAGE_ICON_MAP.get(page[ColNames.NAME], "tabler:circle")
+ sub_links.append(
+ dmc.NavLink(
+ label=page[ColNames.NAME],
+ leftSection=DashIconify(icon=icon, width=20),
+ href=page[ColNames.PATH],
+ id=f"nav-{page[ColNames.PATH].replace('/', '')}",
+ active=False,
+ style={"marginBottom": "4px"},
+ )
+ )
+
+ # Primary Menu
+ parent_group = dmc.NavLink(
+ label="Pages Menu",
+ leftSection=DashIconify(icon="tabler:list-details", width=20),
+ children=sub_links,
+ id=ElementIds.NAV_GROUP_MAIN,
+ variant="light",
+ childrenOffset=18,
+ )
+
+ controls_stack = dmc.Stack(
+ gap="sm",
+ px=0,
+ py="xs",
+ children=[
+ dmc.SegmentedControl(
+ id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT,
+ value="local",
+ data=[
+ {"label": "Global Value Ranges", "value": "global"},
+ {"label": "Local Value Ranges", "value": "local"},
+ ],
+ radius="md",
+ size="sm",
+ ),
+ dmc.SegmentedControl(
+ id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT,
+ value=UnitSystem.SI,
+ data=[
+ {"label": UnitSystem.SI.upper(), "value": UnitSystem.SI},
+ {"label": UnitSystem.IP.upper(), "value": UnitSystem.IP},
+ ],
+ radius="md",
+ size="sm",
+ ),
+ ],
+ )
+
+ # Primary Menu
+ controls_group = dmc.NavLink(
+ label="Tools Menu",
+ leftSection=DashIconify(icon="tabler:settings", width=20),
+ children=[controls_stack],
+ id=ElementIds.NAV_GROUP_CONTROLS,
+ variant="light",
+ childrenOffset=18,
+ )
+
+ # Primary Menu - Documentation
+ doc_link = dmc.NavLink(
+ label="Documentation",
+ leftSection=DashIconify(icon="tabler:file-text", width=20),
+ href=DocLinks.MAIN.value,
+ target="_blank",
+ id=ElementIds.NAV_DOC_LINK,
+ variant="light",
+ )
+ return [parent_group, controls_group, doc_link]
+
+
def store():
- return html.Div(
+ return dmc.Box(
id=ElementIds.STORE,
children=[
dcc.Store(id=ElementIds.ID_LAYOUT_DF_STORE, storage_type="session"),
@@ -236,44 +322,17 @@ def store():
def build_tabs():
- return html.Div(
+ return dmc.Box(
id=ElementIds.TABS_CONTAINER,
children=[
- html.Div(
- id=ElementIds.TABS_PARENT,
- className="custom-tabs",
- children=[
- dbc.Nav(
- [
- dbc.NavItem(
- dbc.NavLink(
- page[ColNames.NAME],
- id=page[ColNames.PATH],
- href=page[ColNames.PATH],
- active="exact",
- className="nav-link",
- disabled=True,
- ),
- className="custom-tab",
- )
- for page in dash.page_registry.values()
- if page[ColNames.NAME] not in ["404", "changelog"]
- ],
- id=ElementIds.TABS,
- class_name="tab-container",
- pills=True,
- justified=True,
- )
- ],
- ),
- html.Div(
+ dmc.Box(
id=ElementIds.STORE_CONTAINER,
children=[
store(),
- html.Div(
+ dmc.Box(
id=ElementIds.TABS_CONTENT,
children=[
- alert(), # alert can be removed after survey is done
+ alert(),
dash.page_container,
],
),
@@ -281,3 +340,46 @@ def build_tabs():
),
],
)
+
+@callback(
+ Output(ElementIds.SIDE_BAR, "opened"),
+ Input(ElementIds.BURGER_BUTTON, "n_clicks"),
+ State(ElementIds.SIDE_BAR, "opened"),
+ prevent_initial_call=True,
+)
+def toggle_sidebar(n_clicks, opened):
+ if n_clicks:
+ return not opened
+ return opened
+
+
+@callback(
+ Output(ElementIds.SIDE_BAR, "opened", allow_duplicate=True),
+ Input(ElementIds.LAYOUT_URL, "pathname"),
+ prevent_initial_call='initial_duplicate',
+)
+def close_sidebar_on_navigation(pathname):
+ return False
+
+
+# Callback to set active state for navigation links based on current URL
+@callback(
+ [Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active")
+ for page in dash.page_registry.values()
+ if page[ColNames.NAME] not in ["404"]],
+ Input(ElementIds.LAYOUT_URL, "pathname"),
+ prevent_initial_call=True,
+)
+def update_nav_active_state(pathname):
+ """Update active state of navigation links based on current URL pathname"""
+ active_states = []
+
+ for page in dash.page_registry.values():
+ if page[ColNames.NAME] in ["404"]:
+ continue
+
+ # Check if current pathname matches this page's path
+ is_active = pathname == page[ColNames.PATH]
+ active_states.append(is_active)
+
+ return active_states
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index 539ca233..281c4f59 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -6,6 +6,7 @@
import dash_bootstrap_components as dbc
import pandas as pd
from dash import html, dash_table, dcc
+import dash_mantine_components as dmc
from config import UnitSystem
from pages.lib.global_scheme import fig_config, mapping_dictionary, month_lst
@@ -151,12 +152,12 @@ def title_with_tooltip(text, tooltip_text, id_button):
if tooltip_text:
display_tooltip = "block"
- return html.Div(
+ return dmc.Box(
className="container-row",
style={"padding": "1rem", "marginTop": "1rem"},
children=[
html.H5(text, style={"marginRight": "0.5rem"}),
- html.Div(
+ dmc.Box(
[
html.Sup(
html.Img(
@@ -187,12 +188,12 @@ def title_with_link(
id_button=None,
doc_link: str = "",
):
- return html.Div(
+ return dmc.Box(
className="container-row",
style={"padding": "1rem", "marginTop": "1rem"},
children=[
html.H5(text, style={"marginRight": "0.5rem"}),
- html.Div(
+ dmc.Box(
[
html.Sup(
html.A(
diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py
index ee002e20..67f21714 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -1,5 +1,8 @@
+import math
+
import dash
from dash import dcc, html
+import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
@@ -41,7 +44,7 @@
def layout():
- return html.Div(
+ return dmc.Box(
className="container-col",
id=ElementIds.MAIN_NV_SECTION,
children=[
@@ -65,7 +68,7 @@ def update_layout(si_ip):
dpt_set = 16
return [
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Natural Ventilation Potential",
id_button=IdButtons.NATURAL_VENTILATION_LABEL,
@@ -74,13 +77,13 @@ def update_layout(si_ip):
),
inputs_tab(tdb_set_min, tdb_set_max, dpt_set),
dcc.Loading(
- html.Div(
+ dmc.Box(
id=ElementIds.NV_HEATMAP_CHART,
style={"marginTop": "1rem"},
),
type="circle",
),
- html.Div(
+ dmc.Box(
className="container-row align-center justify-center",
children=[
dbc.Checklist(
@@ -96,7 +99,7 @@ def update_layout(si_ip):
"marginRight": "-2rem",
},
),
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Normalize data",
tooltip_text=(
@@ -109,7 +112,7 @@ def update_layout(si_ip):
],
),
dcc.Loading(
- html.Div(
+ dmc.Box(
id=ElementIds.NV_BAR_CHART,
style={"marginTop": "1rem"},
),
@@ -119,10 +122,10 @@ def update_layout(si_ip):
def inputs_tab(t_min, t_max, d_set):
- return html.Div(
+ return dmc.Box(
className="container-row full-width three-inputs-container",
children=[
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -133,7 +136,7 @@ def inputs_tab(t_min, t_max, d_set):
n_clicks=1,
),
html.H6("Outdoor dry-bulb air temperature range"),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(children=["Min Value:"], style={"flex": "30%"}),
@@ -147,7 +150,7 @@ def inputs_tab(t_min, t_max, d_set):
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(children=["Max Value:"], style={"flex": "30%"}),
@@ -163,7 +166,7 @@ def inputs_tab(t_min, t_max, d_set):
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -173,11 +176,11 @@ def inputs_tab(t_min, t_max, d_set):
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className="container-row full-width justify-center mt-2",
children=[
html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.NV_MONTH_SLIDER,
min=1,
@@ -203,11 +206,11 @@ def inputs_tab(t_min, t_max, d_set):
),
],
),
- html.Div(
+ dmc.Box(
className="container-row align-center justify-center",
children=[
html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.NV_HOUR_SLIDER,
min=0,
@@ -235,7 +238,7 @@ def inputs_tab(t_min, t_max, d_set):
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -261,7 +264,7 @@ def inputs_tab(t_min, t_max, d_set):
value=[],
id=ElementIds.ENABLE_CONDENSATION,
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
diff --git a/pages/not_found_404.py b/pages/not_found_404.py
index ff3b389c..61e22382 100644
--- a/pages/not_found_404.py
+++ b/pages/not_found_404.py
@@ -27,7 +27,7 @@
dmc.Button(
"Home page",
fullWidth=True,
- leftIcon=DashIconify(icon="material-symbols:home-outline-rounded"),
+ leftSection=DashIconify(icon="material-symbols:home-outline-rounded"),
),
href=PageUrls.SELECT.value,
),
diff --git a/pages/outdoor.py b/pages/outdoor.py
index 0e4885ad..7e8335fc 100644
--- a/pages/outdoor.py
+++ b/pages/outdoor.py
@@ -1,5 +1,6 @@
import dash
from dash import dcc, html
+import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
@@ -43,7 +44,7 @@ def inputs_outdoor_comfort():
md=6,
sm=12,
children=[
- html.Div(
+ dmc.Box(
className="container-row center-block",
children=[
html.H4(
@@ -56,7 +57,7 @@ def inputs_outdoor_comfort():
options=outdoor_dropdown_names,
value="utci_Sun_Wind",
),
- html.Div(
+ dmc.Box(
id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"}
),
],
@@ -77,11 +78,11 @@ def inputs_outdoor_comfort():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className="container-row full-width justify-center mt-2",
children=[
html.H6("Month Range", style={"flex": "5%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
min=1,
@@ -107,11 +108,11 @@ def inputs_outdoor_comfort():
),
],
),
- html.Div(
+ dmc.Box(
className="container-row align-center justify-center",
children=[
html.H6("Hour Range", style={"flex": "5%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
min=0,
@@ -144,10 +145,10 @@ def inputs_outdoor_comfort():
def outdoor_comfort_chart():
- return html.Div(
+ return dmc.Box(
children=[
- html.Div(id=ElementIds.OUTDOOR_COMFORT_OUTPUT),
- html.Div(
+ dmc.Box(id=ElementIds.OUTDOOR_COMFORT_OUTPUT),
+ dmc.Box(
children=title_with_link(
text="UTCI heatmap chart",
id_button=IdButtons.UTCI_CHARTS_LABEL,
@@ -155,10 +156,10 @@ def outdoor_comfort_chart():
)
),
dcc.Loading(
- html.Div(id=ElementIds.UTCI_HEATMAP),
+ dmc.Box(id=ElementIds.UTCI_HEATMAP),
type="circle",
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="UTCI thermal stress chart",
id_button=IdButtons.UTCI_CHARTS_LABEL,
@@ -166,10 +167,10 @@ def outdoor_comfort_chart():
)
),
dcc.Loading(
- html.Div(id=ElementIds.UTCI_CATEGORY_HEATMAP),
+ dmc.Box(id=ElementIds.UTCI_CATEGORY_HEATMAP),
type="circle",
),
- html.Div(
+ dmc.Box(
className="container-row align-center justify-center",
children=[
dbc.Checklist(
@@ -185,7 +186,7 @@ def outdoor_comfort_chart():
"marginRight": "-2rem",
},
),
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Normalize data",
tooltip_text=(
@@ -198,7 +199,7 @@ def outdoor_comfort_chart():
],
),
dcc.Loading(
- html.Div(id=ElementIds.UTCI_SUMMARY_CHART),
+ dmc.Box(id=ElementIds.UTCI_SUMMARY_CHART),
type="circle",
),
],
@@ -209,7 +210,7 @@ def layout():
return (
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
className="container-col",
children=[inputs_outdoor_comfort(), outdoor_comfort_chart()],
),
diff --git a/pages/psy-chart.py b/pages/psy-chart.py
index bf967751..33af12c6 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -1,5 +1,6 @@
import dash
from dash import dcc, html
+import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
@@ -61,13 +62,13 @@
def inputs():
""""""
- return html.Div(
+ return dmc.Box(
className="container-row full-width three-inputs-container",
children=[
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -86,7 +87,7 @@ def inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -96,11 +97,11 @@ def inputs():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className="container-row full-width justify-center mt-2",
children=[
html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.PSY_MONTH_SLIDER,
min=1,
@@ -126,11 +127,11 @@ def inputs():
),
],
),
- html.Div(
+ dmc.Box(
className="container-row align-center justify-center",
children=[
html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Box(
dcc.RangeSlider(
id=ElementIds.PSY_HOUR_SLIDER,
min=0,
@@ -158,7 +159,7 @@ def inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_col_center_one_of_three,
children=[
dbc.Button(
@@ -168,7 +169,7 @@ def inputs():
className="mb-2",
n_clicks=0,
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -182,7 +183,7 @@ def inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(children=["Min Value:"], style={"flex": "30%"}),
@@ -196,7 +197,7 @@ def inputs():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(children=["Max Value:"], style={"flex": "30%"}),
@@ -218,7 +219,7 @@ def inputs():
def layout():
return (
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Psychrometric Chart",
id_button=IdButtons.PSYCHROMETRIC_CHART_CHART,
@@ -227,9 +228,9 @@ def layout():
),
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
className="container-col",
- children=[inputs(), html.Div(id=ElementIds.PSYCH_CHART)],
+ children=[inputs(), dmc.Box(id=ElementIds.PSYCH_CHART)],
),
),
)
@@ -357,7 +358,7 @@ def update_psych_chart(
if si_ip == UnitSystem.IP:
for j in range(len(dbt_list)):
- convert_SI_to_IP(dbt_list_convert, j)
+ dbt_list_convert[j] = dbt_list_convert[j] * 1.8 + 32
fig.add_trace(
go.Scatter(
diff --git a/pages/select.py b/pages/select.py
index 86c22d87..330c8728 100644
--- a/pages/select.py
+++ b/pages/select.py
@@ -39,7 +39,7 @@
def layout():
"""Contents in the first tab 'Select Weather File'"""
- return html.Div(
+ return dmc.Box(
className="container-col tab-container",
children=[
dcc.Loading(
@@ -69,7 +69,7 @@ def layout():
visible=False,
id=ElementIds.SKELETON_GRAPH_CONTAINER,
height=500,
- children=html.Div(id=ElementIds.TAB_ONE_MAP),
+ children=dmc.Box(id=ElementIds.TAB_ONE_MAP),
),
dbc.Modal(
[
@@ -236,15 +236,16 @@ def switch_si_ip(_, si_ip_input, url_store, lines):
@callback(
[
- Output("/", "disabled"),
- Output("/summary", "disabled"),
- Output("/t-rh", "disabled"),
- Output("/sun", "disabled"),
- Output("/wind", "disabled"),
- Output("/psy-chart", "disabled"),
- Output("/explorer", "disabled"),
- Output("/outdoor", "disabled"),
- Output("/natural-ventilation", "disabled"),
+ Output(ElementIds.NAV, "disabled"),
+ Output(ElementIds.NAV_SUMMARY, "disabled"),
+ Output(ElementIds.NAV_T_RH, "disabled"),
+ Output(ElementIds.NAV_SUN, "disabled"),
+ Output(ElementIds.NAV_WIND, "disabled"),
+ Output(ElementIds.NAV_PSY_CHART, "disabled"),
+ Output(ElementIds.NAV_EXPLORER, "disabled"),
+ Output(ElementIds.NAV_OUTDOOR, "disabled"),
+ Output(ElementIds.NAV_NATURAL_VENTILATION, "disabled"),
+ Output(ElementIds.NAV_CHANGELOG, "disabled"),
Output(ElementIds.ID_SELECT_BANNER_SUBTITLE, "children"),
],
[
@@ -266,6 +267,7 @@ def enable_tabs_when_data_is_loaded(meta, data):
True,
True,
True,
+ True, # changelog always disabled
default,
)
else:
@@ -279,6 +281,7 @@ def enable_tabs_when_data_is_loaded(meta, data):
False,
False,
False,
+ True, # changelog always disabled
"Current Location: " + meta[ColNames.CITY] + ", " + meta[ColNames.COUNTRY],
)
@@ -324,7 +327,7 @@ def change_text_modal(click_map):
@callback(
Output(ElementIds.SKELETON_GRAPH_CONTAINER, "children"),
- Input("url", "pathname"),
+ Input(ElementIds.SELECT_URL, "pathname"),
)
def plot_location_epw_files(pathname):
# print(pathname)
diff --git a/pages/summary.py b/pages/summary.py
index de5412d9..832ab7e2 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -2,6 +2,7 @@
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import dcc, html, Output, Input, State, callback
+import dash_mantine_components as dmc
import plotly.graph_objects as go
import requests
@@ -35,7 +36,7 @@
def layout():
"""Contents in the second tab 'Climate Summary'."""
- return html.Div(
+ return dmc.Box(
className="container-col",
id=ElementIds.TAB_TWO_CONTAINER,
children=[
@@ -56,13 +57,13 @@ def update_layout(si_ip):
heating_setpoint = 50
cooling_setpoint = 64
- return html.Div(
+ return dmc.Box(
className="container-col",
id=ElementIds.TAB2_SCE1_CONTAINER,
children=[
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
className="container-col",
id=ElementIds.LOCATION_INFO,
style={"padding": "12px"},
@@ -70,9 +71,9 @@ def update_layout(si_ip):
),
dcc.Loading(
type="circle",
- children=html.Div(className="tab-two-section", id=ElementIds.WORLD_MAP),
+ children=dmc.Box(className="tab-two-section", id=ElementIds.WORLD_MAP),
),
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Download",
id_button=IdButtons.DOWNLOAD_BUTTON_LABEL,
@@ -109,7 +110,7 @@ def update_layout(si_ip):
],
),
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Heating and Cooling Degree Days",
id_button=IdButtons.HDD_CDD_CHART,
@@ -168,9 +169,9 @@ def update_layout(si_ip):
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER),
+ children=dmc.Box(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER),
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Climate Profiles",
id_button=IdButtons.CLIMATE_PROFILES_CHART,
diff --git a/pages/sun.py b/pages/sun.py
index 919655f4..18794ff0 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -2,6 +2,7 @@
from pages.lib.global_element_ids import ElementIds
import dash
+import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
import numpy as np
from dash import html, dcc
@@ -55,10 +56,10 @@
def sun_path():
"""Return the layout for the custom sun path and its dropdowns."""
- return html.Div(
+ return dmc.Box(
className="container-col justify-center",
children=[
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Sun path chart",
id_button=IdButtons.SUN_PATH_CHART_LABEL,
@@ -104,7 +105,7 @@ def sun_path():
),
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
id=ElementIds.CUSTOM_SUNPATH,
),
),
@@ -114,17 +115,17 @@ def sun_path():
def explore_daily_heatmap():
"""Contents of the bottom part of the tab"""
- return html.Div(
+ return dmc.Box(
className="container-col full-width",
children=[
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Daily charts",
id_button=IdButtons.DAILY_CHART_LABEL,
doc_link=DocLinks.CUSTOM_HEATMAP,
),
),
- html.Div(
+ dmc.Box(
className="container-row justify-center align-center mb-2",
children=[
html.H6(
@@ -140,17 +141,17 @@ def explore_daily_heatmap():
),
],
),
- dcc.Loading(type="circle", children=html.Div(id=ElementIds.TAB4_DAILY)),
+ dcc.Loading(type="circle", children=dmc.Box(id=ElementIds.TAB4_DAILY)),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.TAB4_HEATMAP),
+ children=dmc.Box(id=ElementIds.TAB4_HEATMAP),
),
],
)
def static_section():
- return html.Div(
+ return dmc.Box(
id=ElementIds.STATIC_SECTION,
className="container-col full-width",
children=[
@@ -161,7 +162,7 @@ def static_section():
def layout():
"""Contents of tab four."""
- return html.Div(
+ return dmc.Box(
className="container-col",
id=ElementIds.TAB_FOUR_CONTAINER,
children=[sun_path(), static_section(), explore_daily_heatmap()],
@@ -177,7 +178,7 @@ def update_static_section(si_ip):
if si_ip == UnitSystem.IP:
hor_unit = "Btu/ft²"
return [
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")",
id_button=IdButtons.MONTHLY_CHART_LABEL,
@@ -186,9 +187,9 @@ def update_static_section(si_ip):
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.MONTHLY_SOLAR),
+ children=dmc.Box(id=ElementIds.MONTHLY_SOLAR),
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Cloud coverage",
id_button=IdButtons.CLOUD_CHART_LABEL,
@@ -197,7 +198,7 @@ def update_static_section(si_ip):
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.CLOUD_COVER),
+ children=dmc.Box(id=ElementIds.CLOUD_COVER),
),
]
diff --git a/pages/t_rh.py b/pages/t_rh.py
index 6729a31d..4d249f81 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -1,5 +1,6 @@
import dash
from dash_extensions.enrich import Output, Input, State, dcc, html, callback
+import dash_mantine_components as dmc
from config import PageUrls, DocLinks, PageInfo
from pages.lib.global_scheme import dropdown_names
from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile
@@ -30,10 +31,10 @@
def layout():
- return html.Div(
+ return dmc.Box(
className="container-col full-width",
children=[
- html.Div(
+ dmc.Box(
className="container-row full-width align-center justify-center",
children=[
html.H4(
@@ -47,10 +48,10 @@ def layout():
),
],
),
- html.Div(
+ dmc.Box(
className="container-col",
children=[
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Yearly Chart",
id_button=IdButtons.YEARLY_CHART_LABEL,
@@ -59,9 +60,9 @@ def layout():
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.YEARLY_CHART),
+ children=dmc.Box(id=ElementIds.YEARLY_CHART),
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Daily chart",
id_button=IdButtons.DAILY_CHART_LABEL,
@@ -70,9 +71,9 @@ def layout():
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.DAILY),
+ children=dmc.Box(id=ElementIds.DAILY),
),
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Heatmap chart",
id_button=IdButtons.HEATMAP_CHART_LABEL,
@@ -81,16 +82,16 @@ def layout():
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.HEATMAP),
+ children=dmc.Box(id=ElementIds.HEATMAP),
),
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Descriptive statistics",
tooltip_text="count, mean, std, min, max, and percentiles",
id_button=IdButtons.TABLE_TMP_RH,
),
),
- html.Div(
+ dmc.Box(
id=ElementIds.TABLE_TMP_HUM,
),
],
diff --git a/pages/wind.py b/pages/wind.py
index 8b744447..da2813bc 100644
--- a/pages/wind.py
+++ b/pages/wind.py
@@ -1,5 +1,6 @@
import dash
from dash import dcc, html
+import dash_mantine_components as dmc
from dash_extensions.enrich import Output, Input, State, callback
from pages.lib.global_element_ids import ElementIds
@@ -29,11 +30,11 @@
def sliders():
"""Returns 2 sliders for the hour"""
- return html.Div(
+ return dmc.Box(
className="container-col justify-center",
id=ElementIds.SLIDER_CONTAINER,
children=[
- html.Div(
+ dmc.Box(
className="container-row each-slider",
children=[
html.P("Month Range"),
@@ -49,7 +50,7 @@ def sliders():
),
],
),
- html.Div(
+ dmc.Box(
className="container-row each-slider",
children=[
html.P("Hour Range"),
@@ -71,25 +72,25 @@ def sliders():
def seasonal_wind_rose():
"""Return the section with the 4 seasonal wind rose graphs."""
- return html.Div(
+ return dmc.Box(
className="container-col",
children=[
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Seasonal Wind Rose",
id_button=IdButtons.SEASONAL_WIND_ROSE_DOC,
doc_link=DocLinks.WIND_ROSE,
),
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
- html.Div(
+ dmc.Box(
className="container-col",
children=[
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
id=ElementIds.WINTER_WIND_ROSE,
className="daily-wind-graph",
),
@@ -100,12 +101,12 @@ def seasonal_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className="container-col",
children=[
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
id=ElementIds.SPRING_WIND_ROSE,
className="daily-wind-graph",
),
@@ -118,15 +119,15 @@ def seasonal_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
- html.Div(
+ dmc.Box(
className="container-col",
children=[
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
id=ElementIds.SUMMER_WIND_ROSE,
className="daily-wind-graph",
),
@@ -137,12 +138,12 @@ def seasonal_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className="container-col",
children=[
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
id=ElementIds.FALL_WIND_ROSE,
className="daily-wind-graph",
),
@@ -161,28 +162,28 @@ def seasonal_wind_rose():
def daily_wind_rose():
"""Return the section for the 3 daily wind rose graphs."""
- return html.Div(
+ return dmc.Box(
className="container-col full-width",
id=ElementIds.TAB5_DAILY_CONTAINER,
children=[
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Daily Wind Rose",
id_button=IdButtons.DAILY_ROSE_CHART,
doc_link=DocLinks.WIND_ROSE,
),
),
- html.Div(
+ dmc.Box(
id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER,
className="container-row full-width",
children=[
- html.Div(
+ dmc.Box(
className="container-col",
children=[
- html.Div(
+ dmc.Box(
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
className="daily-wind-graph",
id=ElementIds.MORNING_WIND_ROSE,
),
@@ -194,13 +195,13 @@ def daily_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className="container-col",
children=[
- html.Div(
+ dmc.Box(
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
className="daily-wind-graph",
id=ElementIds.NOON_WIND_ROSE,
),
@@ -212,13 +213,13 @@ def daily_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className="container-col",
children=[
- html.Div(
+ dmc.Box(
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
className="daily-wind-graph",
id=ElementIds.NIGHT_WIND_ROSE,
),
@@ -237,24 +238,24 @@ def daily_wind_rose():
def custom_wind_rose():
- return html.Div(
+ return dmc.Box(
className="container-col justify-center full-width",
children=[
- html.Div(
+ dmc.Box(
children=title_with_tooltip(
text="Customizable Wind Rose",
tooltip_text=None,
id_button=IdButtons.CUSTOM_ROSE_CHART,
),
),
- html.Div(
+ dmc.Box(
className="container-row full-width justify-center",
id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER,
children=[
- html.Div(
+ dmc.Box(
className="container-col justify-center p-2 mr-2",
children=[
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -271,7 +272,7 @@ def custom_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -290,10 +291,10 @@ def custom_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className="container-col justify-center p-2 ml-2",
children=[
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -310,7 +311,7 @@ def custom_wind_rose():
),
],
),
- html.Div(
+ dmc.Box(
className=container_row_center_full,
children=[
html.H6(
@@ -333,7 +334,7 @@ def custom_wind_rose():
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.CUSTOM_WIND_ROSE),
+ children=dmc.Box(id=ElementIds.CUSTOM_WIND_ROSE),
),
],
)
@@ -341,10 +342,10 @@ def custom_wind_rose():
def layout():
"""Contents in the fifth tab 'Wind'."""
- return html.Div(
+ return dmc.Box(
className="container-col justify-center",
children=[
- html.Div(
+ dmc.Box(
children=title_with_link(
text="Annual Wind Rose",
id_button=IdButtons.WIND_ROSE_LABEL,
@@ -353,17 +354,17 @@ def layout():
),
dcc.Loading(
type="circle",
- children=html.Div(
+ children=dmc.Box(
id=ElementIds.WIND_ROSE,
),
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.WIND_SPEED),
+ children=dmc.Box(id=ElementIds.WIND_SPEED),
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.WIND_DIRECTION),
+ children=dmc.Box(id=ElementIds.WIND_DIRECTION),
),
seasonal_wind_rose(),
daily_wind_rose(),
diff --git a/requirements.txt b/requirements.txt
index d2d7baaf..14cdb68c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,13 +7,13 @@ certifi==2025.7.14
charset-normalizer==3.4.2
cleanpy==0.5.1
click==8.2.1
-dash==2.15.0
+dash==3.2.0
dash-bootstrap-components==1.2.0
dash-core-components==2.0.0
dash-extensions==1.0.7
dash-html-components==2.0.0
dash-iconify==0.1.2
-dash-mantine-components==0.12.1
+dash-mantine-components==2.2.1
dash-table==5.0.0
dataclass-wizard==0.22.3
EditorConfig==0.17.1
diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js
index 8cb41333..b946fc72 100644
--- a/tests/node/cypress/e2e/spec.cy.js
+++ b/tests/node/cypress/e2e/spec.cy.js
@@ -1,7 +1,26 @@
+/*
+ ⚠️ IMPORTANT: This code is only needed when running tests with Cypress's default browser (Electron)
+ If you run tests with other browsers (e.g., Chrome, Firefox), comment out this entire block
+ The URL.canParse() API is not available in Cypress's bundled Node.js 18.17.1 environment
+ but is available in newer Node.js versions used by other browsers
+*/
+/*Cypress.on('uncaught:exception', (err, runnable) => {
+ // Workaround for Cypress environment lacking support for `URL.canParse()` API
+ // This error does not happen in real browsers; it's safe to ignore during tests
+ if (err.message.includes('URL.canParse is not a function')) {
+ return false;
+ }
+});*/
+
function click_tab(name) {
- cy.get('.nav-item')
- .contains(name)
- .click();
+ // Open the sidebar (burger button is fixed on screen)
+ cy.get('#burger-button', { timeout: 10000 }).click({ force: true });
+ // Expand the main nav group if collapsed
+ cy.get('#nav-group-main').click({ force: true });
+ // Locate tab item by ID prefix, then find label by text
+ cy.get('[id^="nav-"]', { timeout: 10000 }).contains(name).click({ force: true });
+ // Wait for tab content container to appear
+ cy.get('#tabs-content', { timeout: 20000 }).should('exist');
}
function load_epw() {
@@ -101,9 +120,14 @@ describe('Clima', () => {
load_epw()
cy.contains('The EPW was successfully loaded!');
click_tab('Temperature and Humidity')
- cy.contains('Global Value Ranges').click();
+ // Expand the "Data Display Options" nav section to access controls
+ cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true });
+ cy.contains('Global Value Ranges', { timeout: 10000 }).click({ force: true });
cy.contains('-40'); // Global minimum: not something you see in Italy!
- cy.contains('IP').click();
+ // Reopen sidebar
+ cy.get('#burger-button', { timeout: 10000 }).click();
+ cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true });
+ cy.contains('IP').click({ force: true });
cy.contains('100'); // Not a Celsius temperature!
cy.contains('Dry bulb temperature (°F)');
});
From ac8e71a20e0edcdf1f43c31036ce947ef12e351a Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Wed, 10 Sep 2025 14:41:43 +1000
Subject: [PATCH 02/23] style: formatted the code via using ruff and black.
---
main.py | 10 ++---
pages/lib/global_element_ids.py | 2 +-
pages/lib/layout.py | 73 +++++++++++++++++----------------
pages/lib/utils.py | 14 ++++---
pages/natural_ventilation.py | 2 -
pages/psy-chart.py | 1 -
6 files changed, 50 insertions(+), 52 deletions(-)
diff --git a/main.py b/main.py
index 6fa4b598..e74a01af 100644
--- a/main.py
+++ b/main.py
@@ -1,10 +1,9 @@
-import dash_bootstrap_components as dbc
-from dash import html, dcc
+from dash import dcc
from dash_extensions.enrich import Output, Input, callback
import dash_mantine_components as dmc
from app import app
-from pages.lib.layout import banner, footer, build_tabs, burger_button, sidebar
+from pages.lib.layout import banner, footer, build_tabs, sidebar
from config import AppConfig
from pages.lib.global_element_ids import ElementIds
@@ -12,10 +11,7 @@
app.title = AppConfig.TITLE
app.layout = dmc.MantineProvider(
- theme={
- "colorScheme": "light",
- "primaryColor": "blue"
- },
+ theme={"colorScheme": "light", "primaryColor": "blue"},
children=[
dcc.Location(id=ElementIds.MAIN_URL, refresh=False),
sidebar(),
diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py
index 8c597743..b9f227de 100644
--- a/pages/lib/global_element_ids.py
+++ b/pages/lib/global_element_ids.py
@@ -241,4 +241,4 @@ class ElementIds(str, Enum):
NAV_EXPLORER = "nav-explorer"
NAV_OUTDOOR = "nav-outdoor"
NAV_NATURAL_VENTILATION = "nav-natural-ventilation"
- NAV_CHANGELOG = "nav-changelog"
\ No newline at end of file
+ NAV_CHANGELOG = "nav-changelog"
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index c988bcb4..c1fec70f 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -1,6 +1,6 @@
import dash_bootstrap_components as dbc
import dash
-from dash import dcc, html, Input, Output, State, callback
+from dash import dcc, Input, Output, State, callback
import dash_mantine_components as dmc
from dash_iconify import DashIconify
from pages.lib.global_column_names import ColNames
@@ -67,11 +67,11 @@ def footer():
alt="CBE Logo",
h=65,
w="auto",
- fit="contain"
- )
+ fit="contain",
+ ),
),
],
- className="footer-logo-section"
+ className="footer-logo-section",
),
dmc.Box(
children=[
@@ -85,7 +85,7 @@ def footer():
A free and open-source web application for climate analysis tailored to sustainable building design.
Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5).
""",
- className="footer-markdown-text"
+ className="footer-markdown-text",
),
dmc.Group(
[
@@ -95,7 +95,7 @@ def footer():
underline=True,
c="white",
target="_blank",
- className="footer-link"
+ className="footer-link",
),
dmc.Anchor(
"Contributors",
@@ -103,7 +103,7 @@ def footer():
underline=True,
c="white",
target="_blank",
- className="footer-link"
+ className="footer-link",
),
dmc.Anchor(
"Report issues on GitHub",
@@ -111,7 +111,7 @@ def footer():
underline=True,
c="white",
target="_blank",
- className="footer-link"
+ className="footer-link",
),
dmc.Anchor(
"Contact us",
@@ -119,7 +119,7 @@ def footer():
underline=True,
c="white",
target="_blank",
- className="footer-link"
+ className="footer-link",
),
dmc.Anchor(
"Documentation",
@@ -127,7 +127,7 @@ def footer():
underline=True,
c="white",
target="_blank",
- className="footer-link"
+ className="footer-link",
),
dmc.Anchor(
"License",
@@ -135,17 +135,17 @@ def footer():
underline=True,
c="white",
target="_blank",
- className="footer-link"
+ className="footer-link",
),
],
gap="sm",
- className="footer-links-group"
+ className="footer-links-group",
),
],
- className="footer-text-content"
+ className="footer-text-content",
),
],
- className="footer-content-section"
+ className="footer-content-section",
),
],
)
@@ -166,7 +166,9 @@ def banner():
gap="md",
children=[
burger_button(),
- dmc.Image(src="assets/img/cbe-logo-small.png", h=40, w="auto"),
+ dmc.Image(
+ src="assets/img/cbe-logo-small.png", h=40, w="auto"
+ ),
dmc.Stack(
gap=2,
children=[
@@ -192,13 +194,15 @@ def banner():
def sidebar():
- """ create side bar """
+ """create side bar"""
return dmc.Drawer(
id=ElementIds.SIDE_BAR,
- title=dmc.Group([
- dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"),
- dmc.Text("CBE Clima Tool", fw=600)
- ]),
+ title=dmc.Group(
+ [
+ dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"),
+ dmc.Text("CBE Clima Tool", fw=600),
+ ]
+ ),
padding="md",
size="300px",
zIndex=999,
@@ -207,12 +211,7 @@ def sidebar():
styles={
"title": {"paddingRight": 30},
},
- children=[
- dmc.Stack(
- gap="sm",
- children=build_sidebar_nav_items()
- )
- ],
+ children=[dmc.Stack(gap="sm", children=build_sidebar_nav_items())],
)
@@ -227,9 +226,10 @@ def sidebar():
"Natural Ventilation": "tabler:windmill",
"Outdoor Comfort": "tabler:thermometer",
"Data Explorer": "tabler:database",
- "Changelog": "tabler:history"
+ "Changelog": "tabler:history",
}
+
def build_sidebar_nav_items():
# === Secondary Menu ===
sub_links = []
@@ -268,7 +268,7 @@ def build_sidebar_nav_items():
value="local",
data=[
{"label": "Global Value Ranges", "value": "global"},
- {"label": "Local Value Ranges", "value": "local"},
+ {"label": "Local Value Ranges", "value": "local"},
],
radius="md",
size="sm",
@@ -341,6 +341,7 @@ def build_tabs():
],
)
+
@callback(
Output(ElementIds.SIDE_BAR, "opened"),
Input(ElementIds.BURGER_BUTTON, "n_clicks"),
@@ -356,7 +357,7 @@ def toggle_sidebar(n_clicks, opened):
@callback(
Output(ElementIds.SIDE_BAR, "opened", allow_duplicate=True),
Input(ElementIds.LAYOUT_URL, "pathname"),
- prevent_initial_call='initial_duplicate',
+ prevent_initial_call="initial_duplicate",
)
def close_sidebar_on_navigation(pathname):
return False
@@ -364,22 +365,24 @@ def close_sidebar_on_navigation(pathname):
# Callback to set active state for navigation links based on current URL
@callback(
- [Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active")
- for page in dash.page_registry.values()
- if page[ColNames.NAME] not in ["404"]],
+ [
+ Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active")
+ for page in dash.page_registry.values()
+ if page[ColNames.NAME] not in ["404"]
+ ],
Input(ElementIds.LAYOUT_URL, "pathname"),
prevent_initial_call=True,
)
def update_nav_active_state(pathname):
"""Update active state of navigation links based on current URL pathname"""
active_states = []
-
+
for page in dash.page_registry.values():
if page[ColNames.NAME] in ["404"]:
continue
-
+
# Check if current pathname matches this page's path
is_active = pathname == page[ColNames.PATH]
active_states.append(is_active)
-
+
return active_states
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index 281c4f59..6ef56cd2 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -41,9 +41,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None):
)
figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name
else:
- figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = (
- f"{tab_name}{custom_str}"
- )
+ figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][
+ ColNames.FILE_NAME
+ ] = f"{tab_name}{custom_str}"
return figure_config
@@ -248,9 +248,11 @@ def summary_table_tmp_rh_tab(df, value, si_ip):
)
return dash_table.DataTable(
columns=[
- {"name": i, "id": i}
- if i == ColNames.MONTH
- else {"name": f"{i} ({unit})", "id": i}
+ (
+ {"name": i, "id": i}
+ if i == ColNames.MONTH
+ else {"name": f"{i} ({unit})", "id": i}
+ )
for i in df_summary.columns
],
style_table={"overflowX": "auto"},
diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py
index 67f21714..bf1bc88f 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -1,5 +1,3 @@
-import math
-
import dash
from dash import dcc, html
import dash_mantine_components as dmc
diff --git a/pages/psy-chart.py b/pages/psy-chart.py
index 33af12c6..80b512be 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -12,7 +12,6 @@
from config import PageUrls, DocLinks, PageInfo, UnitSystem
from pages.lib.utils import get_max_min_value
-from pages.lib.extract_df import convert_SI_to_IP
from pages.lib.global_element_ids import ElementIds
from pages.lib.global_column_names import ColNames
from pages.lib.global_id_buttons import IdButtons
From 7e4b4978b7090284176371981c8819777b3673c4 Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Wed, 10 Sep 2025 14:46:13 +1000
Subject: [PATCH 03/23] fix: fixed the comment
---
assets/tabs.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/tabs.css b/assets/tabs.css
index 7ba0edbc..4e980f2e 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -293,7 +293,7 @@ p {
border-right: 1px solid #e9ecef;
}
-/* 强制SegmentedControl适应容器宽度 */
+/* SegmentedControl adapt to container width */
#sidebar .mantine-SegmentedControl-root {
width: 100% !important;
}
From 9ab10dfcce16d332bc51df4e4edfce22156f660a Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Wed, 10 Sep 2025 14:56:05 +1000
Subject: [PATCH 04/23] fix: re-formatted the file via using ruff
---
pages/lib/utils.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index 6ef56cd2..4014db9a 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -41,9 +41,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None):
)
figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name
else:
- figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][
- ColNames.FILE_NAME
- ] = f"{tab_name}{custom_str}"
+ figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = (
+ f"{tab_name}{custom_str}"
+ )
return figure_config
From dd356633f3a2d7b848e76eb22106008c6f0819e3 Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Fri, 12 Sep 2025 18:11:05 +1000
Subject: [PATCH 05/23] refactor: remove unnecessary CSS/HTML - used
Dash-Mantine components styles API - formatted the code via using ruff and
black
---
assets/tabs.css | 115 +--
pages/explorer.py | 1080 +++++++++++++++++------------
pages/lib/layout.py | 329 +++++----
pages/lib/template_graphs.py | 4 +-
pages/lib/utils.py | 113 +--
pages/natural_ventilation.py | 407 +++++------
pages/not_found_404.py | 2 +-
pages/outdoor.py | 315 +++++----
pages/psy-chart.py | 381 +++++-----
pages/select.py | 95 +--
pages/summary.py | 326 ++++-----
pages/sun.py | 113 ++-
pages/t_rh.py | 86 ++-
pages/wind.py | 504 +++++++-------
tests/node/cypress/e2e/spec.cy.js | 2 +-
15 files changed, 2032 insertions(+), 1840 deletions(-)
diff --git a/assets/tabs.css b/assets/tabs.css
index 4e980f2e..10a44d92 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -287,12 +287,6 @@ p {
z-index: 1000;
}
-/* side bar */
-#sidebar {
- background-color: #f8f9fa;
- border-right: 1px solid #e9ecef;
-}
-
/* SegmentedControl adapt to container width */
#sidebar .mantine-SegmentedControl-root {
width: 100% !important;
@@ -303,116 +297,9 @@ p {
min-width: 0 !important;
}
-/* burger button */
-#burger-button {
- transition: all 0.2s ease;
-}
-
-#burger-button:hover {
- transform: scale(1.05);
-}
-
-.nav-link {
- border-radius: 6px;
- transition: all 0.2s ease;
-}
-
-.nav-link:hover {
- background-color: #e3f2fd;
-}
-
-/* Active navigation link styles */
-.nav-link[data-active="true"] {
- background-color: #1976d2 !important;
- color: white !important;
- font-weight: 600;
-}
-
-.nav-link[data-active="true"]:hover {
- background-color: #1565c0 !important;
- color: white !important;
-}
-
-/* response design */
+/* Response design */
@media (max-width: 768px) {
#sidebar {
width: 280px !important;
}
-}
-
-/* burger button style */
-#burger-button {
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
-}
-
-/* Alert style */
-.survey-alert {
- position: fixed;
- top: 25px;
- right: 10px;
- width: 400px;
-}
-
-/* Footer style */
-#footer-container {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: nowrap;
- width: 100%;
- min-height: auto;
- padding: 10px 0;
-}
-
-/* Footer Logo style */
-.footer-logo-section {
- flex: 0 0 33.333333%;
- max-width: 33.333333%;
- padding: 10px 15px 10px 25px;
- display: flex;
- align-items: center;
- justify-content: flex-start;
-}
-
-/* Footer style */
-.footer-content-section {
- flex: 0 0 66.666667%;
- max-width: 66.666667%;
- padding: 10px 15px;
- display: flex;
- align-items: center;
- justify-content: flex-start;
-}
-
-/* Footer text style */
-.footer-text-content {
- margin-top: 1rem;
-}
-
-/* Footer Markdown style */
-.footer-markdown-text {
- font-size: 16px;
- line-height: 1.5;
- font-weight: 500;
-}
-
-/* Footer link style */
-.footer-links-group {
- margin-top: 1rem;
-}
-
-.footer-link {
- text-decoration: underline;
- font-size: 16px;
- font-weight: 500;
-}
-
-/* Banner style */
-#banner-title {
- line-height: 1.1;
-}
-
-/* Documentation style */
-#nav-doc-link {
- margin-top: 5px;
}
\ No newline at end of file
diff --git a/pages/explorer.py b/pages/explorer.py
index 8c27421d..68898ee3 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -1,7 +1,6 @@
import dash
-from dash import dcc, html
+from dash import dcc
import dash_mantine_components as dmc
-import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
from dash.exceptions import PreventUpdate
@@ -23,8 +22,6 @@
sun_cloud_tab_dropdown_names,
more_variables_dropdown,
sun_cloud_tab_explore_dropdown_names,
- container_row_center_full,
- container_col_center_one_of_three,
)
from pages.lib.template_graphs import (
heatmap,
@@ -65,10 +62,14 @@
def section_one_inputs():
"""Return the inputs from section one."""
- return dmc.Box(
- className="container-row full-width row-center",
+ return dmc.Group(
+ align="center",
+ justify="center",
+ gap="sm",
+ wrap=False,
+ w="100%",
children=[
- html.H4(className="text-next-to-input", children=["Select a variable: "]),
+ dmc.Title("Select a variable:", order=4),
dropdown(
id=ElementIds.SEC1_VAR_DROPDOWN,
options=explore_dropdown_names,
@@ -80,131 +81,157 @@ def section_one_inputs():
def section_one():
"""Return the graphs for section one"""
- return dmc.Box(
- className="container-col full-width",
+ return dmc.Stack(
+ w="100%",
+ gap="md",
children=[
section_one_inputs(),
- dmc.Box(
- children=title_with_link(
- text="Yearly chart",
- id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
+ # Yearly chart
+ title_with_link(
+ text="Yearly chart",
+ id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.YEARLY_EXPLORE, className="full-width"),
- ),
- dmc.Box(
- children=title_with_link(
- text="Daily chart",
- id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ children=dmc.Paper(
+ id=ElementIds.YEARLY_EXPLORE,
+ p="sm",
+ radius="md",
+ w="100%",
),
),
+ # Daily chart
+ title_with_link(
+ text="Daily chart",
+ id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
dcc.Loading(
- dmc.Box(className="full-width", id=ElementIds.QUERY_DAILY),
type="circle",
- ),
- dmc.Box(
- children=title_with_link(
- text="Heatmap chart",
- id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ children=dmc.Paper(
+ id=ElementIds.QUERY_DAILY,
+ p="sm",
+ radius="md",
+ w="100%",
),
),
+ # Heatmap chart
+ title_with_link(
+ text="Heatmap chart",
+ id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
dcc.Loading(
- dmc.Box(className="full-width", id=ElementIds.QUERY_HEATMAP),
type="circle",
- ),
- dmc.Box(
- children=title_with_tooltip(
- text="Descriptive statistics",
- tooltip_text="count, mean, std, min, max, and percentiles",
- id_button=IdButtons.TABLE_EXPLORE,
+ children=dmc.Paper(
+ id=ElementIds.QUERY_HEATMAP,
+ p="sm",
+ radius="md",
+ w="100%",
),
),
- dmc.Box(
- className="container-row justify-content-center",
- children=[
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply month and hour filter",
- color="primary",
- id=ElementIds.SEC1_TIME_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className=(
- "container-row full-width justify-center mt-2"
+ title_with_tooltip(
+ text="Descriptive statistics",
+ tooltip_text="count, mean, std, min, max, and percentiles",
+ id_button=IdButtons.TABLE_EXPLORE,
+ ),
+ dmc.Center(
+ w="100%",
+ children=dmc.Stack(
+ gap="sm",
+ w="100%",
+ maw=600,
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.SEC1_TIME_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
+ ),
+ # Month Range 行(3-6-3)
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3,
+ children=dmc.Text("Month Range"),
),
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.SEC1_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- style={"flex": "50%"},
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC1_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- dcc.Checklist(
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE,
options=[
- {"label": "Invert", "value": "invert"},
+ {"label": "Invert", "value": "invert"}
],
value=[],
- id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE,
- labelStyle={"flex": "30%"},
),
- ],
- ),
- dmc.Box(
- className="container-row justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.SEC1_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
- style={"flex": "50%"},
+ ),
+ ],
+ ),
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3,
+ children=dmc.Text("Hour Range"),
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC1_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
),
- dcc.Checklist(
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE,
options=[
- {"label": "Invert", "value": "invert"},
+ {"label": "Invert", "value": "invert"}
],
value=[],
- id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE,
- labelStyle={"flex": "30%"},
),
- ],
- ),
- ],
- ),
- ],
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
+ dmc.Paper(
id=ElementIds.TABLE_DATA_EXPLORER,
+ p="sm",
+ radius="md",
+ w="100%",
),
],
)
@@ -212,170 +239,221 @@ def section_one():
def section_two_inputs():
"""Return all the input forms from section two."""
- return dmc.Box(
+ return dmc.Stack(
+ w="100%",
+ gap="md",
children=[
- dmc.Box(
- children=title_with_tooltip(
- text="Customizable heatmap",
- tooltip_text=None,
- id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL,
- ),
+ # 标题(保留你已有的封装)
+ title_with_tooltip(
+ text="Customizable heatmap",
+ tooltip_text=None,
+ id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL,
),
- dmc.Box(
- className="container-row full-width three-inputs-container",
+ # 三列区域:①变量选择 ②时间过滤(月份/小时)③数据过滤(变量/最小/最大)
+ dmc.Grid(
+ gutter="md",
children=[
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Variable:"],
- style={"flex": "30%"},
- ),
- dropdown(
- id=ElementIds.SEC2_VAR_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- style={"flex": "70%"},
- ),
- ],
- ),
- ],
+ # ① 变量选择列
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4,
+ children=dmc.Text("Variable:"),
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.SEC2_VAR_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply month and hour filter",
- color="primary",
- id=ElementIds.SEC2_TIME_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className=(
- "container-row full-width justify-center mt-2"
+ # ② 时间过滤列(按钮 + 月份范围 + 小时范围)
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.SEC2_TIME_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
),
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.SEC2_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- dmc.Box(
- className="container-row justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.SEC2_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- ],
+ # Month Range:3/6/3
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3,
+ children=dmc.Text("Month Range"),
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC2_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
+ options=[
+ {
+ "label": "Invert",
+ "value": "invert",
+ }
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ # Hour Range:3/6/3
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3,
+ children=dmc.Text("Hour Range"),
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC2_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
+ options=[
+ {
+ "label": "Invert",
+ "value": "invert",
+ }
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.SEC2_DATA_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Filter Variable:"],
- style={"flex": "30%"},
- ),
- dropdown(
- id=ElementIds.SEC2_DATA_FILTER_VAR,
- options=explore_dropdown_names,
- value=ColNames.RH,
- style={"flex": "70%"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Min Value:"], style={"flex": "30%"}
- ),
- dbc.Input(
- id=ElementIds.SEC2_MIN_VAL,
- placeholder="Enter a number for the min val",
- type="number",
- value=0,
- step=1,
- style={"flex": "70%"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Max Value:"], style={"flex": "30%"}
- ),
- dbc.Input(
- id=ElementIds.SEC2_MAX_VAL,
- placeholder="Enter a number for the max val",
- type="number",
- value=100,
- step=1,
- style={"flex": "70%"},
- ),
- ],
- ),
- ],
+ # ③ 数据过滤列(按钮 + 过滤变量 + 最小/最大值)
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.SEC2_DATA_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
+ ),
+ # Filter Variable
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4,
+ children=dmc.Text("Filter Variable:"),
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.SEC2_DATA_FILTER_VAR,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ ),
+ ],
+ ),
+ # Min Value
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4,
+ children=dmc.Text("Min Value:"),
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dmc.NumberInput(
+ id=ElementIds.SEC2_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
+ w="100%",
+ ),
+ ),
+ ],
+ ),
+ # Max Value
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4,
+ children=dmc.Text("Max Value:"),
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dmc.NumberInput(
+ id=ElementIds.SEC2_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
+ w="100%",
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
),
@@ -385,31 +463,49 @@ def section_two_inputs():
def section_two():
"""Return the two graphs in section two."""
- return dmc.Box(
+ return dmc.Stack(
id=ElementIds.TAB6_SEC2_CONTAINER,
- className="container-col justify-center full-width",
+ w="100%",
+ gap="md",
+ align="center",
children=[
+ # 输入表单
section_two_inputs(),
+ # 自定义热力图
dcc.Loading(
type="circle",
- children=dmc.Box(className="full-width", id=ElementIds.CUSTOM_HEATMAP),
+ children=dmc.Paper(
+ id=ElementIds.CUSTOM_HEATMAP,
+ radius="md",
+ p="sm",
+ w="100%",
+ ),
),
- dbc.Checklist(
- options=[
- {"label": "Normalize", "value": "normal"},
+ # Normalize 复选框
+ dmc.Group(
+ gap="sm",
+ children=[
+ dmc.CheckboxGroup(
+ id=ElementIds.NORMALIZE,
+ value=[],
+ children=[
+ dmc.Checkbox(label="Normalize", value="normal"),
+ ],
+ ),
],
- value=[],
- id=ElementIds.NORMALIZE,
),
+ # Summary 图表
dcc.Loading(
type="circle",
- children=[
- dcc.Graph(
- className="full-width",
+ children=dmc.Paper(
+ radius="md",
+ p="sm",
+ w="100%",
+ children=dcc.Graph(
id=ElementIds.CUSTOM_SUMMARY,
config=fig_config,
),
- ],
+ ),
),
],
)
@@ -417,175 +513,246 @@ def section_two():
def section_three_inputs():
""""""
- return dmc.Box(
- className="container-row full-width three-inputs-container",
+ return dmc.Stack(
+ w="100%",
+ gap="md",
children=[
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(style={"flex": "30%"}, children=["X Variable:"]),
- dropdown(
- id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN,
- options=explore_dropdown_names,
- value="DBT",
- style={"flex": "70%"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(style={"flex": "30%"}, children=["Y Variable:"]),
- dropdown(
- id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- style={"flex": "70%"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(style={"flex": "30%"}, children=["Color By:"]),
- dropdown(
- id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN,
- options=explore_dropdown_names,
- value="glob_hor_rad",
- style={"flex": "70%"},
- ),
- ],
- ),
- ],
- ),
- dmc.Box(
- className=container_col_center_one_of_three,
+ dmc.Grid(
+ gutter="md",
children=[
- dbc.Button(
- "Apply month and hour filter",
- color="primary",
- id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className="container-row full-width justify-center",
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ # ① X/Y/Color By 列
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ # X Variable
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("X Variable:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN,
+ options=explore_dropdown_names,
+ value="DBT",
+ ),
+ ),
+ ],
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- dmc.Box(
- className="container-row full-width justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ # Y Variable
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Y Variable:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ ),
+ ],
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- ],
- ),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Filter Variable:"], style={"flex": "30%"}
- ),
- dropdown(
- id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- style={"flex": "70%"},
- ),
- ],
+ # Color By
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Color By:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN,
+ options=explore_dropdown_names,
+ value="glob_hor_rad",
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(children=["Min Value:"], style={"flex": "30%"}),
- dbc.Input(
- className="num-input",
- id=ElementIds.TAB6_SEC3_MIN_VAL,
- placeholder="Enter a number for the min val",
- type="number",
- step=1,
- value=0,
- style={"flex": "70%"},
- ),
- ],
+ # ② 时间过滤(月/小时)
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
+ ),
+ # Month Range:3/6/3
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3, children=dmc.Text("Month Range")
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
+ options=[
+ {
+ "label": "Invert",
+ "value": "invert",
+ }
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ # Hour Range:3/6/3
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3, children=dmc.Text("Hour Range")
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
+ options=[
+ {
+ "label": "Invert",
+ "value": "invert",
+ }
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(children=["Max Value:"], style={"flex": "30%"}),
- dbc.Input(
- className="num-input",
- id=ElementIds.TAB6_SEC3_MAX_VAL,
- placeholder="Enter a number for the max val",
- type="number",
- value=100,
- step=1,
- style={"flex": "70%"},
- ),
- ],
+ # ③ 数据过滤(变量/最小/最大)
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
+ ),
+ # Filter Variable
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4,
+ children=dmc.Text("Filter Variable:"),
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ ),
+ ],
+ ),
+ # Min Value
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Min Value:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dmc.NumberInput(
+ id=ElementIds.TAB6_SEC3_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
+ w="100%",
+ ),
+ ),
+ ],
+ ),
+ # Max Value
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Max Value:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dmc.NumberInput(
+ id=ElementIds.TAB6_SEC3_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
+ w="100%",
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
),
@@ -595,24 +762,37 @@ def section_three_inputs():
def section_three():
"""Return the two graphs in section three."""
- return dmc.Box(
- className="container-col full-width",
+ return dmc.Stack(
+ w="100%",
+ gap="md",
children=[
- dmc.Box(
- children=title_with_tooltip(
- text="More charts",
- tooltip_text=None,
- id_button=IdButtons.MORE_CHARTS_LABEL,
- ),
+ # 标题(保留你现有的封装)
+ title_with_tooltip(
+ text="More charts",
+ tooltip_text=None,
+ id_button=IdButtons.MORE_CHARTS_LABEL,
),
+ # 输入区
section_three_inputs(),
+ # 图 1:THREE_VAR
dcc.Loading(
- dmc.Box(id=ElementIds.THREE_VAR),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.THREE_VAR,
+ radius="md",
+ p="sm",
+ w="100%",
+ ),
),
+ # 图 2:TWO_VAR
dcc.Loading(
- dmc.Box(id=ElementIds.TWO_VAR),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.TWO_VAR,
+ radius="md",
+ p="sm",
+ w="100%",
+ ),
),
],
)
@@ -644,7 +824,7 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip):
"""Update the contents of tab size. Passing in the info from the dropdown and the general info."""
if df[var].mean() == 99990.0:
- return dbc.Alert(
+ return dmc.Alert(
"""The selected variable is not available,
the Clima tool could not generate the yearly plot""",
color="warning",
@@ -782,7 +962,7 @@ def update_heatmap(
if not heat_map:
return (
- dbc.Alert(
+ dmc.Alert(
"No data is available in this location under these conditions. Please "
"either change the month and hour filters, or select a wider range for "
"the filter variable",
@@ -903,7 +1083,7 @@ def update_more_charts(
if not three:
custom_inputs = f"{var_x}-{var_y}"
units = generate_units(si_ip)
- return dbc.Alert(
+ return dmc.Alert(
"No data is available in this location under these conditions. Please "
"either change the month and hour filters, or select a wider range for "
"the filter variable",
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index c1fec70f..a5c61791 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -1,4 +1,3 @@
-import dash_bootstrap_components as dbc
import dash
from dash import dcc, Input, Output, State, callback
import dash_mantine_components as dmc
@@ -6,6 +5,7 @@
from pages.lib.global_column_names import ColNames
from config import DocLinks, UnitSystem
from pages.lib.global_element_ids import ElementIds
+from pages.lib.page_icon import PageIcon
def burger_button():
@@ -24,30 +24,34 @@ def alert():
return dmc.Stack(
gap=0,
children=[
- dbc.Toast(
+ dmc.Alert(
[
"If you have a moment, help us improve Clima and take a ",
dmc.Anchor(
"quick user survey",
href="https://forms.gle/k289zP3R92jdu14M7",
target="_blank",
- className="alert-link",
+ c="white",
+ underline=True,
),
"! ☀️",
],
id=ElementIds.ID_LAYOUT_ALERT_AUTO,
- header="CBE Clima User Survey",
- icon="info",
- is_open=False,
- dismissable=True,
- className="survey-alert",
+ title="CBE Clima User Survey",
+ icon="Info",
+ color="blue",
+ variant="filled",
+ withCloseButton=True,
+ pos="fixed",
+ top="25px",
+ right="10px",
+ w="400px",
+ style={"zIndex": 1002, "display": "none"},
),
- dmc.Box(
- children=dcc.Interval(
- id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
- interval=12 * 1000,
- n_intervals=0,
- )
+ dcc.Interval(
+ id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
+ interval=12 * 500,
+ n_intervals=0,
),
],
)
@@ -55,9 +59,50 @@ def alert():
def footer():
"""Build the footer at the bottom of the page."""
+ white_anchor_style = {
+ "underline": True,
+ "c": "white",
+ "fz": "md",
+ "fw": 500,
+ "target": "_blank",
+ }
+
+ footer_links = [
+ (
+ "Version: 0.9.0",
+ "https://center-for-the-built-environment.gitbook.io/clima/version/changelog",
+ ),
+ ("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"),
+ (
+ "Report issues on GitHub",
+ "https://github.com/CenterForTheBuiltEnvironment/clima/issues",
+ ),
+ (
+ "Contact us",
+ "https://github.com/CenterForTheBuiltEnvironment/clima/discussions",
+ ),
+ ("Documentation", "https://center-for-the-built-environment.gitbook.io/clima/"),
+ (
+ "License",
+ "https://center-for-the-built-environment.gitbook.io/clima/#license",
+ ),
+ ]
+
return dmc.Box(
id=ElementIds.FOOTER_CONTAINER,
+ p="md",
+ m=0,
+ c="white",
+ bg="#003262",
+ display="flex",
+ w="100%",
+ style={
+ "flexWrap": "nowrap",
+ "minHeight": "fit-content",
+ "alignItems": "flex-start",
+ },
children=[
+ # Logo section
dmc.Box(
children=[
dmc.Anchor(
@@ -71,8 +116,13 @@ def footer():
),
),
],
- className="footer-logo-section",
+ flex="0 0 33.333333%",
+ maw="33.333333%",
+ p="30px 15px 10px 25px",
+ display="flex",
+ style={"justifyContent": "flex-start", "alignItems": "flex-start"},
),
+ # Content section
dmc.Box(
children=[
dmc.Stack(
@@ -85,67 +135,30 @@ def footer():
A free and open-source web application for climate analysis tailored to sustainable building design.
Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5).
""",
- className="footer-markdown-text",
+ style={
+ "fontSize": "16px",
+ "lineHeight": 1.5,
+ "fontWeight": 500,
+ "color": "white",
+ },
),
dmc.Group(
[
- dmc.Anchor(
- "Version: 0.9.0",
- href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog",
- underline=True,
- c="white",
- target="_blank",
- className="footer-link",
- ),
- dmc.Anchor(
- "Contributors",
- href="https://cbe-berkeley.gitbook.io/clima/#contributions",
- underline=True,
- c="white",
- target="_blank",
- className="footer-link",
- ),
- dmc.Anchor(
- "Report issues on GitHub",
- href="https://github.com/CenterForTheBuiltEnvironment/clima/issues",
- underline=True,
- c="white",
- target="_blank",
- className="footer-link",
- ),
- dmc.Anchor(
- "Contact us",
- href="https://github.com/CenterForTheBuiltEnvironment/clima/discussions",
- underline=True,
- c="white",
- target="_blank",
- className="footer-link",
- ),
- dmc.Anchor(
- "Documentation",
- href="https://center-for-the-built-environment.gitbook.io/clima/",
- underline=True,
- c="white",
- target="_blank",
- className="footer-link",
- ),
- dmc.Anchor(
- "License",
- href="https://center-for-the-built-environment.gitbook.io/clima/#license",
- underline=True,
- c="white",
- target="_blank",
- className="footer-link",
- ),
+ dmc.Anchor(text, href=url, **white_anchor_style)
+ for text, url in footer_links
],
gap="sm",
- className="footer-links-group",
+ mt="md",
),
],
- className="footer-text-content",
+ mt="md",
),
],
- className="footer-content-section",
+ flex="0 0 66.666667%",
+ maw="66.666667%",
+ p="0px 15px 10px 15px",
+ display="flex",
+ style={"justifyContent": "flex-start", "alignItems": "flex-start"},
),
],
)
@@ -155,6 +168,11 @@ def banner():
"""Top banner rewritten with dash-mantine-components only."""
return dmc.Box(
id=ElementIds.BANNER,
+ p="md",
+ bg="#003262",
+ c="white",
+ pos="relative",
+ style={"zIndex": 1},
children=[
dmc.Group(
justify="space-between",
@@ -176,12 +194,21 @@ def banner():
"CBE Clima Tool",
id=ElementIds.BANNER_TITLE,
order=2,
+ fw=500,
+ ff="'Open Sans', sans-serif",
+ lh=1.1,
+ c="white",
),
dmc.Text(
"Current Location: N/A",
id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
size="sm",
opacity=0.85,
+ ff="'Poppins', sans-serif",
+ fw=400,
+ h=25,
+ style={"overflow": "hidden"},
+ c="white",
),
],
),
@@ -194,7 +221,7 @@ def banner():
def sidebar():
- """create side bar"""
+ """create sidebar"""
return dmc.Drawer(
id=ElementIds.SIDE_BAR,
title=dmc.Group(
@@ -203,50 +230,87 @@ def sidebar():
dmc.Text("CBE Clima Tool", fw=600),
]
),
- padding="md",
size="300px",
- zIndex=999,
+ zIndex=1001,
opened=False,
- className="custom-sidebar",
styles={
- "title": {"paddingRight": 30},
+ "content": {
+ "top": "80px",
+ "left": 0,
+ "position": "fixed",
+ "borderRadius": "0 8px 8px 0",
+ "boxShadow": "2px 0 8px rgba(0,0,0,0.1)",
+ "backgroundColor": "#f8f9fa",
+ "padding": "16px",
+ },
+ "overlay": {
+ "top": "80px",
+ "left": 0,
+ "height": "calc(100vh - 80px)",
+ "position": "fixed",
+ },
+ "header": {
+ "borderBottom": "1px solid #e9ecef",
+ "paddingBottom": "12px",
+ "marginBottom": "16px",
+ "position": "sticky",
+ "top": 0,
+ "backgroundColor": "#f8f9fa",
+ "zIndex": 1002,
+ },
+ "title": {
+ "fontWeight": 600,
+ "fontSize": "18px",
+ "paddingRight": 30,
+ "position": "relative",
+ "zIndex": 1001,
+ },
+ "body": {
+ "padding": 0,
+ "overflowY": "auto",
+ "maxHeight": "calc(100vh - 80px)",
+ "position": "relative",
+ "zIndex": 1,
+ },
},
- children=[dmc.Stack(gap="sm", children=build_sidebar_nav_items())],
+ children=[dmc.Stack(gap=0, children=build_sidebar_nav_items())],
)
-# Pages Icon
-PAGE_ICON_MAP = {
- "Select Weather File": "tabler:upload",
- "Climate Summary": "tabler:chart-bar",
- "Temperature and Humidity": "tabler:temperature",
- "Sun and Clouds": "tabler:sun",
- "Wind": "tabler:wind",
- "Psychrometric Chart": "tabler:chart-dots",
- "Natural Ventilation": "tabler:windmill",
- "Outdoor Comfort": "tabler:thermometer",
- "Data Explorer": "tabler:database",
- "Changelog": "tabler:history",
-}
-
-
def build_sidebar_nav_items():
+ nav_link_styles = {
+ "root": {
+ "borderRadius": "6px",
+ "transition": "all 0.2s ease",
+ "&:hover": {"backgroundColor": "#e3f2fd"},
+ "&[data-active='true']": {
+ "backgroundColor": "#1976d2",
+ "color": "white",
+ "fontWeight": 600,
+ },
+ "&[data-active='true']:hover": {
+ "backgroundColor": "#1565c0",
+ "color": "white",
+ },
+ }
+ }
+
# === Secondary Menu ===
- sub_links = []
- for page in dash.page_registry.values():
- if page[ColNames.NAME] in ["404"]:
- continue
- icon = PAGE_ICON_MAP.get(page[ColNames.NAME], "tabler:circle")
- sub_links.append(
- dmc.NavLink(
- label=page[ColNames.NAME],
- leftSection=DashIconify(icon=icon, width=20),
- href=page[ColNames.PATH],
- id=f"nav-{page[ColNames.PATH].replace('/', '')}",
- active=False,
- style={"marginBottom": "4px"},
- )
+ sub_links = [
+ dmc.NavLink(
+ label=page[ColNames.NAME],
+ leftSection=DashIconify(
+ icon=PageIcon.get_icon(page[ColNames.NAME]), width=20
+ ),
+ href=page[ColNames.PATH],
+ id=f"nav-{page[ColNames.PATH].replace('/', '')}",
+ active=False,
+ mb="xs",
+ styles=nav_link_styles,
)
+ for page in dash.page_registry.values()
+ if page[ColNames.NAME] not in ["404"]
+ ]
# Primary Menu
parent_group = dmc.NavLink(
@@ -258,6 +322,11 @@ def build_sidebar_nav_items():
childrenOffset=18,
)
+ segmented_control_styles = {
+ "root": {"width": "100%"},
+ "control": {"flex": 1, "minWidth": 0},
+ }
+
controls_stack = dmc.Stack(
gap="sm",
px=0,
@@ -272,6 +341,8 @@ def build_sidebar_nav_items():
],
radius="md",
size="sm",
+ w="100%",
+ styles=segmented_control_styles,
),
dmc.SegmentedControl(
id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT,
@@ -282,6 +353,8 @@ def build_sidebar_nav_items():
],
radius="md",
size="sm",
+ w="100%",
+ styles=segmented_control_styles,
),
],
)
@@ -324,18 +397,16 @@ def store():
def build_tabs():
return dmc.Box(
id=ElementIds.TABS_CONTAINER,
+ m=0,
+ mt=0,
children=[
+ store(),
dmc.Box(
- id=ElementIds.STORE_CONTAINER,
+ id=ElementIds.TABS_CONTENT,
+ p="md",
children=[
- store(),
- dmc.Box(
- id=ElementIds.TABS_CONTENT,
- children=[
- alert(),
- dash.page_container,
- ],
- ),
+ alert(),
+ dash.page_container,
],
),
],
@@ -349,9 +420,7 @@ def build_tabs():
prevent_initial_call=True,
)
def toggle_sidebar(n_clicks, opened):
- if n_clicks:
- return not opened
- return opened
+ return not opened if n_clicks else opened
@callback(
@@ -375,14 +444,30 @@ def close_sidebar_on_navigation(pathname):
)
def update_nav_active_state(pathname):
"""Update active state of navigation links based on current URL pathname"""
- active_states = []
+ return [
+ pathname == page[ColNames.PATH]
+ for page in dash.page_registry.values()
+ if page[ColNames.NAME] not in ["404"]
+ ]
- for page in dash.page_registry.values():
- if page[ColNames.NAME] in ["404"]:
- continue
- # Check if current pathname matches this page's path
- is_active = pathname == page[ColNames.PATH]
- active_states.append(is_active)
+@callback(
+ Output(ElementIds.ID_LAYOUT_ALERT_AUTO, "style"),
+ Input(ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, "n_intervals"),
+ prevent_initial_call=True,
+)
+def show_alert_after_delay(n_intervals):
+ """Show alert after 6 seconds, then hide after 5 more seconds"""
+ base_style = {
+ "position": "fixed",
+ "top": "25px",
+ "right": "10px",
+ "width": "400px",
+ "zIndex": 1002,
+ }
- return active_states
+ # Determine display status based on the number of intervals
+ if n_intervals == 1:
+ return {**base_style, "display": "block"}
+ else:
+ return {**base_style, "display": "none"}
diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py
index f8e36418..b62f199b 100644
--- a/pages/lib/template_graphs.py
+++ b/pages/lib/template_graphs.py
@@ -642,7 +642,7 @@ def thermal_stress_stacked_barchart(
style={"text-align": "center", "marginTop": "2rem"},
),
)
- isNormalized = True if len(normalize) != 0 else False
+ isNormalized = True if normalize else False
if isNormalized:
new_df = (
df.groupby(ColNames.MONTH)[var]
@@ -674,7 +674,7 @@ def thermal_stress_stacked_barchart(
"
Month: %{x}
Category: "
+ categories[i]
+ "
Count: %{y}
"
- if len(normalize) == 0
+ if not normalize
else "
Month: %{x}
Category: "
+ categories[i]
+ "
Proportion: %{y:.1f}%
"
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index 4014db9a..dc20810a 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -3,9 +3,8 @@
import time
import math
-import dash_bootstrap_components as dbc
import pandas as pd
-from dash import html, dash_table, dcc
+from dash import dash_table, dcc
import dash_mantine_components as dmc
from config import UnitSystem
@@ -148,38 +147,44 @@ def generate_custom_inputs_psy(
def title_with_tooltip(text, tooltip_text, id_button):
- display_tooltip = "none"
if tooltip_text:
- display_tooltip = "block"
-
- return dmc.Box(
- className="container-row",
- style={"padding": "1rem", "marginTop": "1rem"},
- children=[
- html.H5(text, style={"marginRight": "0.5rem"}),
- dmc.Box(
- [
- html.Sup(
- html.Img(
+ return dmc.Group(
+ gap="xs",
+ align="center",
+ mt="md",
+ px="md",
+ children=[
+ dmc.Title(text, order=3),
+ dmc.Tooltip(
+ label=tooltip_text,
+ position="right",
+ withArrow=True,
+ multiline=True,
+ w=220,
+ children=dmc.ActionIcon(
+ dmc.Image(
id=id_button,
- src="../assets/icons/help.png",
+ src="/assets/icons/help.png",
alt="help",
- style={
- "width": "1rem",
- "height": "1rem",
- },
+ w=16,
+ h=16,
),
+ variant="transparent",
+ size="sm",
),
- dbc.Tooltip(
- tooltip_text,
- target=id_button,
- placement="right",
- ),
- ],
- style={"display": display_tooltip},
- ),
- ],
- )
+ ),
+ ],
+ )
+ else:
+ return dmc.Group(
+ gap="xs",
+ align="center",
+ mt="md",
+ px="md",
+ children=[
+ dmc.Title(text, order=3),
+ ],
+ )
def title_with_link(
@@ -188,34 +193,34 @@ def title_with_link(
id_button=None,
doc_link: str = "",
):
- return dmc.Box(
- className="container-row",
- style={"padding": "1rem", "marginTop": "1rem"},
+ return dmc.Group(
+ gap="xs",
+ align="center",
+ mt="md",
+ px="md",
children=[
- html.H5(text, style={"marginRight": "0.5rem"}),
- dmc.Box(
- [
- html.Sup(
- html.A(
- html.Img(
- id=id_button,
- src="../assets/icons/book.png",
- alt="book",
- style={
- "width": "1rem",
- "height": "1rem",
- },
- ),
- href=doc_link,
- target="_blank",
+ dmc.Title(text, order=3),
+ dmc.Tooltip(
+ label=tooltip_text,
+ position="right",
+ withArrow=True,
+ multiline=True,
+ w=220,
+ children=dmc.Anchor(
+ dmc.ActionIcon(
+ dmc.Image(
+ id=id_button,
+ src="/assets/icons/book.png",
+ alt="book",
+ w=16,
+ h=16,
),
+ variant="transparent",
+ size="sm",
),
- dbc.Tooltip(
- tooltip_text,
- target=id_button,
- placement="right",
- ),
- ],
+ href=doc_link,
+ target="_blank",
+ ),
),
],
)
diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py
index bf1bc88f..ea6835fd 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -1,5 +1,5 @@
import dash
-from dash import dcc, html
+from dash import dcc
import dash_mantine_components as dmc
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
@@ -13,8 +13,6 @@
mapping_dictionary,
tight_margins,
month_lst,
- container_row_center_full,
- container_col_center_one_of_three,
)
from pages.lib.utils import get_max_min_value
from pages.lib.template_graphs import filter_df_by_month_and_hour
@@ -42,13 +40,7 @@
def layout():
- return dmc.Box(
- className="container-col",
- id=ElementIds.MAIN_NV_SECTION,
- children=[
- #
- ],
- )
+ return dmc.Stack(id=ElementIds.MAIN_NV_SECTION, gap="md")
@callback(
@@ -66,220 +58,239 @@ def update_layout(si_ip):
dpt_set = 16
return [
- dmc.Box(
- children=title_with_link(
- text="Natural Ventilation Potential",
- id_button=IdButtons.NATURAL_VENTILATION_LABEL,
- doc_link=DocLinks.NATURAL_VENTILATION,
- ),
+ title_with_link(
+ text="Natural Ventilation Potential",
+ id_button=IdButtons.NATURAL_VENTILATION_LABEL,
+ doc_link=DocLinks.NATURAL_VENTILATION,
),
inputs_tab(tdb_set_min, tdb_set_max, dpt_set),
dcc.Loading(
- dmc.Box(
+ type="circle",
+ children=dmc.Paper(
id=ElementIds.NV_HEATMAP_CHART,
- style={"marginTop": "1rem"},
+ p="md",
+ mt="md",
),
- type="circle",
),
- dmc.Box(
- className="container-row align-center justify-center",
+ dmc.Group(
+ align="center",
+ justify="center",
+ gap="sm",
children=[
- dbc.Checklist(
- options=[
- {"label": "", "value": 1},
- ],
- value=[1],
+ dmc.Switch(
id=ElementIds.SWITCHES_INPUT,
- switch=True,
- style={
- "padding": "1rem",
- "marginTop": "1rem",
- "marginRight": "-2rem",
- },
+ label="",
+ checked=True,
+ size="md",
+ color="blue",
+ style={"padding": "1rem", "marginRight": "-2rem"},
),
- dmc.Box(
- children=title_with_tooltip(
- text="Normalize data",
- tooltip_text=(
- "If normalized is enabled it calculates the % "
- "time otherwise it calculates the total number of hours"
- ),
- id_button=IdButtons.NV_NORMALIZE,
+ title_with_tooltip(
+ text="Normalize data",
+ tooltip_text=(
+ "If normalized is enabled it calculates the % "
+ "time otherwise it calculates the total number of hours"
),
+ id_button=IdButtons.NV_NORMALIZE,
),
],
),
dcc.Loading(
- dmc.Box(
+ type="circle",
+ children=dmc.Paper(
id=ElementIds.NV_BAR_CHART,
- style={"marginTop": "1rem"},
+ p="md",
+ mt="md",
),
- type="circle",
),
]
def inputs_tab(t_min, t_max, d_set):
- return dmc.Box(
- className="container-row full-width three-inputs-container",
+ return dmc.Grid(
+ gutter="xl",
children=[
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.NV_DBT_FILTER,
- className="mb-2",
- n_clicks=1,
- ),
- html.H6("Outdoor dry-bulb air temperature range"),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(children=["Min Value:"], style={"flex": "30%"}),
- dbc.Input(
- id=ElementIds.NV_TDB_MIN_VAL,
- placeholder="Enter a number for the min val",
- type="number",
- step=1,
- value=t_min,
- style={"flex": "70%"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(children=["Max Value:"], style={"flex": "30%"}),
- dbc.Input(
- id=ElementIds.NV_TDB_MAX_VAL,
- placeholder="Enter a number for the max val",
- type="number",
- value=t_max,
- step=1,
- style={"flex": "70%"},
- ),
- ],
- ),
- ],
- ),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply month and hour filter",
- color="primary",
- id=ElementIds.NV_MONTH_HOUR_FILTER,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className="container-row full-width justify-center mt-2",
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.NV_MONTH_SLIDER,
- min=1,
- max=12,
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ color="primary",
+ id=ElementIds.NV_DBT_FILTER,
+ variant="link",
+ size="md",
+ fullWidth=True,
+ n_clicks=1,
+ ),
+ dmc.Text(
+ "Outdoor dry-bulb air temperature range",
+ size="md",
+ ),
+ dmc.Group(
+ gap="xl",
+ grow=True,
+ children=[
+ dmc.Text("Min Value:", size="md"),
+ dmc.NumberInput(
+ id=ElementIds.NV_TDB_MIN_VAL,
+ placeholder="Enter a number for the min val",
step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ value=t_min,
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_NV,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- dmc.Box(
- className="container-row align-center justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.NV_HOUR_SLIDER,
- min=0,
- max=24,
+ ],
+ ),
+ dmc.Group(
+ gap="xl",
+ grow=True,
+ children=[
+ dmc.Text("Max Value:", size="md"),
+ dmc.NumberInput(
+ id=ElementIds.NV_TDB_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=t_max,
step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_NV,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- ],
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.NV_DPT_FILTER,
- className="mb-2",
- n_clicks=0,
- disabled=True,
- ),
- dbc.Checklist(
- options=[
- {
- "label": (
- "Avoid condensation with radiant systems: If the"
- " outdoor dew point temperature is below the"
- " radiant system surface temperature, the data"
- " point is not plot."
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ color="primary",
+ id=ElementIds.NV_MONTH_HOUR_FILTER,
+ variant="link",
+ size="md",
+ fullWidth=True,
+ radius="sm",
+ ),
+ dmc.Grid(
+ align="center",
+ gutter="sm",
+ children=[
+ dmc.GridCol(
+ span=3,
+ children=dmc.Text("Month Range", size="md"),
),
- "value": 1,
- },
- ],
- value=[],
- id=ElementIds.ENABLE_CONDENSATION,
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Surface temperature:"],
- style={"marginRight": "1rem"},
- ),
- dbc.Input(
- id=ElementIds.NV_DPT_MAX_VAL,
- placeholder="Enter a number for the max val",
- type="number",
- value=d_set,
- step=1,
- style={"flex": "1"},
- ),
- ],
- ),
- ],
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.NV_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ options=[
+ {"label": "Invert", "value": "invert"},
+ ],
+ value=[],
+ id=ElementIds.INVERT_MONTH_NV,
+ ),
+ ),
+ ],
+ ),
+ dmc.Grid(
+ align="center",
+ gutter="sm",
+ children=[
+ dmc.GridCol(
+ span=3,
+ children=dmc.Text("Hour Range", size="md"),
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.NV_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ options=[
+ {"label": "Invert", "value": "invert"},
+ ],
+ value=[],
+ id=ElementIds.INVERT_HOUR_NV,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ color="primary",
+ id=ElementIds.NV_DPT_FILTER,
+ mb="xs",
+ variant="link",
+ size="md",
+ fullWidth=True,
+ n_clicks=0,
+ disabled=True,
+ ),
+ dcc.Checklist(
+ options=[
+ {
+ "label": (
+ "Avoid condensation with radiant systems: If the"
+ " outdoor dew point temperature is below the"
+ " radiant system surface temperature, the data"
+ " point is not plot."
+ ),
+ "value": 1,
+ },
+ ],
+ value=[],
+ id=ElementIds.ENABLE_CONDENSATION,
+ ),
+ dmc.Group(
+ align="center",
+ gap="sm",
+ grow=True,
+ children=[
+ dmc.Text("Surface temperature:", size="md"),
+ dmc.NumberInput(
+ id=ElementIds.NV_DPT_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=d_set,
+ step=1,
+ w="50%",
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
)
@@ -458,7 +469,7 @@ def nv_heatmap(
Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"),
Input(ElementIds.NV_DBT_FILTER, "n_clicks"),
Input(ElementIds.NV_DPT_FILTER, "n_clicks"),
- Input(ElementIds.SWITCHES_INPUT, "value"),
+ Input(ElementIds.SWITCHES_INPUT, "checked"),
Input(ElementIds.ENABLE_CONDENSATION, "value"),
],
[
@@ -539,7 +550,7 @@ def nv_bar_chart(
per_time_nv_allowed = np.round(100 * (n_hours_nv_allowed / tot_month_hours))
- if len(normalize) == 0:
+ if not normalize:
fig = go.Figure(
go.Bar(
x=df[ColNames.MONTH_NAMES].unique(),
diff --git a/pages/not_found_404.py b/pages/not_found_404.py
index 61e22382..9fc307e5 100644
--- a/pages/not_found_404.py
+++ b/pages/not_found_404.py
@@ -13,7 +13,7 @@
layout = [
dmc.Title("I could not find the page you are currently looking for", order=4),
- dmc.Text("Use the button below to return to the home page.", className="mb-2"),
+ dmc.Text("Use the button below to return to the home page.", mb="sm"),
Lottie(
options=dict(
loop=True,
diff --git a/pages/outdoor.py b/pages/outdoor.py
index 7e8335fc..c33c8b5c 100644
--- a/pages/outdoor.py
+++ b/pages/outdoor.py
@@ -1,7 +1,6 @@
import dash
from dash import dcc, html
import dash_mantine_components as dmc
-import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
import numpy as np
@@ -37,184 +36,220 @@
def inputs_outdoor_comfort():
- return dbc.Row(
- className="container-row full-width three-inputs-container",
+ return dmc.Grid(
+ gutter="md",
children=[
- dbc.Col(
- md=6,
- sm=12,
- children=[
- dmc.Box(
- className="container-row center-block",
- children=[
- html.H4(
- children=["Select a scenario:"],
- style={"flex": "30%"},
- ),
- dropdown(
+ dmc.GridCol(
+ span=6,
+ children=dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(span=3, children=dmc.Text("Select a scenario:")),
+ dmc.GridCol(
+ span=6,
+ children=dropdown(
id=ElementIds.TAB7_DROPDOWN,
- style={"flex": "60%"},
options=outdoor_dropdown_names,
value="utci_Sun_Wind",
+ persistence=True,
+ persistence_type="session",
),
- dmc.Box(
- id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"}
- ),
- ],
- ),
- ],
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dmc.Paper(id=ElementIds.IMAGE_SELECTION),
+ ),
+ ],
+ ),
),
- dbc.Col(
- md=6,
- sm=12,
- children=[
- dbc.Button(
- "Apply month and hour filter",
- color="primary",
- style={
- "width": "100%",
- },
- id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className="container-row full-width justify-center mt-2",
- children=[
- html.H6("Month Range", style={"flex": "5%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
+ w="100%",
+ ),
+ # Month Range
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(span=2, children=dmc.Text("Month Range")),
+ dmc.GridCol(
+ span=7,
+ children=dcc.RangeSlider(
+ id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- dmc.Box(
- className="container-row align-center justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "5%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- ],
+ ],
+ ),
+ # Hour Range
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(span=2, children=dmc.Text("Hour Range")),
+ dmc.GridCol(
+ span=7,
+ children=dcc.RangeSlider(
+ id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
)
def outdoor_comfort_chart():
- return dmc.Box(
+ return dmc.Stack(
+ w="100%",
+ gap="md",
children=[
- dmc.Box(id=ElementIds.OUTDOOR_COMFORT_OUTPUT),
- dmc.Box(
- children=title_with_link(
- text="UTCI heatmap chart",
- id_button=IdButtons.UTCI_CHARTS_LABEL,
- doc_link=DocLinks.UTCI_CHART,
- )
+ # 输出区域
+ dmc.Paper(
+ id=ElementIds.OUTDOOR_COMFORT_OUTPUT,
+ radius="md",
+ p="sm",
+ w="100%",
+ ),
+ # UTCI heatmap chart
+ title_with_link(
+ text="UTCI heatmap chart",
+ id_button=IdButtons.UTCI_CHARTS_LABEL,
+ doc_link=DocLinks.UTCI_CHART,
),
dcc.Loading(
- dmc.Box(id=ElementIds.UTCI_HEATMAP),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.UTCI_HEATMAP,
+ radius="md",
+ p="sm",
+ w="100%",
+ h=400,
+ ),
),
- dmc.Box(
- children=title_with_link(
- text="UTCI thermal stress chart",
- id_button=IdButtons.UTCI_CHARTS_LABEL,
- doc_link=DocLinks.UTCI_CHART,
- )
+ # UTCI thermal stress chart
+ title_with_link(
+ text="UTCI thermal stress chart",
+ id_button=IdButtons.UTCI_CHARTS_LABEL,
+ doc_link=DocLinks.UTCI_CHART,
),
dcc.Loading(
- dmc.Box(id=ElementIds.UTCI_CATEGORY_HEATMAP),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.UTCI_CATEGORY_HEATMAP,
+ radius="md",
+ p="sm",
+ w="100%",
+ h=400,
+ ),
),
- dmc.Box(
- className="container-row align-center justify-center",
+ # Normalize data 开关 + Tooltip
+ dmc.Group(
+ align="center",
+ justify="center",
+ gap="sm",
children=[
- dbc.Checklist(
- options=[
- {"label": "", "value": 1},
- ],
- value=[1],
+ dmc.Switch(
id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT,
- switch=True,
- style={
- "padding": "1rem",
- "marginTop": "1rem",
- "marginRight": "-2rem",
- },
+ label="",
+ checked=True,
+ size="md",
+ color="blue",
),
- dmc.Box(
- children=title_with_tooltip(
- text="Normalize data",
- tooltip_text=(
- "If normalized is enabled it calculates the % "
- "time otherwise it calculates the total number of hours"
- ),
- id_button=IdButtons.OUTDOOR_COMFORT_NORMALIZE,
+ title_with_tooltip(
+ text="Normalize data",
+ tooltip_text=(
+ "If normalized is enabled it calculates the % time "
+ "otherwise it calculates the total number of hours"
),
+ id_button=IdButtons.OUTDOOR_COMFORT_NORMALIZE,
),
],
),
+ # Summary chart
dcc.Loading(
- dmc.Box(id=ElementIds.UTCI_SUMMARY_CHART),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.UTCI_SUMMARY_CHART,
+ radius="md",
+ p="sm",
+ w="100%",
+ ),
),
],
)
def layout():
- return (
- dcc.Loading(
- type="circle",
- children=dmc.Box(
- className="container-col",
- children=[inputs_outdoor_comfort(), outdoor_comfort_chart()],
+ return dmc.Stack(
+ w="100%",
+ gap="md",
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ w="100%",
+ gap="md",
+ children=[
+ inputs_outdoor_comfort(),
+ outdoor_comfort_chart(),
+ ],
+ ),
),
- ),
+ ],
)
diff --git a/pages/psy-chart.py b/pages/psy-chart.py
index 80b512be..672151dc 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -1,7 +1,6 @@
import dash
-from dash import dcc, html
+from dash import dcc
import dash_mantine_components as dmc
-import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
from copy import deepcopy
@@ -17,8 +16,6 @@
from pages.lib.global_id_buttons import IdButtons
from pages.lib.global_tab_names import TabNames
from pages.lib.global_scheme import (
- container_row_center_full,
- container_col_center_one_of_three,
dropdown_names,
sun_cloud_tab_dropdown_names,
more_variables_dropdown,
@@ -61,154 +58,211 @@
def inputs():
""""""
- return dmc.Box(
- className="container-row full-width three-inputs-container",
+ return dmc.Stack(
+ w="100%",
+ gap="md",
children=[
- dmc.Box(
- className=container_col_center_one_of_three,
+ dmc.Grid(
+ gutter="md",
children=[
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Color By:"],
- style={"flex": "30%"},
- ),
- dropdown(
- id=ElementIds.PSY_COLOR_BY_DROPDOWN,
- options=psy_dropdown_names,
- value="Frequency",
- style={"flex": "70%"},
- persistence_type="session",
- persistence=True,
- ),
- ],
- ),
- ],
- ),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply month and hour filter",
- color="primary",
- id=ElementIds.MONTH_HOUR_FILTER,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className="container-row full-width justify-center mt-2",
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.PSY_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ # ① Color By
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Color By:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.PSY_COLOR_BY_DROPDOWN,
+ options=psy_dropdown_names,
+ value="Frequency",
+ persistence=True,
+ persistence_type="session",
+ ),
+ ),
+ ],
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_PSY,
- labelStyle={"flex": "30%"},
- ),
- ],
+ ],
+ ),
),
- dmc.Box(
- className="container-row align-center justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- dmc.Box(
- dcc.RangeSlider(
- id=ElementIds.PSY_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
+ # ② 时间过滤(月/小时)
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.MONTH_HOUR_FILTER,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
),
- style={"flex": "50%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_PSY,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- ],
- ),
- dmc.Box(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.DATA_FILTER,
- className="mb-2",
- n_clicks=0,
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- children=["Filter Variable:"], style={"flex": "30%"}
- ),
- dropdown(
- id=ElementIds.PSY_VAR_DROPDOWN,
- options=dropdown_names,
- value=ColNames.RH,
- style={"flex": "70%"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(children=["Min Value:"], style={"flex": "30%"}),
- dbc.Input(
- id=ElementIds.PSY_MIN_VAL,
- placeholder="Enter a number for the min val",
- type="number",
- step=1,
- value=0,
- style={"flex": "70%"},
- ),
- ],
+ # Month Range:3/6/3
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3, children=dmc.Text("Month Range")
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.PSY_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_PSY,
+ options=[
+ {
+ "label": "Invert",
+ "value": "invert",
+ }
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ # Hour Range:3/6/3
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=3, children=dmc.Text("Hour Range")
+ ),
+ dmc.GridCol(
+ span=6,
+ children=dcc.RangeSlider(
+ id=ElementIds.PSY_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
+ ),
+ ),
+ dmc.GridCol(
+ span=3,
+ children=dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_PSY,
+ options=[
+ {
+ "label": "Invert",
+ "value": "invert",
+ }
+ ],
+ value=[],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(children=["Max Value:"], style={"flex": "30%"}),
- dbc.Input(
- id=ElementIds.PSY_MAX_VAL,
- placeholder="Enter a number for the max val",
- type="number",
- value=100,
- step=1,
- style={"flex": "70%"},
- ),
- ],
+ # ③ 数据过滤(变量/最小/最大)
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="sm",
+ children=[
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.DATA_FILTER,
+ variant="filled",
+ color="blue",
+ size="md",
+ radius="md",
+ ),
+ # Filter Variable
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4,
+ children=dmc.Text("Filter Variable:"),
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dropdown(
+ id=ElementIds.PSY_VAR_DROPDOWN,
+ options=dropdown_names,
+ value=ColNames.RH,
+ ),
+ ),
+ ],
+ ),
+ # Min Value
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Min Value:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dmc.NumberInput(
+ id=ElementIds.PSY_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
+ w="100%",
+ ),
+ ),
+ ],
+ ),
+ # Max Value
+ dmc.Grid(
+ gutter="sm",
+ align="center",
+ children=[
+ dmc.GridCol(
+ span=4, children=dmc.Text("Max Value:")
+ ),
+ dmc.GridCol(
+ span=8,
+ children=dmc.NumberInput(
+ id=ElementIds.PSY_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
+ w="100%",
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
),
@@ -217,21 +271,34 @@ def inputs():
def layout():
- return (
- dmc.Box(
- children=title_with_link(
+ return dmc.Stack(
+ w="100%",
+ gap="md",
+ children=[
+ # 标题(保留封装)
+ title_with_link(
text="Psychrometric Chart",
id_button=IdButtons.PSYCHROMETRIC_CHART_CHART,
doc_link=DocLinks.PSYCHROMETRIC_CHART,
),
- ),
- dcc.Loading(
- type="circle",
- children=dmc.Box(
- className="container-col",
- children=[inputs(), dmc.Box(id=ElementIds.PSYCH_CHART)],
+ # 内容区:输入区 + 图表
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ w="100%",
+ gap="md",
+ children=[
+ inputs(),
+ dmc.Paper(
+ id=ElementIds.PSYCH_CHART,
+ radius="md",
+ p="sm",
+ w="100%",
+ ),
+ ],
+ ),
),
- ),
+ ],
)
@@ -293,7 +360,7 @@ def update_psych_chart(
if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0:
return (
- dbc.Alert(
+ dmc.Alert(
"No data is available in this location under these conditions. Please "
"either change the month and hour filters, or select a wider range for "
"the filter variable",
diff --git a/pages/select.py b/pages/select.py
index 330c8728..fefa5872 100644
--- a/pages/select.py
+++ b/pages/select.py
@@ -40,7 +40,6 @@
def layout():
"""Contents in the first tab 'Select Weather File'"""
return dmc.Box(
- className="container-col tab-container",
children=[
dcc.Loading(
id=ElementIds.LOADING_ONE,
@@ -50,20 +49,21 @@ def layout():
),
dcc.Upload(
id=ElementIds.UPLOAD_DATA,
- children=dbc.Button(
+ children=dmc.Button(
[
"Drag and Drop or ",
html.A("Select an EPW file from your computer"),
],
id=ElementIds.UPLOAD_DATA_BUTTON,
- outline=True,
- color="secondary",
- className="mt-2",
- style={"borderRadius": "5px", "borderStyle": "dashed"},
+ variant="outline",
+ color="gray",
+ radius="sm",
+ style={"borderStyle": "dashed", "borderRadius": "5px"},
+ styles={"label": {"fontWeight": 400}},
),
# Allow multiple files to be uploaded
multiple=True,
- className="d-grid",
+ style={"display": "grid"},
),
dmc.Skeleton(
visible=False,
@@ -71,30 +71,53 @@ def layout():
height=500,
children=dmc.Box(id=ElementIds.TAB_ONE_MAP),
),
- dbc.Modal(
- [
- dbc.ModalHeader(id=ElementIds.MODAL_HEADER),
- dbc.ModalFooter(
- children=[
- dbc.Button(
+ dmc.Modal(
+ id=ElementIds.MODAL,
+ title=dmc.Text(id=ElementIds.MODAL_HEADER),
+ opened=False,
+ centered=True,
+ children=[
+ dmc.Divider(
+ size="xs",
+ color="gray",
+ my="sm",
+ style={
+ "borderTop": "1px solid var(--mantine-color-gray-4)",
+ "marginTop": "-6px",
+ },
+ ),
+ dmc.Group(
+ [
+ dmc.Button(
"Close",
id=ElementIds.MODAL_CLOSE_BUTTON,
- className="ml-2",
- color="light",
+ ml="sm",
+ color="gray",
+ variant="outline",
),
- dbc.Button(
+ dmc.Button(
"Yes",
id=ElementIds.MODAL_YES_BUTTON,
- className="ml-2",
- color="primary",
+ ml="sm",
+ color="blue",
),
- ]
+ ],
+ justify="flex-end",
+ gap="md",
+ w="100%",
),
],
- id=ElementIds.MODAL,
- is_open=False,
),
],
+ w="100%",
+ mx=0,
+ px=0,
+ py="md",
+ style={
+ "display": "flex",
+ "flexDirection": "column",
+ "gap": "var(--mantine-spacing-md)",
+ },
)
@@ -288,7 +311,7 @@ def enable_tabs_when_data_is_loaded(meta, data):
@callback(
[
- Output(ElementIds.MODAL, "is_open"),
+ Output(ElementIds.MODAL, "opened"),
Output(ElementIds.ID_SELECT_URL_STORE, "data"),
],
[
@@ -296,30 +319,25 @@ def enable_tabs_when_data_is_loaded(meta, data):
Input(ElementIds.TAB_ONE_MAP, "clickData"),
Input(ElementIds.MODAL_CLOSE_BUTTON, "n_clicks"),
],
- [State(ElementIds.MODAL, "is_open")],
+ [State(ElementIds.MODAL, "opened")],
prevent_initial_call=True,
)
-def display_modal_when_data_clicked(_, click_map, __, is_open):
+def display_modal_when_data_clicked(_, click_map, __, opened):
"""display the modal to the user and check if he wants to use that file"""
if click_map:
url = re.search(
r'href=[\'"]?([^\'" >]+)', click_map["points"][0]["customdata"][-1]
).group(1)
- return not is_open, url
- return is_open, ""
+ return (not opened, url) # 点到点 → 打开 Modal
+ return (opened, "")
@callback(
- [
- Output(ElementIds.MODAL_HEADER, "children"),
- ],
- [
- Input(ElementIds.TAB_ONE_MAP, "clickData"),
- ],
+ [Output(ElementIds.MODAL_HEADER, "children")],
+ [Input(ElementIds.TAB_ONE_MAP, "clickData")],
prevent_initial_call=True,
)
def change_text_modal(click_map):
- """change the text of the modal header"""
if click_map:
return [f"Analyse data from {click_map['points'][0]['hovertext']}?"]
return ["Analyse data from this location?"]
@@ -377,10 +395,9 @@ def plot_location_epw_files(pathname):
fig.update_layout(mapbox_style="carto-positron")
fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
- return (
- dcc.Graph(
- id=ElementIds.TAB_ONE_MAP,
- figure=fig,
- config=generate_chart_name(TabNames.EPW_LOCATION_SELECT),
- ),
+ return dcc.Graph(
+ id=ElementIds.TAB_ONE_MAP,
+ figure=fig,
+ config=generate_chart_name(TabNames.EPW_LOCATION_SELECT),
+ style={"position": "relative", "zIndex": 5},
)
diff --git a/pages/summary.py b/pages/summary.py
index 832ab7e2..8dc257ea 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -1,9 +1,7 @@
import dash
-import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate
-from dash_extensions.enrich import dcc, html, Output, Input, State, callback
+from dash_extensions.enrich import dcc, Output, Input, State, callback
import dash_mantine_components as dmc
-
import plotly.graph_objects as go
import requests
@@ -36,11 +34,11 @@
def layout():
"""Contents in the second tab 'Climate Summary'."""
- return dmc.Box(
- className="container-col",
- id=ElementIds.TAB_TWO_CONTAINER,
+ return dmc.Container(
+ fluid=True,
+ px="md",
children=[
- #
+ dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, gap="md"),
],
)
@@ -57,134 +55,107 @@ def update_layout(si_ip):
heating_setpoint = 50
cooling_setpoint = 64
- return dmc.Box(
- className="container-col",
+ return dmc.Stack(
id=ElementIds.TAB2_SCE1_CONTAINER,
+ gap="xl",
children=[
dcc.Loading(
type="circle",
- children=dmc.Box(
- className="container-col",
+ children=dmc.Stack(
id=ElementIds.LOCATION_INFO,
- style={"padding": "12px"},
+ gap="xs",
+ p="md",
),
),
dcc.Loading(
type="circle",
- children=dmc.Box(className="tab-two-section", id=ElementIds.WORLD_MAP),
+ children=dmc.Stack(id=ElementIds.WORLD_MAP, gap=0),
),
- dmc.Box(
- children=title_with_tooltip(
- text="Download",
- id_button=IdButtons.DOWNLOAD_BUTTON_LABEL,
- tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file",
- ),
+ title_with_tooltip(
+ text="Download",
+ id_button=IdButtons.DOWNLOAD_BUTTON_LABEL,
+ tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file",
),
dcc.Loading(
type="circle",
- children=dbc.Row(
- [
- dbc.Col(
- dbc.Button(
- "Download EPW",
- color="primary",
- id=ElementIds.DOWN_EPW_BUTTON,
- ),
- width="auto",
- ),
- dbc.Col(
- dbc.Button(
- "Download Clima dataframe",
- color="primary",
- id=ElementIds.DOWNLOAD_BUTTON,
- ),
- width="auto",
+ children=dmc.Group(
+ align="center",
+ justify="flex-start",
+ gap="md",
+ children=[
+ dmc.Button(
+ "Download EPW",
+ id=ElementIds.DOWN_EPW_BUTTON,
+ color="blue",
+ variant="filled",
),
- dbc.Col(
- [
- dcc.Download(id=ElementIds.DOWNLOAD_DATAFRAME_CSV),
- dcc.Download(id=ElementIds.DOWNLOAD_EPW),
- ],
- width=1,
+ dmc.Button(
+ "Download Clima dataframe",
+ id=ElementIds.DOWNLOAD_BUTTON,
+ color="blue",
+ variant="filled",
),
+ dcc.Download(id=ElementIds.DOWNLOAD_DATAFRAME_CSV),
+ dcc.Download(id=ElementIds.DOWNLOAD_EPW),
],
),
),
- dmc.Box(
- children=title_with_link(
- text="Heating and Cooling Degree Days",
- id_button=IdButtons.HDD_CDD_CHART,
- doc_link=DocLinks.DEGREE_DAYS,
- ),
- ),
- dbc.Alert(
- "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!",
- color="warning",
- is_open=False,
- id=ElementIds.WARNING_CDD_HIGHER_HDD,
+ title_with_link(
+ text="Heating and Cooling Degree Days",
+ id_button=IdButtons.HDD_CDD_CHART,
+ doc_link=DocLinks.DEGREE_DAYS,
),
- dbc.Row(
- [
- dbc.Col(
- html.Label(
- "Heating degree day (HDD) setpoint",
- ),
- width="auto",
- ),
- dbc.Col(
- dbc.Input(
- id=ElementIds.INPUT_HDD_SET_POINT,
- type="number",
- value=heating_setpoint,
- style={"width": "4rem"},
- ),
- width="auto",
- ),
- dbc.Col(
- html.Label(
- "Cooling degree day (CDD) setpoint",
- ),
- width="auto",
+ dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD, gap=0),
+ dmc.Group(
+ align="center",
+ justify="center",
+ gap="md",
+ children=[
+ dmc.Text("Heating degree day (HDD) setpoint"),
+ dmc.NumberInput(
+ id=ElementIds.INPUT_HDD_SET_POINT,
+ value=heating_setpoint,
+ step=1,
+ min=-100,
+ max=100,
+ w=80,
+ hideControls=False,
),
- dbc.Col(
- dbc.Input(
- id=ElementIds.INPUT_CDD_SET_POINT,
- type="number",
- value=cooling_setpoint,
- style={"width": "4rem"},
- ),
- width="auto",
+ dmc.Text("Cooling degree day (CDD) setpoint"),
+ dmc.NumberInput(
+ id=ElementIds.INPUT_CDD_SET_POINT,
+ value=cooling_setpoint,
+ step=1,
+ min=-100,
+ max=100,
+ w=80,
+ hideControls=False,
),
- dbc.Col(
- dbc.Button(
- id=ElementIds.SUBMIT_SET_POINTS,
- children="Submit",
- color="primary",
- ),
- width="auto",
+ dmc.Button(
+ id=ElementIds.SUBMIT_SET_POINTS,
+ children="Submit",
+ color="blue",
+ variant="filled",
),
],
- align="center",
- justify="center",
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER),
+ children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER, gap=0),
),
- dmc.Box(
- children=title_with_link(
- text="Climate Profiles",
- id_button=IdButtons.CLIMATE_PROFILES_CHART,
- doc_link=DocLinks.CLIMATE_PROFILES,
- ),
+ title_with_link(
+ text="Climate Profiles",
+ id_button=IdButtons.CLIMATE_PROFILES_CHART,
+ doc_link=DocLinks.CLIMATE_PROFILES,
),
- dbc.Row(
+ dmc.Grid(
id=ElementIds.GRAPH_CONTAINER,
+ gutter="md",
children=[
- dbc.Col(id=ElementIds.TEMP_PROFILE_GRAPH, width=12, md=6, lg=3),
- dbc.Col(id=ElementIds.HUMIDITY_PROFILE_GRAPH, width=12, md=6, lg=3),
- dbc.Col(id=ElementIds.SOLAR_RADIATION_GRAPH, width=12, md=6, lg=3),
- dbc.Col(id=ElementIds.WIND_SPEED_GRAPH, width=12, md=6, lg=3),
+ dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span=3),
+ dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span=3),
+ dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span=3),
+ dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span=3),
],
),
],
@@ -208,14 +179,11 @@ def update_layout(si_ip):
)
def update_map(meta):
"""Update the contents of tab two. Passing in the general info (df, meta)."""
- map_world = dcc.Graph(
- id=ElementIds.GH_RAD_PROFILE_GRAPH,
+ return dcc.Graph(
config=generate_chart_name(TabNames.MAP, meta),
figure=world_map(meta),
)
- return map_world
-
@callback(
Output(ElementIds.LOCATION_INFO, "children"),
@@ -232,94 +200,76 @@ def update_location_info(ts, df, meta, si_ip):
lon = f"Longitude: {meta[ColNames.LON]}"
lat = f"Latitude: {meta[ColNames.LAT]}"
- site_elevation = float(meta[ColNames.SITE_ELEVATION])
- site_elevation = round(site_elevation, 2)
-
- elevation = f"Elevation above sea level: {str(site_elevation)} m"
+ site_elevation = round(float(meta[ColNames.SITE_ELEVATION]), 2)
if si_ip != UnitSystem.SI:
- site_elevation = site_elevation * 3.281
- site_elevation = round(site_elevation, 2)
- elevation = f"Elevation above sea level: {str(site_elevation)} ft"
+ site_elevation = round(site_elevation * 3.281, 2)
+ elevation = f"Elevation above sea level: {site_elevation} ft"
+
+ else:
+ elevation = f"Elevation above sea level: {site_elevation} m"
period = ""
if meta[ColNames.PERIOD]:
start, stop = meta[ColNames.PERIOD].split("-")
period = f"This file is based on data collected between {start} and {stop}"
- r = requests.get(
- f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}"
- )
-
climate_text = ""
- if r.status_code == 200:
- try:
- climate_zone = r.json()["return_values"][0]["koppen_geiger_zone"]
- zone_description = r.json()["return_values"][0]["zone_description"]
-
- climate_text = (
- f"Köppen–Geiger climate zone: {climate_zone}. {zone_description}."
- )
- except KeyError:
- pass
+ try:
+ r = requests.get(
+ f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}"
+ )
+ if r.status_code == 200:
+ j = r.json()["return_values"][0]
+ climate_text = f"Köppen-Geiger climate zone: {j['koppen_geiger_zone']}. {j['zone_description']}."
+ except Exception:
+ pass
# global horizontal irradiance
# Note that the value is divided by 1000, so a corresponding change is made in the unit:
total_solar_rad_value = round(df[ColNames.GLOB_HOR_RAD].sum() / 1000, 2)
- total_solar_rad_unit = (
- "k" + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][ColNames.UNIT]
- )
+ total_solar_rad_unit = "k" + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][
+ ColNames.UNIT
+ ].replace("", "").replace("", "")
total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}"
glob_sum = df[ColNames.GLOB_HOR_RAD].sum()
- if glob_sum > 0:
- diffuse_percentage = round(df[ColNames.DIF_HOR_RAD].sum() / glob_sum * 100, 1)
- else:
- diffuse_percentage = 0
+ diffuse_percentage = (
+ round(df[ColNames.DIF_HOR_RAD].sum() / glob_sum * 100, 1) if glob_sum > 0 else 0
+ )
total_diffuse_rad = (
f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %"
)
+
tmp_unit = mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT]
+
average_yearly_tmp = (
- f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} " + tmp_unit
- )
- hottest_yearly_tmp = (
- f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} "
- + tmp_unit
- )
- coldest_yearly_tmp = (
- f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} "
- + tmp_unit
+ f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} {tmp_unit}"
)
+ hottest_yearly_tmp = f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} {tmp_unit}"
+ coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}"
- location_info = dbc.Col(
- [
- dbc.Row(location, style={"fontWeight": "bold"}),
- dbc.Row(lon),
- dbc.Row(lat),
- dbc.Row(elevation),
- dbc.Row(period),
- dbc.Row(climate_text),
- dbc.Row(average_yearly_tmp),
- dbc.Row(hottest_yearly_tmp),
- dbc.Row(coldest_yearly_tmp),
- dbc.Row(
- dcc.Markdown(
- dangerously_allow_html=True,
- children=[total_solar_rad],
- style={"padding": 0},
- )
- ),
- dbc.Row(total_diffuse_rad),
+ return dmc.Stack(
+ gap=4,
+ children=[
+ dmc.Text(location, fw=700),
+ dmc.Text(lon),
+ dmc.Text(lat),
+ dmc.Text(elevation),
+ dmc.Text(period) if period else None,
+ dmc.Text(climate_text) if climate_text else None,
+ dmc.Text(average_yearly_tmp),
+ dmc.Text(hottest_yearly_tmp),
+ dmc.Text(coldest_yearly_tmp),
+ dmc.Text(total_solar_rad),
+ dmc.Text(total_diffuse_rad),
],
)
- return location_info
-
@callback(
[
Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"),
- Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is_open"),
+ Output(ElementIds.WARNING_CDD_HIGHER_HDD, "children"),
],
[
Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"),
@@ -346,9 +296,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_
hdd_setpoint = hdd_value
cdd_setpoint = cdd_value
- warning_setpoint = False
- if cdd_setpoint < hdd_setpoint:
- warning_setpoint = True
+ warning_setpoint = cdd_setpoint < hdd_setpoint
color_hdd = "red"
color_cdd = "dodgerblue"
@@ -363,18 +311,14 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_
# calculates HDD per month
query = query_month + str(i) + " and DBT<=" + str(hdd_setpoint)
a = df.query(query)[ColNames.DBT].sub(hdd_setpoint)
- hdd = a.sum(axis=0, skipna=True)
- hdd = hdd / 24
- hdd = int(hdd)
- hdd_array.append(hdd)
+ hdd = a.sum(axis=0, skipna=True) / 24
+ hdd_array.append(int(hdd))
# calculates CDD per month
query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint)
a = df.query(query)[ColNames.DBT].sub(cdd_setpoint)
- cdd = a.sum(axis=0, skipna=True)
- cdd = cdd / 24
- cdd = int(cdd)
- cdd_array.append(cdd)
+ cdd = a.sum(axis=0, skipna=True) / 24
+ cdd_array.append(int(cdd))
trace1 = go.Bar(
x=months,
@@ -397,11 +341,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_
),
)
- data = [trace2, trace1]
-
- fig = go.Figure(
- data=data,
- )
+ fig = go.Figure(data=[trace2, trace1])
fig.update_layout(
barmode="relative",
margin=tight_margins,
@@ -424,7 +364,19 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_
figure=fig,
)
- return chart, warning_setpoint
+ alert_children = (
+ dmc.Alert(
+ "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!",
+ color="yellow",
+ variant="filled",
+ title="Warning",
+ radius="md",
+ withCloseButton=True,
+ )
+ if warning_setpoint
+ else None
+ )
+ return chart, alert_children
@callback(
@@ -443,7 +395,6 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip):
units = generate_units_degree(si_ip)
return dcc.Graph(
id=ElementIds.TDB_PROFILE_GRAPH,
- className="violin-container",
config=generate_chart_name(TabNames.DRY_BULB_TEMPERATURE, meta, units),
figure=violin(df, ColNames.DBT, global_local, si_ip),
)
@@ -466,7 +417,6 @@ def update_tab_wind(ts, global_local, df, meta, si_ip):
units = generate_units(si_ip)
return dcc.Graph(
id=ElementIds.WIND_PROFILE_GRAPH,
- className="violin-container",
config=generate_chart_name(TabNames.WIND_SPEED, meta, units),
figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip),
)
@@ -489,7 +439,6 @@ def update_tab_rh(ts, global_local, df, meta, si_ip):
units = generate_units(si_ip)
return dcc.Graph(
id=ElementIds.RH_PROFILE_GRAPH,
- className="violin-container",
config=generate_chart_name(TabNames.RELATIVE_HUMIDITY, meta, units),
figure=violin(df, ColNames.RH, global_local, si_ip),
)
@@ -512,7 +461,6 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip):
units = generate_units(si_ip)
return dcc.Graph(
id=ElementIds.GH_RAD_PROFILE_GRAPH,
- className="violin-container",
config=generate_chart_name(TabNames.GLOBAL_HORIZONTAL_RADIATION, meta, units),
figure=violin(df, ColNames.GLOB_HOR_RAD, global_local, si_ip),
)
diff --git a/pages/sun.py b/pages/sun.py
index 18794ff0..315be16d 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -3,9 +3,9 @@
import dash
import dash_mantine_components as dmc
-import dash_bootstrap_components as dbc
+
import numpy as np
-from dash import html, dcc
+from dash import dcc
from dash_extensions.enrich import Output, Input, State, callback
from pages.lib.global_column_names import ColNames
@@ -56,25 +56,20 @@
def sun_path():
"""Return the layout for the custom sun path and its dropdowns."""
- return dmc.Box(
- className="container-col justify-center",
+ return dmc.Stack(
+ gap="md",
children=[
- dmc.Box(
- children=title_with_link(
- text="Sun path chart",
- id_button=IdButtons.SUN_PATH_CHART_LABEL,
- doc_link=DocLinks.SUN_PATH_DIAGRAM,
- ),
+ title_with_link(
+ text="Sun path chart",
+ id_button=IdButtons.SUN_PATH_CHART_LABEL,
+ doc_link=DocLinks.SUN_PATH_DIAGRAM,
),
- dbc.Row(
+ dmc.Group(
align="center",
justify="center",
+ gap="md",
children=[
- html.H6(
- className="text-next-to-input",
- children=["View: "],
- style={"width": "10rem"},
- ),
+ dmc.Title("View: ", order=6, w="10rem", fz="xl"),
dropdown(
id=ElementIds.CUSTOM_SUN_VIEW_DROPDOWN,
options={
@@ -86,15 +81,12 @@ def sun_path():
),
],
),
- dbc.Row(
+ dmc.Group(
align="center",
justify="center",
+ gap="md",
children=[
- html.H6(
- className="text-next-to-input",
- children=["Select variable: "],
- style={"width": "10rem"},
- ),
+ dmc.Title("Select Variable: ", order=6, w="10rem", fz="xl"),
dropdown(
id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN,
options=sc_dropdown_names,
@@ -105,9 +97,7 @@ def sun_path():
),
dcc.Loading(
type="circle",
- children=dmc.Box(
- id=ElementIds.CUSTOM_SUNPATH,
- ),
+ children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"),
),
],
)
@@ -115,24 +105,21 @@ def sun_path():
def explore_daily_heatmap():
"""Contents of the bottom part of the tab"""
- return dmc.Box(
- className="container-col full-width",
+ return dmc.Stack(
+ gap="md",
+ w="100%",
children=[
- dmc.Box(
- children=title_with_link(
- text="Daily charts",
- id_button=IdButtons.DAILY_CHART_LABEL,
- doc_link=DocLinks.CUSTOM_HEATMAP,
- ),
+ title_with_link(
+ text="Daily charts",
+ id_button=IdButtons.DAILY_CHART_LABEL,
+ doc_link=DocLinks.CUSTOM_HEATMAP,
),
- dmc.Box(
- className="container-row justify-center align-center mb-2",
+ dmc.Group(
+ align="center",
+ justify="center",
+ gap="md",
children=[
- html.H6(
- className="text-next-to-input",
- children=["Select variable: "],
- style={"width": "10rem"},
- ),
+ dmc.Title("Select variable: ", order=6, w="10rem"),
dropdown(
id=ElementIds.TAB_EXPLORE_DROPDOWN,
options=sun_cloud_tab_explore_dropdown_names,
@@ -141,19 +128,22 @@ def explore_daily_heatmap():
),
],
),
- dcc.Loading(type="circle", children=dmc.Box(id=ElementIds.TAB4_DAILY)),
+ dcc.Loading(
+ type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY, w="100%")
+ ),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.TAB4_HEATMAP),
+ children=dmc.Stack(id=ElementIds.TAB4_HEATMAP, w="100%"),
),
],
)
def static_section():
- return dmc.Box(
+ return dmc.Stack(
id=ElementIds.STATIC_SECTION,
- className="container-col full-width",
+ gap="md",
+ w="100%",
children=[
# ...
],
@@ -162,8 +152,9 @@ def static_section():
def layout():
"""Contents of tab four."""
- return dmc.Box(
- className="container-col",
+ return dmc.Stack(
+ gap="md",
+ w="100%",
id=ElementIds.TAB_FOUR_CONTAINER,
children=[sun_path(), static_section(), explore_daily_heatmap()],
)
@@ -178,27 +169,23 @@ def update_static_section(si_ip):
if si_ip == UnitSystem.IP:
hor_unit = "Btu/ft²"
return [
- dmc.Box(
- children=title_with_link(
- text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")",
- id_button=IdButtons.MONTHLY_CHART_LABEL,
- doc_link=DocLinks.SOLAR_RADIATION,
- ),
+ title_with_link(
+ text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")",
+ id_button=IdButtons.MONTHLY_CHART_LABEL,
+ doc_link=DocLinks.SOLAR_RADIATION,
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.MONTHLY_SOLAR),
+ children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR, w="100%"),
),
- dmc.Box(
- children=title_with_link(
- text="Cloud coverage",
- id_button=IdButtons.CLOUD_CHART_LABEL,
- doc_link=DocLinks.CLOUD_COVER,
- ),
+ title_with_link(
+ text="Cloud coverage",
+ id_button=IdButtons.CLOUD_CHART_LABEL,
+ doc_link=DocLinks.CLOUD_COVER,
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.CLOUD_COVER),
+ children=dmc.Stack(id=ElementIds.CLOUD_COVER, w="100%"),
),
]
@@ -238,11 +225,13 @@ def monthly_and_cloud_chart(_, df, meta, si_ip):
)
units = generate_units(si_ip)
return dcc.Graph(
+ style={"width": "100%", "height": "520px"},
config=generate_chart_name(
TabNames.GLOBAL_AND_DIFFUSE_HORIZONTAL_SOLAR_RADIATION, meta, units
),
figure=monthly,
), dcc.Graph(
+ style={"width": "100%", "height": "520px"},
config=generate_chart_name(TabNames.CLOUD_COVER, meta, units),
figure=cover,
)
@@ -268,6 +257,7 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip):
units = "" if var == "None" else generate_units(si_ip)
if view == "polar":
return dcc.Graph(
+ style={"width": "100%", "height": "520px"},
config=generate_chart_name(
TabNames.SPHERICAL_SUNPATH, meta, custom_inputs, units
),
@@ -275,6 +265,7 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip):
)
else:
return dcc.Graph(
+ style={"width": "100%", "height": "520px"},
config=generate_chart_name(
TabNames.CARTESIAN_SUNPATH, meta, custom_inputs, units
),
@@ -300,6 +291,7 @@ def daily(_, var, global_local, df, meta, si_ip):
custom_inputs = generate_custom_inputs(var)
units = generate_units(si_ip)
return dcc.Graph(
+ style={"width": "100%", "height": "520px"},
config=generate_chart_name(TabNames.DAILY, meta, custom_inputs, units),
figure=daily_profile(df, var, global_local, si_ip),
)
@@ -322,6 +314,7 @@ def update_heatmap(_, var, global_local, df, meta, si_ip):
custom_inputs = generate_custom_inputs(var)
units = generate_units(si_ip)
return dcc.Graph(
+ style={"width": "100%", "height": "520px"},
config=generate_chart_name(TabNames.HEATMAP, meta, custom_inputs, units),
figure=heatmap(df, var, global_local, si_ip),
)
diff --git a/pages/t_rh.py b/pages/t_rh.py
index 4d249f81..7fd9707d 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -1,6 +1,7 @@
import dash
-from dash_extensions.enrich import Output, Input, State, dcc, html, callback
+from dash_extensions.enrich import Output, Input, State, dcc, callback
import dash_mantine_components as dmc
+
from config import PageUrls, DocLinks, PageInfo
from pages.lib.global_scheme import dropdown_names
from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile
@@ -31,69 +32,66 @@
def layout():
- return dmc.Box(
- className="container-col full-width",
+ return dmc.Container(
+ fluid=True,
+ px="md",
children=[
- dmc.Box(
- className="container-row full-width align-center justify-center",
+ dmc.Group(
+ justify="center",
+ align="center",
+ gap="sm",
+ wrap="nowrap",
children=[
- html.H4(
- className="text-next-to-input", children=["Select a variable: "]
- ),
+ dmc.Text("Select a variable:", fz="xl"),
dropdown(
id=ElementIds.ID_T_RH_DROPDOWN,
- className="dropdown-t-rh",
options={var: dropdown_names[var] for var in var_to_plot},
value=dropdown_names[var_to_plot[0]],
+ style={"width": "14rem"},
),
],
),
- dmc.Box(
- className="container-col",
+ dmc.Stack(
+ gap="lg",
+ mt="md",
children=[
- dmc.Box(
- children=title_with_link(
- text="Yearly Chart",
- id_button=IdButtons.YEARLY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
+ # Yearly Chart
+ title_with_link(
+ text="Yearly Chart",
+ id_button=IdButtons.YEARLY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.YEARLY_CHART),
+ children=dmc.Stack(id=ElementIds.YEARLY_CHART, gap=0),
),
- dmc.Box(
- children=title_with_link(
- text="Daily chart",
- id_button=IdButtons.DAILY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
+ # Daily chart
+ title_with_link(
+ text="Daily chart",
+ id_button=IdButtons.DAILY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.DAILY),
+ children=dmc.Stack(id=ElementIds.DAILY, gap=0),
),
- dmc.Box(
- children=title_with_link(
- text="Heatmap chart",
- id_button=IdButtons.HEATMAP_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
+ # Heatmap chart
+ title_with_link(
+ text="Heatmap chart",
+ id_button=IdButtons.HEATMAP_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.HEATMAP),
- ),
- dmc.Box(
- children=title_with_tooltip(
- text="Descriptive statistics",
- tooltip_text="count, mean, std, min, max, and percentiles",
- id_button=IdButtons.TABLE_TMP_RH,
- ),
+ children=dmc.Stack(id=ElementIds.HEATMAP, gap=0),
),
- dmc.Box(
- id=ElementIds.TABLE_TMP_HUM,
+ # Descriptive statistics
+ title_with_tooltip(
+ text="Descriptive statistics",
+ tooltip_text="count, mean, std, min, max, and percentiles",
+ id_button=IdButtons.TABLE_TMP_RH,
),
+ dmc.Stack(id=ElementIds.TABLE_TMP_HUM, gap=0),
],
),
],
@@ -193,7 +191,7 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip):
@callback(
- [Output(ElementIds.HEATMAP, "children")],
+ Output(ElementIds.HEATMAP, "children"),
[
Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"),
Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"),
@@ -206,7 +204,7 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip):
],
)
def update_heatmap(_, global_local, dd_value, df, meta, si_ip):
- """Update the contents of tab three. Passing in general info (df, meta)."""
+ """Update heatmap content."""
if dd_value == dropdown_names[var_to_plot[0]]:
units = generate_units_degree(si_ip)
return dcc.Graph(
@@ -261,7 +259,7 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip):
],
)
def update_table(_, dd_value, df, si_ip):
- """Update the contents of tab three. Passing in general info (df, meta)."""
+ """Update the contents of descriptive statistics table."""
return summary_table_tmp_rh_tab(
df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]],
dd_value,
diff --git a/pages/wind.py b/pages/wind.py
index da2813bc..38204b9e 100644
--- a/pages/wind.py
+++ b/pages/wind.py
@@ -1,11 +1,11 @@
import dash
-from dash import dcc, html
+from dash import dcc
import dash_mantine_components as dmc
from dash_extensions.enrich import Output, Input, State, callback
from pages.lib.global_element_ids import ElementIds
from config import PageUrls, DocLinks, PageInfo
-from pages.lib.global_scheme import month_lst, container_row_center_full
+from pages.lib.global_scheme import month_lst
from pages.lib.template_graphs import heatmap, wind_rose
from pages.lib.global_column_names import ColNames
from pages.lib.global_id_buttons import IdButtons
@@ -30,14 +30,16 @@
def sliders():
"""Returns 2 sliders for the hour"""
- return dmc.Box(
- className="container-col justify-center",
+ return dmc.Stack(
id=ElementIds.SLIDER_CONTAINER,
+ gap="md",
+ align="center",
children=[
- dmc.Box(
- className="container-row each-slider",
+ dmc.Group(
+ gap="sm",
+ align="center",
children=[
- html.P("Month Range"),
+ dmc.Text("Month Range"),
dcc.RangeSlider(
id=ElementIds.MONTH_SLIDER,
min=1,
@@ -50,10 +52,11 @@ def sliders():
),
],
),
- dmc.Box(
- className="container-row each-slider",
+ dmc.Group(
+ gap="sm",
+ align="center",
children=[
- html.P("Hour Range"),
+ dmc.Text("Hour Range"),
dcc.RangeSlider(
id=ElementIds.HOUR_SLIDER,
min=1,
@@ -72,87 +75,76 @@ def sliders():
def seasonal_wind_rose():
"""Return the section with the 4 seasonal wind rose graphs."""
- return dmc.Box(
- className="container-col",
+ return dmc.Stack(
+ gap="md",
children=[
- dmc.Box(
- children=title_with_link(
- text="Seasonal Wind Rose",
- id_button=IdButtons.SEASONAL_WIND_ROSE_DOC,
- doc_link=DocLinks.WIND_ROSE,
- ),
+ title_with_link(
+ text="Seasonal Wind Rose",
+ id_button=IdButtons.SEASONAL_WIND_ROSE_DOC,
+ doc_link=DocLinks.WIND_ROSE,
),
- dmc.Box(
- className=container_row_center_full,
+ dmc.Grid(
+ gutter="md",
children=[
- dmc.Box(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=dmc.Box(
- id=ElementIds.WINTER_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.WINTER_WIND_ROSE, w="100%"
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.WINTER_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.WINTER_WIND_ROSE_TEXT),
+ ],
+ ),
),
- dmc.Box(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=dmc.Box(
- id=ElementIds.SPRING_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.SPRING_WIND_ROSE, w="100%"
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.SPRING_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.SPRING_WIND_ROSE_TEXT),
+ ],
+ ),
),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- dmc.Box(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=dmc.Box(
- id=ElementIds.SUMMER_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.SUMMER_WIND_ROSE, w="100%"
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.SUMMER_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.SUMMER_WIND_ROSE_TEXT),
+ ],
+ ),
),
- dmc.Box(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=dmc.Box(
- id=ElementIds.FALL_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.FALL_WIND_ROSE, w="100%"
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.FALL_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.FALL_WIND_ROSE_TEXT),
+ ],
+ ),
),
],
),
@@ -162,74 +154,62 @@ def seasonal_wind_rose():
def daily_wind_rose():
"""Return the section for the 3 daily wind rose graphs."""
- return dmc.Box(
- className="container-col full-width",
+ return dmc.Stack(
+ gap="md",
id=ElementIds.TAB5_DAILY_CONTAINER,
children=[
- dmc.Box(
- children=title_with_link(
- text="Daily Wind Rose",
- id_button=IdButtons.DAILY_ROSE_CHART,
- doc_link=DocLinks.WIND_ROSE,
- ),
+ title_with_link(
+ text="Daily Wind Rose",
+ id_button=IdButtons.DAILY_ROSE_CHART,
+ doc_link=DocLinks.WIND_ROSE,
),
- dmc.Box(
- id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER,
- className="container-row full-width",
+ dmc.Grid(
+ gutter="md",
children=[
- dmc.Box(
- className="container-col",
- children=[
- dmc.Box(
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
dcc.Loading(
type="circle",
- children=dmc.Box(
- className="daily-wind-graph",
- id=ElementIds.MORNING_WIND_ROSE,
+ children=dmc.Stack(
+ id=ElementIds.MORNING_WIND_ROSE, w="100%"
),
),
- ),
- html.P(
- className="daily-text",
- id=ElementIds.MORNING_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.MORNING_WIND_ROSE_TEXT),
+ ],
+ ),
),
- dmc.Box(
- className="container-col",
- children=[
- dmc.Box(
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
dcc.Loading(
type="circle",
- children=dmc.Box(
- className="daily-wind-graph",
- id=ElementIds.NOON_WIND_ROSE,
+ children=dmc.Stack(
+ id=ElementIds.NOON_WIND_ROSE, w="100%"
),
),
- ),
- html.P(
- className="daily-text",
- id=ElementIds.NOON_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.NOON_WIND_ROSE_TEXT),
+ ],
+ ),
),
- dmc.Box(
- className="container-col",
- children=[
- dmc.Box(
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ gap="xs",
+ children=[
dcc.Loading(
type="circle",
- children=dmc.Box(
- className="daily-wind-graph",
- id=ElementIds.NIGHT_WIND_ROSE,
+ children=dmc.Stack(
+ id=ElementIds.NIGHT_WIND_ROSE, w="100%"
),
),
- ),
- html.P(
- className="daily-text",
- id=ElementIds.NIGHT_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.NIGHT_WIND_ROSE_TEXT),
+ ],
+ ),
),
],
),
@@ -238,103 +218,119 @@ def daily_wind_rose():
def custom_wind_rose():
- return dmc.Box(
- className="container-col justify-center full-width",
+ return dmc.Stack(
+ gap="md",
+ align="stretch", # stretch 让子项默认靠左
children=[
- dmc.Box(
- children=title_with_tooltip(
- text="Customizable Wind Rose",
- tooltip_text=None,
- id_button=IdButtons.CUSTOM_ROSE_CHART,
- ),
+ # 标题靠左
+ title_with_tooltip(
+ text="Customizable Wind Rose",
+ tooltip_text=None,
+ id_button=IdButtons.CUSTOM_ROSE_CHART,
),
- dmc.Box(
- className="container-row full-width justify-center",
- id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER,
+ # 参数区(保持居中排布)
+ dmc.Grid(
+ gutter="md",
+ justify="center",
+ align="center",
+ maw=900,
+ mx="auto",
+ w="100%",
children=[
- dmc.Box(
- className="container-col justify-center p-2 mr-2",
- children=[
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- style={"width": "8rem"},
- children=["Start Month:"],
- ),
- dropdown(
- id=ElementIds.TAB5_CUSTOM_START_MONTH,
- options={
- j: i + 1 for i, j in enumerate(month_lst)
- },
- value=1,
- style={"width": "6rem"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- style={"width": "8rem"},
- children=["Start Hour:"],
- ),
- dropdown(
- id=ElementIds.TAB5_CUSTOM_START_HOUR,
- options={
- str(i) + ":00": i for i in range(0, 24)
- },
- value=0,
- style={"width": "6rem"},
- ),
- ],
- ),
- ],
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="md",
+ align="center",
+ children=[
+ dmc.Group(
+ gap="md",
+ children=[
+ dmc.Title(
+ "Start Month:",
+ order=6,
+ w="8rem",
+ ta="right",
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_START_MONTH,
+ options={
+ j: i + 1
+ for i, j in enumerate(month_lst)
+ },
+ value=1,
+ style={"width": "6rem"},
+ ),
+ ],
+ ),
+ dmc.Group(
+ gap="md",
+ children=[
+ dmc.Title(
+ "Start Hour:", order=6, w="8rem", ta="right"
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_START_HOUR,
+ options={
+ str(i) + ":00": i for i in range(0, 24)
+ },
+ value=0,
+ style={"width": "6rem"},
+ ),
+ ],
+ ),
+ ],
+ ),
),
- dmc.Box(
- className="container-col justify-center p-2 ml-2",
- children=[
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- style={"width": "8rem"},
- children=["End Month:"],
- ),
- dropdown(
- id=ElementIds.TAB5_CUSTOM_END_MONTH,
- options={
- j: i + 1 for i, j in enumerate(month_lst)
- },
- value=12,
- style={"width": "6rem"},
- ),
- ],
- ),
- dmc.Box(
- className=container_row_center_full,
- children=[
- html.H6(
- style={"width": "8rem"},
- children=["End Hour:"],
- ),
- dropdown(
- id=ElementIds.TAB5_CUSTOM_END_HOUR,
- options={
- str(i) + ":00": i for i in range(1, 25)
- },
- value=24,
- style={"width": "6rem"},
- ),
- ],
- ),
- ],
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ gap="md",
+ align="center",
+ children=[
+ dmc.Group(
+ gap="md",
+ children=[
+ dmc.Title(
+ "End Month:", order=6, w="8rem", ta="right"
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_END_MONTH,
+ options={
+ j: i + 1
+ for i, j in enumerate(month_lst)
+ },
+ value=12,
+ style={"width": "6rem"},
+ ),
+ ],
+ ),
+ dmc.Group(
+ gap="md",
+ children=[
+ dmc.Title(
+ "End Hour:", order=6, w="8rem", ta="right"
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_END_HOUR,
+ options={
+ str(i) + ":00": i for i in range(1, 25)
+ },
+ value=24,
+ style={"width": "6rem"},
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.CUSTOM_WIND_ROSE),
+ children=dmc.Stack(
+ id=ElementIds.CUSTOM_WIND_ROSE, w="100%", maw=900, mx="auto"
+ ),
),
],
)
@@ -342,29 +338,26 @@ def custom_wind_rose():
def layout():
"""Contents in the fifth tab 'Wind'."""
- return dmc.Box(
- className="container-col justify-center",
+ return dmc.Stack(
+ gap="md",
+ align="stretch",
children=[
- dmc.Box(
- children=title_with_link(
- text="Annual Wind Rose",
- id_button=IdButtons.WIND_ROSE_LABEL,
- doc_link=DocLinks.WIND_ROSE,
- ),
+ title_with_link(
+ text="Annual Wind Rose",
+ id_button=IdButtons.WIND_ROSE_LABEL,
+ doc_link=DocLinks.WIND_ROSE,
),
dcc.Loading(
type="circle",
- children=dmc.Box(
- id=ElementIds.WIND_ROSE,
- ),
+ children=dmc.Stack(id=ElementIds.WIND_ROSE, w="100%"),
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.WIND_SPEED),
+ children=dmc.Stack(id=ElementIds.WIND_SPEED, w="100%"),
),
dcc.Loading(
type="circle",
- children=dmc.Box(id=ElementIds.WIND_DIRECTION),
+ children=dmc.Stack(id=ElementIds.WIND_DIRECTION, w="100%"),
),
seasonal_wind_rose(),
daily_wind_rose(),
@@ -373,7 +366,6 @@ def layout():
)
-# wind rose
@callback(
Output(ElementIds.WIND_ROSE, "children"),
Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
@@ -384,8 +376,6 @@ def layout():
],
)
def update_annual_wind_rose(_, df, meta, si_ip):
- """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta)."""
-
annual = wind_rose(df, "", [1, 12], [1, 24], True, si_ip)
units = generate_units(si_ip)
return dcc.Graph(
@@ -394,10 +384,8 @@ def update_annual_wind_rose(_, df, meta, si_ip):
)
-# wind speed
@callback(
Output(ElementIds.WIND_SPEED, "children"),
- # General
[
Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"),
@@ -409,8 +397,6 @@ def update_annual_wind_rose(_, df, meta, si_ip):
],
)
def update_tab_wind_speed(_, global_local, df, meta, si_ip):
- """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta)."""
-
speed = heatmap(df, ColNames.WIND_SPEED, global_local, si_ip)
units = generate_units(si_ip)
return dcc.Graph(
@@ -419,13 +405,9 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip):
)
-# wind direction
@callback(
Output(ElementIds.WIND_DIRECTION, "children"),
- # General
- [
- Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"),
- ],
+ [Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value")],
[
State(ElementIds.ID_WIND_DF_STORE, "data"),
State(ElementIds.ID_WIND_META_STORE, "data"),
@@ -433,8 +415,6 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip):
],
)
def update_tab_wind_direction(global_local, df, meta, si_ip):
- """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta)."""
-
direction = heatmap(df, ColNames.WIND_DIR, global_local, si_ip)
units = generate_units(si_ip)
return dcc.Graph(
@@ -443,10 +423,8 @@ def update_tab_wind_direction(global_local, df, meta, si_ip):
)
-# Custom Wind rose
@callback(
Output(ElementIds.CUSTOM_WIND_ROSE, "children"),
- # Custom Graph Input
[
Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
Input(ElementIds.TAB5_CUSTOM_START_MONTH, "value"),
@@ -463,14 +441,11 @@ def update_tab_wind_direction(global_local, df, meta, si_ip):
def update_custom_wind_rose(
_, start_month, start_hour, end_month, end_hour, df, meta, si_ip
):
- """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta)."""
-
start_hour = int(start_hour)
end_hour = int(end_hour)
start_month = int(start_month)
end_month = int(end_month)
- # Wind Rose Graphs
if start_month <= end_month:
df = df.loc[
(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)
@@ -483,6 +458,7 @@ def update_custom_wind_rose(
df = df.loc[(df[ColNames.HOUR] >= start_hour) & (df[ColNames.HOUR] <= end_hour)]
else:
df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)]
+
custom = wind_rose(
df, "", [start_month, end_month], [start_hour, end_hour], True, si_ip
)
@@ -509,9 +485,7 @@ def update_custom_wind_rose(
Output(ElementIds.SUMMER_WIND_ROSE_TEXT, "children"),
Output(ElementIds.FALL_WIND_ROSE_TEXT, "children"),
],
- [
- Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
- ],
+ [Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp")],
[
State(ElementIds.ID_WIND_DF_STORE, "data"),
State(ElementIds.ID_WIND_META_STORE, "data"),
@@ -525,18 +499,17 @@ def update_seasonal_graphs(_, df, meta, si_ip):
summer_months = [6, 8]
fall_months = [9, 12]
- # Wind Rose Graphs
winter = wind_rose(df, "", winter_months, hours, False, si_ip)
spring = wind_rose(df, "", spring_months, hours, True, si_ip)
summer = wind_rose(df, "", summer_months, hours, False, si_ip)
fall = wind_rose(df, "", fall_months, hours, False, si_ip)
- # Text
+ query_calm_wind = f"{ColNames.WIND_SPEED} == 0"
+
winter_df = df.loc[
(df[ColNames.MONTH] <= winter_months[1])
| (df[ColNames.MONTH] >= winter_months[0])
]
- query_calm_wind = f"{ColNames.WIND_SPEED} == 0"
winter_total_count = winter_df.shape[0]
winter_calm_count = winter_df.query(query_calm_wind).shape[0]
@@ -565,8 +538,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm):
f"Observations between the months of {month_start} and {month_end} "
f"between 01:00 hours and 24:00 hours. "
f"Selected observations {str(count)} of 8760, or "
- f"{str(int(100 * (count / 8760)))} %. {str(n_calm)} observations have "
- f"calm winds."
+ f"{str(int(100 * (count / 8760)))} %. {str(n_calm)} observations have calm winds."
)
winter_text = seasonal_chart_caption(
@@ -593,6 +565,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm):
fall_total_count,
fall_calm_count,
)
+
units = generate_units(si_ip)
return (
dcc.Graph(
@@ -619,7 +592,6 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm):
@callback(
- # Daily Graphs
[
Output(ElementIds.MORNING_WIND_ROSE, "children"),
Output(ElementIds.NOON_WIND_ROSE, "children"),
@@ -628,7 +600,6 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm):
Output(ElementIds.NOON_WIND_ROSE_TEXT, "children"),
Output(ElementIds.NIGHT_WIND_ROSE_TEXT, "children"),
],
- # General
Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
[
State(ElementIds.ID_WIND_DF_STORE, "data"),
@@ -637,20 +608,17 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm):
],
)
def update_daily_graphs(_, df, meta, si_ip):
- """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta)."""
-
months = [1, 12]
morning_times = [6, 13]
noon_times = [14, 21]
night_times = [22, 5]
- # Wind Rose Graphs
morning = wind_rose(df, "", months, morning_times, False, si_ip)
noon = wind_rose(df, "", months, noon_times, False, si_ip)
night = wind_rose(df, "", months, night_times, True, si_ip)
- # Text
query_calm_wind = f"{ColNames.WIND_SPEED} == 0"
+
morning_df = df.loc[
(df[ColNames.HOUR] >= morning_times[0])
& (df[ColNames.HOUR] <= morning_times[1])
@@ -676,21 +644,19 @@ def daily_chart_caption(hour_start, hour_end, count, calm_count):
f"Observations between the months of Jan and Dec between "
f"{str(hour_start)}:00 hours and {str(hour_end)}:00 hours. "
f"Selected observations {count} of 8760, or "
- f"{str(int(100 * (count / 8760)))}%. {calm_count} "
- f"observations have calm winds."
+ f"{str(int(100 * (count / 8760)))}%. {calm_count} observations have calm winds."
)
morning_text = daily_chart_caption(
morning_times[0], morning_times[1], morning_total_count, morning_calm_count
)
-
noon_text = daily_chart_caption(
noon_times[0], noon_times[1], noon_total_count, noon_calm_count
)
-
night_text = daily_chart_caption(
night_times[0], night_times[1], night_total_count, night_calm_count
)
+
units = generate_units(si_ip)
return (
dcc.Graph(
diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js
index b946fc72..26013789 100644
--- a/tests/node/cypress/e2e/spec.cy.js
+++ b/tests/node/cypress/e2e/spec.cy.js
@@ -45,7 +45,7 @@ describe('Clima', () => {
cy.contains('Latitude: 44.5308');
cy.contains('Elevation above sea level: 37.0 m');
cy.contains('This file is based on data collected between 2004 and 2018');
- cy.contains('Köppen–Geiger climate zone: Cfa. Humid subtropical, no dry season.');
+ cy.contains('Köppen-Geiger climate zone: Cfa. Humid subtropical, no dry season.');
cy.contains('Average yearly temperature: 14.5 °C');
cy.contains('Hottest yearly temperature (99%): 34.0 °C');
cy.contains('Coldest yearly temperature (1%): -2.0 °C');
From 2539f52a311b1b0324bc2e6a44e6a423194f64e8 Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Fri, 12 Sep 2025 18:38:06 +1000
Subject: [PATCH 06/23] add: created a new class file to store icon value
---
pages/lib/page_icon.py | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 pages/lib/page_icon.py
diff --git a/pages/lib/page_icon.py b/pages/lib/page_icon.py
new file mode 100644
index 00000000..6515a6b1
--- /dev/null
+++ b/pages/lib/page_icon.py
@@ -0,0 +1,32 @@
+class PageIcon:
+ """Page icon mappings - optimized with dictionary."""
+
+ # Page Name to icon Mapping
+ _ICON_MAP = {
+ "Select Weather File": "tabler:upload",
+ "Climate Summary": "tabler:chart-bar",
+ "Temperature and Humidity": "tabler:temperature",
+ "Sun and Clouds": "tabler:sun",
+ "Wind": "tabler:wind",
+ "Psychrometric Chart": "tabler:chart-dots",
+ "Natural Ventilation": "tabler:windmill",
+ "Outdoor Comfort": "tabler:thermometer",
+ "Data Explorer": "tabler:database",
+ "Changelog": "tabler:history",
+ }
+
+ SELECT_WEATHER_FILE = _ICON_MAP["Select Weather File"]
+ CLIMATE_SUMMARY = _ICON_MAP["Climate Summary"]
+ TEMPERATURE_AND_HUMIDITY = _ICON_MAP["Temperature and Humidity"]
+ SUN_AND_CLOUDS = _ICON_MAP["Sun and Clouds"]
+ WIND = _ICON_MAP["Wind"]
+ PSYCHROMETRIC_CHART = _ICON_MAP["Psychrometric Chart"]
+ NATURAL_VENTILATION = _ICON_MAP["Natural Ventilation"]
+ OUTDOOR_COMFORT = _ICON_MAP["Outdoor Comfort"]
+ DATA_EXPLORER = _ICON_MAP["Data Explorer"]
+ CHANGELOG = _ICON_MAP["Changelog"]
+
+ @classmethod
+ def get_icon(cls, page_name):
+ """Get icon for a page name."""
+ return cls._ICON_MAP.get(page_name, "tabler:circle")
From 6d7a4aa98079a604bdd49cf6360fd68213fd7883 Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Sun, 14 Sep 2025 19:25:05 +1000
Subject: [PATCH 07/23] fix: removed all html and dash bootstrap via using dash
mantine components
---
pages/explorer.py | 31 -------------------------------
pages/lib/layout.py | 2 +-
pages/natural_ventilation.py | 16 +++++++++-------
pages/outdoor.py | 7 ++-----
pages/psy-chart.py | 10 ----------
pages/select.py | 22 ++++++++++------------
6 files changed, 22 insertions(+), 66 deletions(-)
diff --git a/pages/explorer.py b/pages/explorer.py
index 68898ee3..442cc367 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -101,7 +101,6 @@ def section_one():
w="100%",
),
),
- # Daily chart
title_with_link(
text="Daily chart",
id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
@@ -116,7 +115,6 @@ def section_one():
w="100%",
),
),
- # Heatmap chart
title_with_link(
text="Heatmap chart",
id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
@@ -151,7 +149,6 @@ def section_one():
size="md",
radius="md",
),
- # Month Range 行(3-6-3)
dmc.Grid(
gutter="sm",
align="center",
@@ -243,17 +240,14 @@ def section_two_inputs():
w="100%",
gap="md",
children=[
- # 标题(保留你已有的封装)
title_with_tooltip(
text="Customizable heatmap",
tooltip_text=None,
id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL,
),
- # 三列区域:①变量选择 ②时间过滤(月份/小时)③数据过滤(变量/最小/最大)
dmc.Grid(
gutter="md",
children=[
- # ① 变量选择列
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -280,7 +274,6 @@ def section_two_inputs():
],
),
),
- # ② 时间过滤列(按钮 + 月份范围 + 小时范围)
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -294,7 +287,6 @@ def section_two_inputs():
size="md",
radius="md",
),
- # Month Range:3/6/3
dmc.Grid(
gutter="sm",
align="center",
@@ -334,7 +326,6 @@ def section_two_inputs():
),
],
),
- # Hour Range:3/6/3
dmc.Grid(
gutter="sm",
align="center",
@@ -377,7 +368,6 @@ def section_two_inputs():
],
),
),
- # ③ 数据过滤列(按钮 + 过滤变量 + 最小/最大值)
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -410,7 +400,6 @@ def section_two_inputs():
),
],
),
- # Min Value
dmc.Grid(
gutter="sm",
align="center",
@@ -431,7 +420,6 @@ def section_two_inputs():
),
],
),
- # Max Value
dmc.Grid(
gutter="sm",
align="center",
@@ -469,9 +457,7 @@ def section_two():
gap="md",
align="center",
children=[
- # 输入表单
section_two_inputs(),
- # 自定义热力图
dcc.Loading(
type="circle",
children=dmc.Paper(
@@ -481,7 +467,6 @@ def section_two():
w="100%",
),
),
- # Normalize 复选框
dmc.Group(
gap="sm",
children=[
@@ -494,7 +479,6 @@ def section_two():
),
],
),
- # Summary 图表
dcc.Loading(
type="circle",
children=dmc.Paper(
@@ -520,13 +504,11 @@ def section_three_inputs():
dmc.Grid(
gutter="md",
children=[
- # ① X/Y/Color By 列
dmc.GridCol(
span=4,
children=dmc.Stack(
gap="sm",
children=[
- # X Variable
dmc.Grid(
gutter="sm",
align="center",
@@ -544,7 +526,6 @@ def section_three_inputs():
),
],
),
- # Y Variable
dmc.Grid(
gutter="sm",
align="center",
@@ -562,7 +543,6 @@ def section_three_inputs():
),
],
),
- # Color By
dmc.Grid(
gutter="sm",
align="center",
@@ -583,7 +563,6 @@ def section_three_inputs():
],
),
),
- # ② 时间过滤(月/小时)
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -597,7 +576,6 @@ def section_three_inputs():
size="md",
radius="md",
),
- # Month Range:3/6/3
dmc.Grid(
gutter="sm",
align="center",
@@ -636,7 +614,6 @@ def section_three_inputs():
),
],
),
- # Hour Range:3/6/3
dmc.Grid(
gutter="sm",
align="center",
@@ -678,7 +655,6 @@ def section_three_inputs():
],
),
),
- # ③ 数据过滤(变量/最小/最大)
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -692,7 +668,6 @@ def section_three_inputs():
size="md",
radius="md",
),
- # Filter Variable
dmc.Grid(
gutter="sm",
align="center",
@@ -711,7 +686,6 @@ def section_three_inputs():
),
],
),
- # Min Value
dmc.Grid(
gutter="sm",
align="center",
@@ -731,7 +705,6 @@ def section_three_inputs():
),
],
),
- # Max Value
dmc.Grid(
gutter="sm",
align="center",
@@ -766,15 +739,12 @@ def section_three():
w="100%",
gap="md",
children=[
- # 标题(保留你现有的封装)
title_with_tooltip(
text="More charts",
tooltip_text=None,
id_button=IdButtons.MORE_CHARTS_LABEL,
),
- # 输入区
section_three_inputs(),
- # 图 1:THREE_VAR
dcc.Loading(
type="circle",
children=dmc.Paper(
@@ -784,7 +754,6 @@ def section_three():
w="100%",
),
),
- # 图 2:TWO_VAR
dcc.Loading(
type="circle",
children=dmc.Paper(
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index a5c61791..e47b9eea 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -50,7 +50,7 @@ def alert():
),
dcc.Interval(
id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
- interval=12 * 500,
+ interval=12 * 1000,
n_intervals=0,
),
],
diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py
index ea6835fd..0c2d210f 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -1,7 +1,6 @@
import dash
from dash import dcc
import dash_mantine_components as dmc
-import dash_bootstrap_components as dbc
from dash_extensions.enrich import Output, Input, State, callback
import numpy as np
@@ -355,12 +354,15 @@ def nv_heatmap(
if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0:
return (
- dbc.Alert(
- "Natural ventilation is not available in this location under these"
- " conditions. Please either select a different outdoor dry-bulb air"
- " temperature range, change the month and hour filter, or increase"
- " thedew-point temperature.",
- color="danger",
+ dmc.Alert(
+ title="Notice",
+ color="red",
+ children=(
+ "Natural ventilation is not available in this location under these "
+ "conditions. Please either select a different outdoor dry-bulb air "
+ "temperature range, change the month and hour filter, or increase "
+ "the dew-point temperature."
+ ),
style={"text-align": "center", "marginTop": "2rem"},
),
)
diff --git a/pages/outdoor.py b/pages/outdoor.py
index c33c8b5c..d3cececb 100644
--- a/pages/outdoor.py
+++ b/pages/outdoor.py
@@ -77,7 +77,6 @@ def inputs_outdoor_comfort():
radius="md",
w="100%",
),
- # Month Range
dmc.Grid(
gutter="sm",
align="center",
@@ -111,7 +110,6 @@ def inputs_outdoor_comfort():
),
],
),
- # Hour Range
dmc.Grid(
gutter="sm",
align="center",
@@ -157,7 +155,6 @@ def outdoor_comfort_chart():
w="100%",
gap="md",
children=[
- # 输出区域
dmc.Paper(
id=ElementIds.OUTDOOR_COMFORT_OUTPUT,
radius="md",
@@ -196,7 +193,7 @@ def outdoor_comfort_chart():
h=400,
),
),
- # Normalize data 开关 + Tooltip
+ # Normalize data
dmc.Group(
align="center",
justify="center",
@@ -442,7 +439,7 @@ def update_tab_utci_category(
[
Input(ElementIds.TAB7_DROPDOWN, "value"),
Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"),
- Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "value"),
+ Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "checked"),
],
[
State(ElementIds.ID_OUTDOOR_DF_STORE, "data"),
diff --git a/pages/psy-chart.py b/pages/psy-chart.py
index 672151dc..a2dfa61b 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -65,7 +65,6 @@ def inputs():
dmc.Grid(
gutter="md",
children=[
- # ① Color By
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -93,7 +92,6 @@ def inputs():
],
),
),
- # ② 时间过滤(月/小时)
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -107,7 +105,6 @@ def inputs():
size="md",
radius="md",
),
- # Month Range:3/6/3
dmc.Grid(
gutter="sm",
align="center",
@@ -146,7 +143,6 @@ def inputs():
),
],
),
- # Hour Range:3/6/3
dmc.Grid(
gutter="sm",
align="center",
@@ -188,7 +184,6 @@ def inputs():
],
),
),
- # ③ 数据过滤(变量/最小/最大)
dmc.GridCol(
span=4,
children=dmc.Stack(
@@ -202,7 +197,6 @@ def inputs():
size="md",
radius="md",
),
- # Filter Variable
dmc.Grid(
gutter="sm",
align="center",
@@ -221,7 +215,6 @@ def inputs():
),
],
),
- # Min Value
dmc.Grid(
gutter="sm",
align="center",
@@ -241,7 +234,6 @@ def inputs():
),
],
),
- # Max Value
dmc.Grid(
gutter="sm",
align="center",
@@ -275,13 +267,11 @@ def layout():
w="100%",
gap="md",
children=[
- # 标题(保留封装)
title_with_link(
text="Psychrometric Chart",
id_button=IdButtons.PSYCHROMETRIC_CHART_CHART,
doc_link=DocLinks.PSYCHROMETRIC_CHART,
),
- # 内容区:输入区 + 图表
dcc.Loading(
type="circle",
children=dmc.Stack(
diff --git a/pages/select.py b/pages/select.py
index fefa5872..738a6818 100644
--- a/pages/select.py
+++ b/pages/select.py
@@ -3,7 +3,6 @@
import re
import dash
-import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
import pandas as pd
import plotly.express as px
@@ -123,12 +122,11 @@ def layout():
def alert():
"""Alert layout for the submit button."""
- return dbc.Alert(
+ return dmc.Alert(
messages_alert["start"],
- color="primary",
+ color="blue",
id=ElementIds.ALERT,
- dismissable=False,
- is_open=True,
+ withCloseButton=False,
style={"maxHeight": "66px"},
)
@@ -138,7 +136,7 @@ def alert():
[
Output(ElementIds.ID_SELECT_META_STORE, "data"),
Output(ElementIds.ID_SELECT_LINES_STORE, "data"),
- Output(ElementIds.ALERT, "is_open"),
+ Output(ElementIds.ALERT, "visible"),
Output(ElementIds.ALERT, "children"),
Output(ElementIds.ALERT, "color"),
],
@@ -172,7 +170,7 @@ def submitted_data(
None,
True,
messages_alert["not_available"],
- "warning",
+ "orange",
)
location_info = get_location_info(
lines, url_store
@@ -182,7 +180,7 @@ def submitted_data(
lines,
True,
messages_alert["success"],
- "success",
+ "green",
)
elif (
@@ -206,7 +204,7 @@ def submitted_data(
lines,
True,
messages_alert["success"],
- "success",
+ "green",
)
else:
return (
@@ -214,7 +212,7 @@ def submitted_data(
None,
True,
messages_alert["invalid_format"],
- "warning",
+ "orange",
)
except (ValueError, IndexError, KeyError) as e:
print(f"Error parsing EPW file: {e}")
@@ -223,7 +221,7 @@ def submitted_data(
None,
True,
messages_alert["wrong_extension"],
- "warning",
+ "orange",
)
raise PreventUpdate
@@ -328,7 +326,7 @@ def display_modal_when_data_clicked(_, click_map, __, opened):
url = re.search(
r'href=[\'"]?([^\'" >]+)', click_map["points"][0]["customdata"][-1]
).group(1)
- return (not opened, url) # 点到点 → 打开 Modal
+ return (not opened, url)
return (opened, "")
From 316c8812a1cf1485bfcfbd5ddbed951eae51cdf5 Mon Sep 17 00:00:00 2001
From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com>
Date: Mon, 15 Sep 2025 18:32:06 +1000
Subject: [PATCH 08/23] fix: fixed the components that ensured cypress testing
could be passed
---
pages/lib/utils.py | 28 ++++++++++------------------
1 file changed, 10 insertions(+), 18 deletions(-)
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index dc20810a..8e534d90 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -4,7 +4,7 @@
import math
import pandas as pd
-from dash import dash_table, dcc
+from dash import html, dash_table, dcc
import dash_mantine_components as dmc
from config import UnitSystem
@@ -159,19 +159,15 @@ def title_with_tooltip(text, tooltip_text, id_button):
label=tooltip_text,
position="right",
withArrow=True,
- multiline=True,
- w=220,
- children=dmc.ActionIcon(
+ children=[
dmc.Image(
id=id_button,
src="/assets/icons/help.png",
alt="help",
w=16,
h=16,
- ),
- variant="transparent",
- size="sm",
- ),
+ )
+ ],
),
],
)
@@ -204,10 +200,8 @@ def title_with_link(
label=tooltip_text,
position="right",
withArrow=True,
- multiline=True,
- w=220,
- children=dmc.Anchor(
- dmc.ActionIcon(
+ children=[
+ html.A(
dmc.Image(
id=id_button,
src="/assets/icons/book.png",
@@ -215,12 +209,10 @@ def title_with_link(
w=16,
h=16,
),
- variant="transparent",
- size="sm",
- ),
- href=doc_link,
- target="_blank",
- ),
+ href=doc_link,
+ target="_blank",
+ )
+ ],
),
],
)
From 682cea43f24b2460670ca42647457f20271f6fc8 Mon Sep 17 00:00:00 2001
From: yluo0664
Date: Wed, 17 Sep 2025 18:36:57 +1000
Subject: [PATCH 09/23] refactor: used AppShell in Dash Mantine to manage the
whole layout - optimized the original simple dmc components - removed the
element ids are no longer needed - removed the unnecessary css style in css
files - removed page_icon.py, moved to layout.py and renamed class name to
NavbarIcons - updated spec.cy.js - reformatted the code
---
assets/layout.css | 12 -
assets/tabs.css | 16 -
main.py | 19 +-
pages/explorer.py | 865 ++++++++++++------------------
pages/lib/global_element_ids.py | 17 +-
pages/lib/layout.py | 583 +++++++++-----------
pages/lib/page_icon.py | 32 --
pages/lib/utils.py | 12 +-
pages/natural_ventilation.py | 328 +++++------
pages/outdoor.py | 207 +++----
pages/psy-chart.py | 326 +++++------
pages/select.py | 4 -
pages/summary.py | 191 +++----
pages/sun.py | 17 +-
pages/t_rh.py | 10 +-
pages/wind.py | 55 +-
tests/node/cypress/e2e/spec.cy.js | 7 +-
17 files changed, 1100 insertions(+), 1601 deletions(-)
delete mode 100644 pages/lib/page_icon.py
diff --git a/assets/layout.css b/assets/layout.css
index 611c3a11..42ed6cb2 100644
--- a/assets/layout.css
+++ b/assets/layout.css
@@ -29,15 +29,3 @@
width: 100%; //or any percentage width you want
}
-/* begin displaying banner below the banner */
-.custom-sidebar .mantine-Drawer-content {
- top: 80px !important;
- height: calc(100vh - 80px) !important;
- position: fixed !important;
-}
-
-.custom-sidebar .mantine-Drawer-overlay {
- top: 80px !important;
- height: calc(100vh - 80px) !important;
-}
-
diff --git a/assets/tabs.css b/assets/tabs.css
index 10a44d92..1813deda 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -287,19 +287,3 @@ p {
z-index: 1000;
}
-/* SegmentedControl adapt to container width */
-#sidebar .mantine-SegmentedControl-root {
- width: 100% !important;
-}
-
-#sidebar .mantine-SegmentedControl-control {
- flex: 1 !important;
- min-width: 0 !important;
-}
-
-/* Response design */
-@media (max-width: 768px) {
- #sidebar {
- width: 280px !important;
- }
-}
\ No newline at end of file
diff --git a/main.py b/main.py
index e74a01af..cdf1b941 100644
--- a/main.py
+++ b/main.py
@@ -1,9 +1,8 @@
from dash import dcc
-from dash_extensions.enrich import Output, Input, callback
import dash_mantine_components as dmc
from app import app
-from pages.lib.layout import banner, footer, build_tabs, sidebar
+from pages.lib.layout import create_collapsible_layout
from config import AppConfig
from pages.lib.global_element_ids import ElementIds
@@ -11,26 +10,12 @@
app.title = AppConfig.TITLE
app.layout = dmc.MantineProvider(
- theme={"colorScheme": "light", "primaryColor": "blue"},
children=[
dcc.Location(id=ElementIds.MAIN_URL, refresh=False),
- sidebar(),
- banner(),
- dmc.Box(id=ElementIds.PAGE_CONTENT, children=build_tabs()),
- footer(),
+ create_collapsible_layout(),
],
)
-
-# callback for survey alert (dbc.Toast)
-@callback(
- Output(ElementIds.ID_MAIN_ALERT_AUTO, "is_open"),
- Input(ElementIds.ID_MAIN_INTERVAL_COMPONENT, "n_intervals"),
-)
-def display_alert(n):
- return n == 1
-
-
if __name__ == "__main__":
app.run(
debug=AppConfig.DEBUG,
diff --git a/pages/explorer.py b/pages/explorer.py
index 442cc367..bc36fc65 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -63,13 +63,10 @@
def section_one_inputs():
"""Return the inputs from section one."""
return dmc.Group(
- align="center",
+ mt="md",
justify="center",
- gap="sm",
- wrap=False,
- w="100%",
children=[
- dmc.Title("Select a variable:", order=4),
+ dmc.Title("Select a variable:", order=5),
dropdown(
id=ElementIds.SEC1_VAR_DROPDOWN,
options=explore_dropdown_names,
@@ -82,8 +79,6 @@ def section_one_inputs():
def section_one():
"""Return the graphs for section one"""
return dmc.Stack(
- w="100%",
- gap="md",
children=[
section_one_inputs(),
# Yearly chart
@@ -97,8 +92,6 @@ def section_one():
children=dmc.Paper(
id=ElementIds.YEARLY_EXPLORE,
p="sm",
- radius="md",
- w="100%",
),
),
title_with_link(
@@ -111,8 +104,6 @@ def section_one():
children=dmc.Paper(
id=ElementIds.QUERY_DAILY,
p="sm",
- radius="md",
- w="100%",
),
),
title_with_link(
@@ -125,8 +116,6 @@ def section_one():
children=dmc.Paper(
id=ElementIds.QUERY_HEATMAP,
p="sm",
- radius="md",
- w="100%",
),
),
title_with_tooltip(
@@ -134,102 +123,83 @@ def section_one():
tooltip_text="count, mean, std, min, max, and percentiles",
id_button=IdButtons.TABLE_EXPLORE,
),
- dmc.Center(
- w="100%",
- children=dmc.Stack(
- gap="sm",
- w="100%",
- maw=600,
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.SEC1_TIME_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3,
- children=dmc.Text("Month Range"),
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.SEC1_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ dmc.SimpleGrid(
+ cols={"base": 1, "md": 3},
+ children=[
+ dmc.Stack(),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.SEC1_TIME_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ # Month
+ dmc.Flex(
+ children=[
+ dmc.Text("Month Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC1_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
+ ),
),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
+ dcc.Checklist(
id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE,
options=[
{"label": "Invert", "value": "invert"}
],
value=[],
),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3,
- children=dmc.Text("Hour Range"),
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.SEC1_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Hour Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC1_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
+ ),
),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
+ dcc.Checklist(
id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE,
options=[
{"label": "Invert", "value": "invert"}
],
value=[],
),
- ),
- ],
- ),
- ],
- ),
- ),
- dmc.Paper(
- id=ElementIds.TABLE_DATA_EXPLORER,
- p="sm",
- radius="md",
- w="100%",
+ ],
+ ),
+ ],
+ ),
+ dmc.Stack(),
+ ],
),
+ dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"),
],
)
@@ -237,211 +207,152 @@ def section_one():
def section_two_inputs():
"""Return all the input forms from section two."""
return dmc.Stack(
- w="100%",
- gap="md",
children=[
title_with_tooltip(
text="Customizable heatmap",
tooltip_text=None,
id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL,
),
- dmc.Grid(
- gutter="md",
+ dmc.SimpleGrid(
+ cols={"base": 1, "md": 3},
children=[
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4,
- children=dmc.Text("Variable:"),
- ),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.SEC2_VAR_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
+ dmc.Stack(
+ children=[
+ dmc.Flex(
+ children=[
+ dmc.Text("Variable:", miw=110, ml="md"),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.SEC2_VAR_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
),
- ],
- ),
- ],
- ),
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.SEC2_TIME_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3,
- children=dmc.Text("Month Range"),
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.SEC2_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
- options=[
- {
- "label": "Invert",
- "value": "invert",
- }
- ],
- value=[],
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3,
- children=dmc.Text("Hour Range"),
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.SEC2_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.SEC2_TIME_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Month Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC2_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
- options=[
- {
- "label": "Invert",
- "value": "invert",
- }
- ],
- value=[],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Hour Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC2_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
),
- ],
- ),
- ],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply filter",
- id=ElementIds.SEC2_DATA_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- # Filter Variable
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4,
- children=dmc.Text("Filter Variable:"),
- ),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.SEC2_DATA_FILTER_VAR,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4,
- children=dmc.Text("Min Value:"),
- ),
- dmc.GridCol(
- span=8,
- children=dmc.NumberInput(
- id=ElementIds.SEC2_MIN_VAL,
- placeholder="Enter a number for the min val",
- value=0,
- step=1,
- w="100%",
- ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.SEC2_DATA_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Filter Variable:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.SEC2_DATA_FILTER_VAR,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4,
- children=dmc.Text("Max Value:"),
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Min Value:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dmc.NumberInput(
+ id=ElementIds.SEC2_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
),
- dmc.GridCol(
- span=8,
- children=dmc.NumberInput(
- id=ElementIds.SEC2_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=100,
- step=1,
- w="100%",
- ),
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Max Value:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dmc.NumberInput(
+ id=ElementIds.SEC2_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
),
- ],
- ),
- ],
- ),
+ ),
+ ],
+ ),
+ ],
),
],
),
@@ -453,22 +364,16 @@ def section_two():
"""Return the two graphs in section two."""
return dmc.Stack(
id=ElementIds.TAB6_SEC2_CONTAINER,
- w="100%",
- gap="md",
- align="center",
children=[
section_two_inputs(),
dcc.Loading(
type="circle",
children=dmc.Paper(
id=ElementIds.CUSTOM_HEATMAP,
- radius="md",
p="sm",
- w="100%",
),
),
dmc.Group(
- gap="sm",
children=[
dmc.CheckboxGroup(
id=ElementIds.NORMALIZE,
@@ -482,9 +387,7 @@ def section_two():
dcc.Loading(
type="circle",
children=dmc.Paper(
- radius="md",
p="sm",
- w="100%",
children=dcc.Graph(
id=ElementIds.CUSTOM_SUMMARY,
config=fig_config,
@@ -498,234 +401,173 @@ def section_two():
def section_three_inputs():
""""""
return dmc.Stack(
- w="100%",
- gap="md",
children=[
- dmc.Grid(
- gutter="md",
+ dmc.SimpleGrid(
+ cols={"base": 1, "md": 3},
children=[
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("X Variable:")
- ),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN,
- options=explore_dropdown_names,
- value="DBT",
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Y Variable:")
- ),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
+ dmc.Stack(
+ children=[
+ dmc.Flex(
+ children=[
+ dmc.Text("X Variable:", miw=110, ml="md"),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN,
+ options=explore_dropdown_names,
+ value="DBT",
),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Color By:")
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Y Variable:", miw=110, ml="md"),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN,
- options=explore_dropdown_names,
- value="glob_hor_rad",
- ),
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Color By:", miw=110, ml="md"),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN,
+ options=explore_dropdown_names,
+ value="glob_hor_rad",
),
- ],
- ),
- ],
- ),
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3, children=dmc.Text("Month Range")
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
- options=[
- {
- "label": "Invert",
- "value": "invert",
- }
- ],
- value=[],
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3, children=dmc.Text("Hour Range")
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Month Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
- options=[
- {
- "label": "Invert",
- "value": "invert",
- }
- ],
- value=[],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Hour Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- ],
- ),
- ],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply filter",
- id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4,
- children=dmc.Text("Filter Variable:"),
- ),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Min Value:")
- ),
- dmc.GridCol(
- span=8,
- children=dmc.NumberInput(
- id=ElementIds.TAB6_SEC3_MIN_VAL,
- placeholder="Enter a number for the min val",
- value=0,
- step=1,
- w="100%",
- ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Filter Variable:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Max Value:")
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Min Value:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dmc.NumberInput(
+ id=ElementIds.TAB6_SEC3_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
),
- dmc.GridCol(
- span=8,
- children=dmc.NumberInput(
- id=ElementIds.TAB6_SEC3_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=100,
- step=1,
- w="100%",
- ),
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Max Value:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dmc.NumberInput(
+ id=ElementIds.TAB6_SEC3_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
),
- ],
- ),
- ],
- ),
+ ),
+ ],
+ ),
+ ],
),
],
),
@@ -736,8 +578,6 @@ def section_three_inputs():
def section_three():
"""Return the two graphs in section three."""
return dmc.Stack(
- w="100%",
- gap="md",
children=[
title_with_tooltip(
text="More charts",
@@ -749,18 +589,14 @@ def section_three():
type="circle",
children=dmc.Paper(
id=ElementIds.THREE_VAR,
- radius="md",
p="sm",
- w="100%",
),
),
dcc.Loading(
type="circle",
children=dmc.Paper(
id=ElementIds.TWO_VAR,
- radius="md",
p="sm",
- w="100%",
),
),
],
@@ -770,7 +606,6 @@ def section_three():
def layout():
"""Return the contents of tab six."""
return dmc.Box(
- className="justify-center",
children=[section_one(), section_two(), section_three()],
)
diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py
index b9f227de..63709a8e 100644
--- a/pages/lib/global_element_ids.py
+++ b/pages/lib/global_element_ids.py
@@ -155,7 +155,6 @@ class ElementIds(str, Enum):
ID_WIND_META_STORE = "meta-store"
ID_WIND_SI_IP_UNIT_STORE = "si-ip-unit-store"
ID_WIND_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input"
- SEASONAL_WIND_ROSE_DOC = "seasonal-wind-rose-doc"
LOADING_ONE = "loading-1"
UPLOAD_DATA = "upload-data"
UPLOAD_DATA_BUTTON = "upload-data-button"
@@ -201,13 +200,9 @@ class ElementIds(str, Enum):
WIND_PROFILE_GRAPH = "wind-profile-graph"
TDB_PROFILE_GRAPH = "tdb-profile-graph"
RH_PROFILE_GRAPH = "rh-profile-graph"
- ALERT_CONTAINER = "alert-container"
ID_LAYOUT_ALERT_AUTO = "alert-auto"
- ID_MAIN_ALERT_AUTO = "alert-auto"
ID_LAYOUT_INTERVAL_COMPONENT = "interval-component"
- ID_MAIN_INTERVAL_COMPONENT = "interval-component"
FOOTER_CONTAINER = "footer-container"
- BANNER = "banner"
BANNER_TITLE = "banner-title"
ID_LAYOUT_BANNER_SUBTITLE = "banner-subtitle"
ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input"
@@ -218,20 +213,13 @@ class ElementIds(str, Enum):
ID_LAYOUT_URL_STORE = "url-store"
ID_LAYOUT_SI_IP_UNIT_STORE = "si-ip-unit-store"
ID_LAYOUT_LINES_STORE = "lines-store"
- TABS_CONTAINER = "tabs-container"
- TABS_PARENT = "tabs-parent"
- TABS = "tabs"
- STORE_CONTAINER = "store-container"
- TABS_CONTENT = "tabs-content"
BURGER_BUTTON = "burger-button"
- SIDE_BAR = "sidebar"
+ NAVBAR = "navbar"
NAV_GROUP_MAIN = "nav-group-main"
NAV_GROUP_CONTROLS = "nav-group-controls"
NAV_DOC_LINK = "nav-doc-link"
- LAYOUT_URL = "url"
SELECT_URL = "url"
MAIN_URL = "url"
- PAGE_CONTENT = "page-content"
NAV = "nav-"
NAV_SUMMARY = "nav-summary"
NAV_T_RH = "nav-t-rh"
@@ -242,3 +230,6 @@ class ElementIds(str, Enum):
NAV_OUTDOOR = "nav-outdoor"
NAV_NATURAL_VENTILATION = "nav-natural-ventilation"
NAV_CHANGELOG = "nav-changelog"
+ APP_SHELL = "appshell"
+ NAVBAR_CONTAINER = "navbar-container"
+ TOOLS_MENU_EXPANDED = "tools-menu-expanded"
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index e47b9eea..d6891777 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -5,279 +5,40 @@
from pages.lib.global_column_names import ColNames
from config import DocLinks, UnitSystem
from pages.lib.global_element_ids import ElementIds
-from pages.lib.page_icon import PageIcon
-def burger_button():
- """create burger button"""
- return dmc.ActionIcon(
- DashIconify(icon="radix-icons:hamburger-menu", width=20),
- id=ElementIds.BURGER_BUTTON,
- size="lg",
- variant="filled",
- color="blue",
- )
-
-
-def alert():
- """Survey toast + periodic timer."""
- return dmc.Stack(
- gap=0,
- children=[
- dmc.Alert(
- [
- "If you have a moment, help us improve Clima and take a ",
- dmc.Anchor(
- "quick user survey",
- href="https://forms.gle/k289zP3R92jdu14M7",
- target="_blank",
- c="white",
- underline=True,
- ),
- "! ☀️",
- ],
- id=ElementIds.ID_LAYOUT_ALERT_AUTO,
- title="CBE Clima User Survey",
- icon="Info",
- color="blue",
- variant="filled",
- withCloseButton=True,
- pos="fixed",
- top="25px",
- right="10px",
- w="400px",
- style={"zIndex": 1002, "display": "none"},
- ),
- dcc.Interval(
- id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
- interval=12 * 1000,
- n_intervals=0,
- ),
- ],
- )
-
-
-def footer():
- """Build the footer at the bottom of the page."""
- white_anchor_style = {
- "underline": True,
- "c": "white",
- "fz": "md",
- "fw": 500,
- "target": "_blank",
+class NavBarIcons:
+ _ICON_MAP = {
+ "Select Weather File": "tabler:upload",
+ "Climate Summary": "tabler:chart-bar",
+ "Temperature and Humidity": "tabler:temperature",
+ "Sun and Clouds": "tabler:sun",
+ "Wind": "tabler:wind",
+ "Psychrometric Chart": "tabler:chart-dots",
+ "Natural Ventilation": "tabler:windmill",
+ "Outdoor Comfort": "tabler:thermometer",
+ "Data Explorer": "tabler:database",
+ "Changelog": "tabler:history",
}
- footer_links = [
- (
- "Version: 0.9.0",
- "https://center-for-the-built-environment.gitbook.io/clima/version/changelog",
- ),
- ("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"),
- (
- "Report issues on GitHub",
- "https://github.com/CenterForTheBuiltEnvironment/clima/issues",
- ),
- (
- "Contact us",
- "https://github.com/CenterForTheBuiltEnvironment/clima/discussions",
- ),
- ("Documentation", "https://center-for-the-built-environment.gitbook.io/clima/"),
- (
- "License",
- "https://center-for-the-built-environment.gitbook.io/clima/#license",
- ),
- ]
-
- return dmc.Box(
- id=ElementIds.FOOTER_CONTAINER,
- p="md",
- m=0,
- c="white",
- bg="#003262",
- display="flex",
- w="100%",
- style={
- "flexWrap": "nowrap",
- "minHeight": "fit-content",
- "alignItems": "flex-start",
- },
- children=[
- # Logo section
- dmc.Box(
- children=[
- dmc.Anchor(
- href="https://cbe.berkeley.edu/",
- children=dmc.Image(
- src="assets/img/cbe-logo.png",
- alt="CBE Logo",
- h=65,
- w="auto",
- fit="contain",
- ),
- ),
- ],
- flex="0 0 33.333333%",
- maw="33.333333%",
- p="30px 15px 10px 25px",
- display="flex",
- style={"justifyContent": "flex-start", "alignItems": "flex-start"},
- ),
- # Content section
- dmc.Box(
- children=[
- dmc.Stack(
- gap="xs",
- children=[
- dcc.Markdown(
- """
- Please cite us:
- Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool:
- A free and open-source web application for climate analysis tailored to sustainable building design.
- Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5).
- """,
- style={
- "fontSize": "16px",
- "lineHeight": 1.5,
- "fontWeight": 500,
- "color": "white",
- },
- ),
- dmc.Group(
- [
- dmc.Anchor(text, href=url, **white_anchor_style)
- for text, url in footer_links
- ],
- gap="sm",
- mt="md",
- ),
- ],
- mt="md",
- ),
- ],
- flex="0 0 66.666667%",
- maw="66.666667%",
- p="0px 15px 10px 15px",
- display="flex",
- style={"justifyContent": "flex-start", "alignItems": "flex-start"},
- ),
- ],
- )
+ SELECT_WEATHER_FILE = _ICON_MAP["Select Weather File"]
+ CLIMATE_SUMMARY = _ICON_MAP["Climate Summary"]
+ TEMPERATURE_AND_HUMIDITY = _ICON_MAP["Temperature and Humidity"]
+ SUN_AND_CLOUDS = _ICON_MAP["Sun and Clouds"]
+ WIND = _ICON_MAP["Wind"]
+ PSYCHROMETRIC_CHART = _ICON_MAP["Psychrometric Chart"]
+ NATURAL_VENTILATION = _ICON_MAP["Natural Ventilation"]
+ OUTDOOR_COMFORT = _ICON_MAP["Outdoor Comfort"]
+ DATA_EXPLORER = _ICON_MAP["Data Explorer"]
+ CHANGELOG = _ICON_MAP["Changelog"]
+ @classmethod
+ def get_icon(cls, page_name):
+ """Get icon for a page name."""
+ return cls._ICON_MAP.get(page_name, "tabler:circle")
-def banner():
- """Top banner rewritten with dash-mantine-components only."""
- return dmc.Box(
- id=ElementIds.BANNER,
- p="md",
- bg="#003262",
- c="white",
- pos="relative",
- style={"zIndex": 1},
- children=[
- dmc.Group(
- justify="space-between",
- align="center",
- wrap="nowrap",
- children=[
- dmc.Group(
- align="center",
- gap="md",
- children=[
- burger_button(),
- dmc.Image(
- src="assets/img/cbe-logo-small.png", h=40, w="auto"
- ),
- dmc.Stack(
- gap=2,
- children=[
- dmc.Title(
- "CBE Clima Tool",
- id=ElementIds.BANNER_TITLE,
- order=2,
- fw=500,
- ff="'Open Sans', sans-serif",
- lh=1.1,
- c="white",
- ),
- dmc.Text(
- "Current Location: N/A",
- id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
- size="sm",
- opacity=0.85,
- ff="'Poppins', sans-serif",
- fw=400,
- h=25,
- style={"overflow": "hidden"},
- c="white",
- ),
- ],
- ),
- ],
- ),
- ],
- )
- ],
- )
-
-def sidebar():
- """create sidebar"""
- return dmc.Drawer(
- id=ElementIds.SIDE_BAR,
- title=dmc.Group(
- [
- dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"),
- dmc.Text("CBE Clima Tool", fw=600),
- ]
- ),
- size="300px",
- zIndex=1001,
- opened=False,
- styles={
- "content": {
- "top": "80px",
- "left": 0,
- "position": "fixed",
- "borderRadius": "0 8px 8px 0",
- "boxShadow": "2px 0 8px rgba(0,0,0,0.1)",
- "backgroundColor": "#f8f9fa",
- "padding": "16px",
- },
- "overlay": {
- "top": "80px",
- "left": 0,
- "height": "calc(100vh - 80px)",
- "position": "fixed",
- },
- "header": {
- "borderBottom": "1px solid #e9ecef",
- "paddingBottom": "12px",
- "marginBottom": "16px",
- "position": "sticky",
- "top": 0,
- "backgroundColor": "#f8f9fa",
- "zIndex": 1002,
- },
- "title": {
- "fontWeight": 600,
- "fontSize": "18px",
- "paddingRight": 30,
- "position": "relative",
- "zIndex": 1001,
- },
- "body": {
- "padding": 0,
- "overflowY": "auto",
- "maxHeight": "calc(100vh - 80px)",
- "position": "relative",
- "zIndex": 1,
- },
- },
- children=[dmc.Stack(gap=0, children=build_sidebar_nav_items())],
- )
-
-
-def build_sidebar_nav_items():
+def create_navbar():
nav_link_styles = {
"root": {
"borderRadius": "6px",
@@ -295,31 +56,28 @@ def build_sidebar_nav_items():
}
}
- # === Secondary Menu ===
+ # Secondary Menu
sub_links = [
dmc.NavLink(
label=page[ColNames.NAME],
leftSection=DashIconify(
- icon=PageIcon.get_icon(page[ColNames.NAME]), width=20
+ icon=NavBarIcons.get_icon(page[ColNames.NAME]), width=20
),
href=page[ColNames.PATH],
id=f"nav-{page[ColNames.PATH].replace('/', '')}",
active=False,
- mb="xs",
+ mb="2px",
styles=nav_link_styles,
)
for page in dash.page_registry.values()
if page[ColNames.NAME] not in ["404"]
]
- # Primary Menu
parent_group = dmc.NavLink(
label="Pages Menu",
- leftSection=DashIconify(icon="tabler:list-details", width=20),
children=sub_links,
id=ElementIds.NAV_GROUP_MAIN,
variant="light",
- childrenOffset=18,
)
segmented_control_styles = {
@@ -328,60 +86,211 @@ def build_sidebar_nav_items():
}
controls_stack = dmc.Stack(
- gap="sm",
- px=0,
+ gap="xs",
py="xs",
children=[
dmc.SegmentedControl(
id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT,
value="local",
+ color="blue",
data=[
{"label": "Global Value Ranges", "value": "global"},
{"label": "Local Value Ranges", "value": "local"},
],
radius="md",
size="sm",
- w="100%",
styles=segmented_control_styles,
),
dmc.SegmentedControl(
id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT,
value=UnitSystem.SI,
+ color="blue",
data=[
{"label": UnitSystem.SI.upper(), "value": UnitSystem.SI},
{"label": UnitSystem.IP.upper(), "value": UnitSystem.IP},
],
radius="md",
size="sm",
- w="100%",
styles=segmented_control_styles,
),
],
)
- # Primary Menu
+ # Tools
controls_group = dmc.NavLink(
label="Tools Menu",
- leftSection=DashIconify(icon="tabler:settings", width=20),
children=[controls_stack],
id=ElementIds.NAV_GROUP_CONTROLS,
variant="light",
- childrenOffset=18,
+ childrenOffset=8,
)
- # Primary Menu - Documentation
+ # Documentation
doc_link = dmc.NavLink(
label="Documentation",
- leftSection=DashIconify(icon="tabler:file-text", width=20),
href=DocLinks.MAIN.value,
target="_blank",
id=ElementIds.NAV_DOC_LINK,
variant="light",
)
- return [parent_group, controls_group, doc_link]
+
+ return dmc.ScrollArea(
+ children=dmc.Stack(gap="xs", children=[parent_group, controls_group, doc_link]),
+ pr="xs",
+ )
+
+
+def create_header():
+ return dmc.Group(
+ [
+ dmc.Burger(
+ id=ElementIds.BURGER_BUTTON,
+ size="sm",
+ opened=True,
+ color="blue",
+ ),
+ dmc.Anchor(
+ href="/",
+ children=dmc.Image(src="assets/img/cbe-logo-small.png", h=40, flex=0),
+ ),
+ dmc.Stack(
+ gap="xs",
+ children=[
+ dmc.Title(
+ "CBE Clima Tool",
+ id=ElementIds.BANNER_TITLE,
+ order=2,
+ fw=500,
+ ff="'Open Sans', sans-serif",
+ lh=1.1,
+ c="white",
+ ),
+ dmc.Text(
+ "Current Location: N/A",
+ id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
+ size="sm",
+ opacity=0.85,
+ ff="'Poppins', sans-serif",
+ fw=400,
+ h=25,
+ style={"overflow": "hidden"},
+ c="white",
+ ),
+ ],
+ ),
+ dmc.Alert(
+ [
+ "If you have a moment, help us improve Clima and take a ",
+ dmc.Anchor(
+ "quick user survey",
+ href="https://forms.gle/k289zP3R92jdu14M7",
+ target="_blank",
+ c="white",
+ underline=True,
+ ),
+ "! ☀️",
+ ],
+ id=ElementIds.ID_LAYOUT_ALERT_AUTO,
+ title="CBE Clima User Survey",
+ icon=dmc.ThemeIcon(
+ DashIconify(icon="tabler:info-circle", color="white"),
+ ),
+ color="blue",
+ variant="filled",
+ withCloseButton=True,
+ style={"display": "none"},
+ ),
+ ],
+ h="100%",
+ px="md",
+ )
+
+
+def create_footer():
+ white_anchor_style = {
+ "underline": True,
+ "c": "white",
+ "fz": "md",
+ "fw": 500,
+ "target": "_blank",
+ }
+
+ footer_links = [
+ (
+ "Version: 0.9.0",
+ "https://center-for-the-built-environment.gitbook.io/clima/version/changelog",
+ ),
+ ("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"),
+ (
+ "Report issues on GitHub",
+ "https://github.com/CenterForTheBuiltEnvironment/clima/issues",
+ ),
+ (
+ "Contact us",
+ "https://github.com/CenterForTheBuiltEnvironment/clima/discussions",
+ ),
+ ("Documentation", "https://center-for-the-built-environment.gitbook.io/clima/"),
+ (
+ "License",
+ "https://center-for-the-built-environment.gitbook.io/clima/#license",
+ ),
+ ]
+
+ return dmc.Group(
+ [
+ dmc.Anchor(
+ href="https://cbe.berkeley.edu/",
+ children=dmc.Image(
+ src="assets/img/cbe-logo.png",
+ alt="CBE Logo",
+ h=40,
+ w="auto",
+ fit="contain",
+ ),
+ ),
+ dmc.Stack(
+ gap="xs",
+ children=[
+ dcc.Markdown(
+ """
+ Please cite us: Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5).
+ """,
+ style={
+ "fontSize": "16px",
+ "lineHeight": 1.3,
+ "fontWeight": 400,
+ "color": "white",
+ "textAlign": "left",
+ },
+ ),
+ dmc.Group(
+ [
+ dmc.Anchor(text, href=url, **white_anchor_style)
+ for text, url in footer_links
+ ],
+ gap="sm",
+ wrap="wrap",
+ justify="flex-start",
+ ),
+ ],
+ flex=1,
+ align="flex-start",
+ ml="xl",
+ ),
+ ],
+ id=ElementIds.FOOTER_CONTAINER,
+ p="sm",
+ c="white",
+ bg="#003262",
+ h="100%",
+ gap="xl",
+ justify="flex-start",
+ align="center",
+ px="lg",
+ )
-def store():
+def create_stores():
return dmc.Box(
id=ElementIds.STORE,
children=[
@@ -390,60 +299,106 @@ def store():
dcc.Store(id=ElementIds.ID_LAYOUT_URL_STORE, storage_type="session"),
dcc.Store(id=ElementIds.ID_LAYOUT_SI_IP_UNIT_STORE, storage_type="session"),
dcc.Store(id=ElementIds.ID_LAYOUT_LINES_STORE, storage_type="session"),
+ dcc.Store(
+ id=ElementIds.TOOLS_MENU_EXPANDED, data=False, storage_type="session"
+ ),
+ dcc.Interval(
+ id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
+ interval=12 * 1000,
+ n_intervals=0,
+ ),
],
)
-def build_tabs():
- return dmc.Box(
- id=ElementIds.TABS_CONTAINER,
- m=0,
- mt=0,
- children=[
- store(),
- dmc.Box(
- id=ElementIds.TABS_CONTENT,
+def create_collapsible_layout():
+ return dmc.AppShell(
+ [
+ dmc.AppShellHeader(
+ create_header(),
+ bg="#003262",
+ ),
+ dmc.AppShellNavbar(
+ id=ElementIds.NAVBAR,
+ children=create_navbar(),
p="md",
- children=[
- alert(),
- dash.page_container,
- ],
+ bg="#f8f9fa",
+ ),
+ # including main and footer
+ dmc.AppShellMain(
+ dmc.ScrollArea(
+ children=[
+ create_stores(),
+ dash.page_container,
+ create_footer(),
+ ],
+ ),
+ pos="relative",
+ style={
+ "zIndex": 1,
+ "@media (max-width: 768px)": {
+ "left": "0px",
+ },
+ },
),
],
+ header={"height": 80},
+ navbar={
+ "width": 180,
+ "breakpoint": "sm",
+ "collapsed": {"mobile": True, "desktop": False},
+ "id": ElementIds.NAVBAR_CONTAINER,
+ },
+ padding="md",
+ id=ElementIds.APP_SHELL,
)
@callback(
- Output(ElementIds.SIDE_BAR, "opened"),
- Input(ElementIds.BURGER_BUTTON, "n_clicks"),
- State(ElementIds.SIDE_BAR, "opened"),
- prevent_initial_call=True,
+ [
+ Output(ElementIds.APP_SHELL, "navbar"),
+ Output(ElementIds.TOOLS_MENU_EXPANDED, "data"),
+ ],
+ [
+ Input(ElementIds.BURGER_BUTTON, "opened"),
+ Input(ElementIds.NAV_GROUP_CONTROLS, "opened"),
+ Input(ElementIds.NAV_GROUP_MAIN, "opened"),
+ ],
+ [
+ State(ElementIds.APP_SHELL, "navbar"),
+ State(ElementIds.TOOLS_MENU_EXPANDED, "data"),
+ ],
)
-def toggle_sidebar(n_clicks, opened):
- return not opened if n_clicks else opened
-
+def toggle_navbar_and_width(
+ burger_opened, tools_opened, pages_opened, navbar, tools_expanded
+):
+ navbar["collapsed"] = {"mobile": not burger_opened, "desktop": not burger_opened}
+
+ WIDTHS = {"default": 180, "pages": 300, "tools": 400}
+
+ if tools_opened is not None:
+ tools_expanded = tools_opened
+ navbar["width"] = (
+ WIDTHS["tools"]
+ if tools_opened
+ else (WIDTHS["pages"] if pages_opened else WIDTHS["default"])
+ )
+ elif pages_opened is not None:
+ navbar["width"] = WIDTHS["pages"] if pages_opened else WIDTHS["default"]
-@callback(
- Output(ElementIds.SIDE_BAR, "opened", allow_duplicate=True),
- Input(ElementIds.LAYOUT_URL, "pathname"),
- prevent_initial_call="initial_duplicate",
-)
-def close_sidebar_on_navigation(pathname):
- return False
+ return navbar, tools_expanded
-# Callback to set active state for navigation links based on current URL
@callback(
[
Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active")
for page in dash.page_registry.values()
if page[ColNames.NAME] not in ["404"]
],
- Input(ElementIds.LAYOUT_URL, "pathname"),
+ Input(ElementIds.MAIN_URL, "pathname"),
prevent_initial_call=True,
)
def update_nav_active_state(pathname):
- """Update active state of navigation links based on current URL pathname"""
return [
pathname == page[ColNames.PATH]
for page in dash.page_registry.values()
@@ -457,7 +412,6 @@ def update_nav_active_state(pathname):
prevent_initial_call=True,
)
def show_alert_after_delay(n_intervals):
- """Show alert after 6 seconds, then hide after 5 more seconds"""
base_style = {
"position": "fixed",
"top": "25px",
@@ -466,7 +420,6 @@ def show_alert_after_delay(n_intervals):
"zIndex": 1002,
}
- # Determine display status based on the number of intervals
if n_intervals == 1:
return {**base_style, "display": "block"}
else:
diff --git a/pages/lib/page_icon.py b/pages/lib/page_icon.py
deleted file mode 100644
index 6515a6b1..00000000
--- a/pages/lib/page_icon.py
+++ /dev/null
@@ -1,32 +0,0 @@
-class PageIcon:
- """Page icon mappings - optimized with dictionary."""
-
- # Page Name to icon Mapping
- _ICON_MAP = {
- "Select Weather File": "tabler:upload",
- "Climate Summary": "tabler:chart-bar",
- "Temperature and Humidity": "tabler:temperature",
- "Sun and Clouds": "tabler:sun",
- "Wind": "tabler:wind",
- "Psychrometric Chart": "tabler:chart-dots",
- "Natural Ventilation": "tabler:windmill",
- "Outdoor Comfort": "tabler:thermometer",
- "Data Explorer": "tabler:database",
- "Changelog": "tabler:history",
- }
-
- SELECT_WEATHER_FILE = _ICON_MAP["Select Weather File"]
- CLIMATE_SUMMARY = _ICON_MAP["Climate Summary"]
- TEMPERATURE_AND_HUMIDITY = _ICON_MAP["Temperature and Humidity"]
- SUN_AND_CLOUDS = _ICON_MAP["Sun and Clouds"]
- WIND = _ICON_MAP["Wind"]
- PSYCHROMETRIC_CHART = _ICON_MAP["Psychrometric Chart"]
- NATURAL_VENTILATION = _ICON_MAP["Natural Ventilation"]
- OUTDOOR_COMFORT = _ICON_MAP["Outdoor Comfort"]
- DATA_EXPLORER = _ICON_MAP["Data Explorer"]
- CHANGELOG = _ICON_MAP["Changelog"]
-
- @classmethod
- def get_icon(cls, page_name):
- """Get icon for a page name."""
- return cls._ICON_MAP.get(page_name, "tabler:circle")
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index 8e534d90..53d9c26f 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -149,7 +149,6 @@ def generate_custom_inputs_psy(
def title_with_tooltip(text, tooltip_text, id_button):
if tooltip_text:
return dmc.Group(
- gap="xs",
align="center",
mt="md",
px="md",
@@ -172,15 +171,7 @@ def title_with_tooltip(text, tooltip_text, id_button):
],
)
else:
- return dmc.Group(
- gap="xs",
- align="center",
- mt="md",
- px="md",
- children=[
- dmc.Title(text, order=3),
- ],
- )
+ return dmc.Title(text, order=3, mt="md", px="md")
def title_with_link(
@@ -190,7 +181,6 @@ def title_with_link(
doc_link: str = "",
):
return dmc.Group(
- gap="xs",
align="center",
mt="md",
px="md",
diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py
index 0c2d210f..4499cc8d 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -39,7 +39,7 @@
def layout():
- return dmc.Stack(id=ElementIds.MAIN_NV_SECTION, gap="md")
+ return dmc.Stack(id=ElementIds.MAIN_NV_SECTION)
@callback(
@@ -67,14 +67,11 @@ def update_layout(si_ip):
type="circle",
children=dmc.Paper(
id=ElementIds.NV_HEATMAP_CHART,
- p="md",
- mt="md",
+ p="sm",
),
),
dmc.Group(
- align="center",
justify="center",
- gap="sm",
children=[
dmc.Switch(
id=ElementIds.SWITCHES_INPUT,
@@ -98,198 +95,161 @@ def update_layout(si_ip):
type="circle",
children=dmc.Paper(
id=ElementIds.NV_BAR_CHART,
- p="md",
- mt="md",
+ p="sm",
),
),
]
def inputs_tab(t_min, t_max, d_set):
- return dmc.Grid(
- gutter="xl",
+ return dmc.SimpleGrid(
+ cols={"base": 1, "md": 3},
children=[
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- children=[
- dmc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.NV_DBT_FILTER,
- variant="link",
- size="md",
- fullWidth=True,
- n_clicks=1,
- ),
- dmc.Text(
- "Outdoor dry-bulb air temperature range",
- size="md",
- ),
- dmc.Group(
- gap="xl",
- grow=True,
- children=[
- dmc.Text("Min Value:", size="md"),
- dmc.NumberInput(
- id=ElementIds.NV_TDB_MIN_VAL,
- placeholder="Enter a number for the min val",
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ color="primary",
+ id=ElementIds.NV_DBT_FILTER,
+ variant="link",
+ size="md",
+ fullWidth=True,
+ n_clicks=1,
+ ),
+ dmc.Text(
+ "Outdoor dry-bulb air temperature range",
+ size="md",
+ ml="md",
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Min Value:", size="md", ml="md"),
+ dmc.NumberInput(
+ id=ElementIds.NV_TDB_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ step=1,
+ value=t_min,
+ ),
+ ],
+ ),
+ # Max
+ dmc.Flex(
+ children=[
+ dmc.Text("Max Value:", size="md", ml="md"),
+ dmc.NumberInput(
+ id=ElementIds.NV_TDB_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=t_max,
+ step=1,
+ ),
+ ],
+ ),
+ ],
+ ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ color="primary",
+ id=ElementIds.NV_MONTH_HOUR_FILTER,
+ variant="link",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Month Range", size="md", miw=120),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.NV_MONTH_SLIDER,
+ min=1,
+ max=12,
step=1,
- value=t_min,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- ],
- ),
- dmc.Group(
- gap="xl",
- grow=True,
- children=[
- dmc.Text("Max Value:", size="md"),
- dmc.NumberInput(
- id=ElementIds.NV_TDB_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=t_max,
+ ),
+ dcc.Checklist(
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
+ id=ElementIds.INVERT_MONTH_NV,
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Hour Range", size="md", miw=120),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.NV_HOUR_SLIDER,
+ min=0,
+ max=24,
step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
),
- ],
- ),
- ],
- ),
- ),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- children=[
- dmc.Button(
- "Apply month and hour filter",
- color="primary",
- id=ElementIds.NV_MONTH_HOUR_FILTER,
- variant="link",
- size="md",
- fullWidth=True,
- radius="sm",
- ),
- dmc.Grid(
- align="center",
- gutter="sm",
- children=[
- dmc.GridCol(
- span=3,
- children=dmc.Text("Month Range", size="md"),
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.NV_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_NV,
- ),
- ),
- ],
- ),
- dmc.Grid(
- align="center",
- gutter="sm",
- children=[
- dmc.GridCol(
- span=3,
- children=dmc.Text("Hour Range", size="md"),
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.NV_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_NV,
- ),
- ),
- ],
- ),
- ],
- ),
+ ),
+ dcc.Checklist(
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
+ id=ElementIds.INVERT_HOUR_NV,
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- children=[
- dmc.Button(
- "Apply filter",
- color="primary",
- id=ElementIds.NV_DPT_FILTER,
- mb="xs",
- variant="link",
- size="md",
- fullWidth=True,
- n_clicks=0,
- disabled=True,
- ),
- dcc.Checklist(
- options=[
- {
- "label": (
- "Avoid condensation with radiant systems: If the"
- " outdoor dew point temperature is below the"
- " radiant system surface temperature, the data"
- " point is not plot."
- ),
- "value": 1,
- },
- ],
- value=[],
- id=ElementIds.ENABLE_CONDENSATION,
- ),
- dmc.Group(
- align="center",
- gap="sm",
- grow=True,
- children=[
- dmc.Text("Surface temperature:", size="md"),
- dmc.NumberInput(
- id=ElementIds.NV_DPT_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=d_set,
- step=1,
- w="50%",
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ color="primary",
+ id=ElementIds.NV_DPT_FILTER,
+ mb="xs",
+ variant="link",
+ size="md",
+ fullWidth=True,
+ n_clicks=0,
+ disabled=True,
+ ),
+ dcc.Checklist(
+ options=[
+ {
+ "label": (
+ "Avoid condensation with radiant systems: If the "
+ "outdoor dew point temperature is below the radiant "
+ "system surface temperature, the data point is not plot."
),
- ],
- ),
- ],
- ),
+ "value": 1,
+ },
+ ],
+ value=[],
+ id=ElementIds.ENABLE_CONDENSATION,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Surface temperature:", size="md"),
+ dmc.NumberInput(
+ id=ElementIds.NV_DPT_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=d_set,
+ step=1,
+ w="50%",
+ ),
+ ],
+ ),
+ ],
),
],
)
diff --git a/pages/outdoor.py b/pages/outdoor.py
index d3cececb..fcc814f4 100644
--- a/pages/outdoor.py
+++ b/pages/outdoor.py
@@ -36,115 +36,94 @@
def inputs_outdoor_comfort():
- return dmc.Grid(
- gutter="md",
+ return dmc.SimpleGrid(
+ cols={"base": 1, "md": 2},
children=[
- dmc.GridCol(
- span=6,
- children=dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(span=3, children=dmc.Text("Select a scenario:")),
- dmc.GridCol(
- span=6,
- children=dropdown(
- id=ElementIds.TAB7_DROPDOWN,
- options=outdoor_dropdown_names,
- value="utci_Sun_Wind",
- persistence=True,
- persistence_type="session",
+ dmc.Stack(
+ children=[
+ dmc.Flex(
+ children=[
+ dmc.Text("Select a scenario:", miw=140, ml="md"),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.TAB7_DROPDOWN,
+ options=outdoor_dropdown_names,
+ value="utci_Sun_Wind",
+ persistence=True,
+ persistence_type="session",
+ ),
),
- ),
- dmc.GridCol(
- span=3,
- children=dmc.Paper(id=ElementIds.IMAGE_SELECTION),
- ),
- ],
- ),
+ dmc.Center(
+ children=dmc.Paper(id=ElementIds.IMAGE_SELECTION)
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=6,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- w="100%",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(span=2, children=dmc.Text("Month Range")),
- dmc.GridCol(
- span=7,
- children=dcc.RangeSlider(
- id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(span=2, children=dmc.Text("Hour Range")),
- dmc.GridCol(
- span=7,
- children=dcc.RangeSlider(
- id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Month Range", miw=120),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Hour Range", miw=120),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
),
- ],
- ),
- ],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
+ ),
+ ],
+ ),
+ ],
),
],
)
@@ -152,14 +131,10 @@ def inputs_outdoor_comfort():
def outdoor_comfort_chart():
return dmc.Stack(
- w="100%",
- gap="md",
children=[
dmc.Paper(
id=ElementIds.OUTDOOR_COMFORT_OUTPUT,
- radius="md",
p="sm",
- w="100%",
),
# UTCI heatmap chart
title_with_link(
@@ -171,10 +146,7 @@ def outdoor_comfort_chart():
type="circle",
children=dmc.Paper(
id=ElementIds.UTCI_HEATMAP,
- radius="md",
p="sm",
- w="100%",
- h=400,
),
),
# UTCI thermal stress chart
@@ -187,21 +159,15 @@ def outdoor_comfort_chart():
type="circle",
children=dmc.Paper(
id=ElementIds.UTCI_CATEGORY_HEATMAP,
- radius="md",
p="sm",
- w="100%",
- h=400,
),
),
# Normalize data
dmc.Group(
- align="center",
justify="center",
- gap="sm",
children=[
dmc.Switch(
id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT,
- label="",
checked=True,
size="md",
color="blue",
@@ -221,9 +187,7 @@ def outdoor_comfort_chart():
type="circle",
children=dmc.Paper(
id=ElementIds.UTCI_SUMMARY_CHART,
- radius="md",
p="sm",
- w="100%",
),
),
],
@@ -232,14 +196,11 @@ def outdoor_comfort_chart():
def layout():
return dmc.Stack(
- w="100%",
- gap="md",
+ mt="md",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- w="100%",
- gap="md",
children=[
inputs_outdoor_comfort(),
outdoor_comfort_chart(),
diff --git a/pages/psy-chart.py b/pages/psy-chart.py
index a2dfa61b..1793e076 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -59,202 +59,150 @@
def inputs():
""""""
return dmc.Stack(
- w="100%",
- gap="md",
children=[
- dmc.Grid(
- gutter="md",
+ dmc.SimpleGrid(
+ cols={"base": 1, "md": 3},
+ spacing="md",
children=[
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Color By:")
- ),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.PSY_COLOR_BY_DROPDOWN,
- options=psy_dropdown_names,
- value="Frequency",
- persistence=True,
- persistence_type="session",
- ),
- ),
- ],
+ dmc.Flex(
+ align="center",
+ mt="md",
+ children=[
+ dmc.Text("Color By:", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.PSY_COLOR_BY_DROPDOWN,
+ options=psy_dropdown_names,
+ value="Frequency",
+ persistence=True,
+ persistence_type="session",
),
- ],
- ),
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.MONTH_HOUR_FILTER,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3, children=dmc.Text("Month Range")
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.PSY_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.MONTH_HOUR_FILTER,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Month Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.PSY_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_MONTH_PSY,
- options=[
- {
- "label": "Invert",
- "value": "invert",
- }
- ],
- value=[],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_PSY,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Hour Range", miw=110),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.PSY_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=3, children=dmc.Text("Hour Range")
- ),
- dmc.GridCol(
- span=6,
- children=dcc.RangeSlider(
- id=ElementIds.PSY_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
- ),
- dmc.GridCol(
- span=3,
- children=dcc.Checklist(
- id=ElementIds.INVERT_HOUR_PSY,
- options=[
- {
- "label": "Invert",
- "value": "invert",
- }
- ],
- value=[],
- ),
- ),
- ],
- ),
- ],
- ),
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_PSY,
+ options=[
+ {"label": "Invert", "value": "invert"}
+ ],
+ value=[],
+ ),
+ ],
+ ),
+ ],
),
- dmc.GridCol(
- span=4,
- children=dmc.Stack(
- gap="sm",
- children=[
- dmc.Button(
- "Apply filter",
- id=ElementIds.DATA_FILTER,
- variant="filled",
- color="blue",
- size="md",
- radius="md",
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4,
- children=dmc.Text("Filter Variable:"),
+ dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.DATA_FILTER,
+ variant="filled",
+ color="blue",
+ size="md",
+ fullWidth=True,
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Filter Variable:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dropdown(
+ id=ElementIds.PSY_VAR_DROPDOWN,
+ options=dropdown_names,
+ value=ColNames.RH,
),
- dmc.GridCol(
- span=8,
- children=dropdown(
- id=ElementIds.PSY_VAR_DROPDOWN,
- options=dropdown_names,
- value=ColNames.RH,
- ),
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Min Value:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dmc.NumberInput(
+ id=ElementIds.PSY_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
+ size="md",
),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Min Value:")
+ ),
+ ],
+ ),
+ dmc.Flex(
+ children=[
+ dmc.Text("Max Value:", miw=130),
+ dmc.Stack(
+ flex=1,
+ children=dmc.NumberInput(
+ id=ElementIds.PSY_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
+ size="md",
),
- dmc.GridCol(
- span=8,
- children=dmc.NumberInput(
- id=ElementIds.PSY_MIN_VAL,
- placeholder="Enter a number for the min val",
- value=0,
- step=1,
- w="100%",
- ),
- ),
- ],
- ),
- dmc.Grid(
- gutter="sm",
- align="center",
- children=[
- dmc.GridCol(
- span=4, children=dmc.Text("Max Value:")
- ),
- dmc.GridCol(
- span=8,
- children=dmc.NumberInput(
- id=ElementIds.PSY_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=100,
- step=1,
- w="100%",
- ),
- ),
- ],
- ),
- ],
- ),
+ ),
+ ],
+ ),
+ ],
),
],
),
@@ -264,8 +212,6 @@ def inputs():
def layout():
return dmc.Stack(
- w="100%",
- gap="md",
children=[
title_with_link(
text="Psychrometric Chart",
@@ -275,15 +221,11 @@ def layout():
dcc.Loading(
type="circle",
children=dmc.Stack(
- w="100%",
- gap="md",
children=[
inputs(),
dmc.Paper(
id=ElementIds.PSYCH_CHART,
- radius="md",
p="sm",
- w="100%",
),
],
),
diff --git a/pages/select.py b/pages/select.py
index 738a6818..4a8e2a4f 100644
--- a/pages/select.py
+++ b/pages/select.py
@@ -56,7 +56,6 @@ def layout():
id=ElementIds.UPLOAD_DATA_BUTTON,
variant="outline",
color="gray",
- radius="sm",
style={"borderStyle": "dashed", "borderRadius": "5px"},
styles={"label": {"fontWeight": 400}},
),
@@ -102,13 +101,10 @@ def layout():
),
],
justify="flex-end",
- gap="md",
- w="100%",
),
],
),
],
- w="100%",
mx=0,
px=0,
py="md",
diff --git a/pages/summary.py b/pages/summary.py
index 8dc257ea..f96f237d 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -1,10 +1,11 @@
import dash
-from dash.exceptions import PreventUpdate
-from dash_extensions.enrich import dcc, Output, Input, State, callback
+import pandas as pd
import dash_mantine_components as dmc
import plotly.graph_objects as go
import requests
+from dash.exceptions import PreventUpdate
+from dash_extensions.enrich import dcc, Output, Input, State, callback
from config import PageUrls, DocLinks, PageInfo, UnitSystem
from pages.lib.charts_summary import world_map
from pages.lib.extract_df import get_data
@@ -38,7 +39,7 @@ def layout():
fluid=True,
px="md",
children=[
- dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, gap="md"),
+ dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER),
],
)
@@ -57,19 +58,17 @@ def update_layout(si_ip):
return dmc.Stack(
id=ElementIds.TAB2_SCE1_CONTAINER,
- gap="xl",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
id=ElementIds.LOCATION_INFO,
- gap="xs",
- p="md",
+ p="sm",
),
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.WORLD_MAP, gap=0),
+ children=dmc.Stack(id=ElementIds.WORLD_MAP),
),
title_with_tooltip(
text="Download",
@@ -81,7 +80,6 @@ def update_layout(si_ip):
children=dmc.Group(
align="center",
justify="flex-start",
- gap="md",
children=[
dmc.Button(
"Download EPW",
@@ -105,11 +103,10 @@ def update_layout(si_ip):
id_button=IdButtons.HDD_CDD_CHART,
doc_link=DocLinks.DEGREE_DAYS,
),
- dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD, gap=0),
+ dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD),
dmc.Group(
align="center",
justify="center",
- gap="md",
children=[
dmc.Text("Heating degree day (HDD) setpoint"),
dmc.NumberInput(
@@ -141,7 +138,7 @@ def update_layout(si_ip):
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER, gap=0),
+ children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER),
),
title_with_link(
text="Climate Profiles",
@@ -197,7 +194,7 @@ def update_map(meta):
def update_location_info(ts, df, meta, si_ip):
"""Update the contents of tab two. Passing in the general info (df, meta)."""
location = f"Location: {meta[ColNames.CITY]}, {meta[ColNames.COUNTRY]}"
- lon = f"Longitude: {meta[ColNames.LON]}"
+ lon = f" Longitude: {meta[ColNames.LON]}"
lat = f"Latitude: {meta[ColNames.LAT]}"
site_elevation = round(float(meta[ColNames.SITE_ELEVATION]), 2)
@@ -249,7 +246,6 @@ def update_location_info(ts, df, meta, si_ip):
coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}"
return dmc.Stack(
- gap=4,
children=[
dmc.Text(location, fw=700),
dmc.Text(lon),
@@ -269,114 +265,105 @@ def update_location_info(ts, df, meta, si_ip):
@callback(
[
Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"),
- Output(ElementIds.WARNING_CDD_HIGHER_HDD, "children"),
+ Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is-open"),
],
[
Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"),
- Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks_timestamp"),
+ Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks"),
],
[
State(ElementIds.ID_SUMMARY_DF_STORE, "data"),
State(ElementIds.ID_SUMMARY_META_STORE, "data"),
State(ElementIds.INPUT_HDD_SET_POINT, "value"),
State(ElementIds.INPUT_CDD_SET_POINT, "value"),
- State(ElementIds.SUBMIT_SET_POINTS, "n_clicks"),
State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"),
],
+ prevent_initial_call=False,
)
-def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ip):
- """Update the contents of tab two. Passing in the general info (df, meta)."""
+def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip):
+ """Redraw HDD/CDD chart only when Submit is clicked."""
- ctx = dash.callback_context
-
- if (
- ctx.triggered[0][ColNames.PROP_ID] == "submit-set-points.n_clicks_timestamp"
- or n_clicks is None
- ):
- hdd_setpoint = hdd_value
- cdd_setpoint = cdd_value
-
- warning_setpoint = cdd_setpoint < hdd_setpoint
-
- color_hdd = "red"
- color_cdd = "dodgerblue"
-
- hdd_array = []
- cdd_array = []
- months = df[ColNames.MONTH_NAMES].unique()
-
- for i in range(1, 13):
- query_month = "month=="
-
- # calculates HDD per month
- query = query_month + str(i) + " and DBT<=" + str(hdd_setpoint)
- a = df.query(query)[ColNames.DBT].sub(hdd_setpoint)
- hdd = a.sum(axis=0, skipna=True) / 24
- hdd_array.append(int(hdd))
-
- # calculates CDD per month
- query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint)
- a = df.query(query)[ColNames.DBT].sub(cdd_setpoint)
- cdd = a.sum(axis=0, skipna=True) / 24
- cdd_array.append(int(cdd))
-
- trace1 = go.Bar(
- x=months,
- y=hdd_array,
- name="Heating Degree Days",
- marker_color=color_hdd,
- customdata=[abs(ele) for ele in hdd_array],
- hovertemplate=(
- " Heating Degree Days:
%{customdata} per month
"
- ),
- )
- trace2 = go.Bar(
- x=months,
- y=cdd_array,
- name="Cooling Degree Days",
- marker_color=color_cdd,
- customdata=cdd_array,
- hovertemplate=(
- "Cooling Degree Days:
%{customdata} per month
"
- ),
- )
+ if df is None or meta is None:
+ raise PreventUpdate
- fig = go.Figure(data=[trace2, trace1])
- fig.update_layout(
- barmode="relative",
- margin=tight_margins,
- template=template,
- dragmode=False,
- legend=dict(
- orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1
- ),
- )
+ if isinstance(df, (list, tuple, dict)):
+ df = pd.DataFrame(df)
- fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
- fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
+ hdd_setpoint = hdd_value
+ cdd_setpoint = cdd_value
+ warning_setpoint = cdd_setpoint < hdd_setpoint
- custom_inputs = f"{hdd_value}-{cdd_value}"
- units = generate_units_degree(si_ip)
+ color_hdd = "red"
+ color_cdd = "dodgerblue"
- chart = dcc.Graph(
- id=ElementIds.DEGREE_DAYS_CHART,
- config=generate_chart_name(TabNames.HDD_CDD, meta, custom_inputs, units),
- figure=fig,
- )
+ hdd_array, cdd_array = [], []
+ months = df[ColNames.MONTH_NAMES].unique()
- alert_children = (
- dmc.Alert(
- "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!",
- color="yellow",
- variant="filled",
- title="Warning",
- radius="md",
- withCloseButton=True,
- )
- if warning_setpoint
- else None
+ for i in range(1, 13):
+ query_month = "month=="
+
+ a = df.query(query_month + str(i) + " and DBT<=" + str(hdd_setpoint))[
+ ColNames.DBT
+ ].sub(hdd_setpoint)
+ hdd_array.append(int(a.sum(skipna=True) / 24))
+
+ a = df.query(query_month + str(i) + " and DBT>=" + str(cdd_setpoint))[
+ ColNames.DBT
+ ].sub(cdd_setpoint)
+ cdd_array.append(int(a.sum(skipna=True) / 24))
+
+ trace1 = go.Bar(
+ x=months,
+ y=hdd_array,
+ name="Heating Degree Days",
+ marker_color=color_hdd,
+ customdata=[abs(x) for x in hdd_array],
+ hovertemplate=" Heating Degree Days:
%{customdata} per month
",
+ )
+
+ trace2 = go.Bar(
+ x=months,
+ y=cdd_array,
+ name="Cooling Degree Days",
+ marker_color=color_cdd,
+ customdata=cdd_array,
+ hovertemplate="Cooling Degree Days:
%{customdata} per month
",
+ )
+
+ fig = go.Figure(data=[trace2, trace1])
+ fig.update_layout(
+ barmode="relative",
+ margin=tight_margins,
+ template=template,
+ dragmode=False,
+ legend=dict(orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1),
+ yaxis=dict(range=[-100, 400]),
+ )
+ fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
+ fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
+
+ custom_inputs = f"{hdd_value}-{cdd_value}"
+ units = generate_units_degree(si_ip)
+
+ chart = dcc.Graph(
+ id=ElementIds.DEGREE_DAYS_CHART,
+ config=generate_chart_name(TabNames.HDD_CDD, meta, custom_inputs, units),
+ figure=fig,
+ )
+
+ alert_children = (
+ dmc.Alert(
+ "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!",
+ color="yellow",
+ variant="filled",
+ title="Warning",
+ withCloseButton=True,
)
- return chart, alert_children
+ if warning_setpoint
+ else None
+ )
+
+ return chart, alert_children
@callback(
diff --git a/pages/sun.py b/pages/sun.py
index 315be16d..4c9b2aef 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -57,7 +57,7 @@
def sun_path():
"""Return the layout for the custom sun path and its dropdowns."""
return dmc.Stack(
- gap="md",
+ p="sm",
children=[
title_with_link(
text="Sun path chart",
@@ -67,9 +67,8 @@ def sun_path():
dmc.Group(
align="center",
justify="center",
- gap="md",
children=[
- dmc.Title("View: ", order=6, w="10rem", fz="xl"),
+ dmc.Title("View: ", order=5),
dropdown(
id=ElementIds.CUSTOM_SUN_VIEW_DROPDOWN,
options={
@@ -84,9 +83,8 @@ def sun_path():
dmc.Group(
align="center",
justify="center",
- gap="md",
children=[
- dmc.Title("Select Variable: ", order=6, w="10rem", fz="xl"),
+ dmc.Title("Select Variable: ", order=5),
dropdown(
id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN,
options=sc_dropdown_names,
@@ -106,7 +104,7 @@ def sun_path():
def explore_daily_heatmap():
"""Contents of the bottom part of the tab"""
return dmc.Stack(
- gap="md",
+ p="sm",
w="100%",
children=[
title_with_link(
@@ -117,9 +115,8 @@ def explore_daily_heatmap():
dmc.Group(
align="center",
justify="center",
- gap="md",
children=[
- dmc.Title("Select variable: ", order=6, w="10rem"),
+ dmc.Title("Select variable: ", order=5),
dropdown(
id=ElementIds.TAB_EXPLORE_DROPDOWN,
options=sun_cloud_tab_explore_dropdown_names,
@@ -142,7 +139,7 @@ def explore_daily_heatmap():
def static_section():
return dmc.Stack(
id=ElementIds.STATIC_SECTION,
- gap="md",
+ p="sm",
w="100%",
children=[
# ...
@@ -153,7 +150,7 @@ def static_section():
def layout():
"""Contents of tab four."""
return dmc.Stack(
- gap="md",
+ p="sm",
w="100%",
id=ElementIds.TAB_FOUR_CONTAINER,
children=[sun_path(), static_section(), explore_daily_heatmap()],
diff --git a/pages/t_rh.py b/pages/t_rh.py
index 7fd9707d..7d57f698 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -39,7 +39,6 @@ def layout():
dmc.Group(
justify="center",
align="center",
- gap="sm",
wrap="nowrap",
children=[
dmc.Text("Select a variable:", fz="xl"),
@@ -52,7 +51,6 @@ def layout():
],
),
dmc.Stack(
- gap="lg",
mt="md",
children=[
# Yearly Chart
@@ -63,7 +61,7 @@ def layout():
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.YEARLY_CHART, gap=0),
+ children=dmc.Stack(id=ElementIds.YEARLY_CHART),
),
# Daily chart
title_with_link(
@@ -73,7 +71,7 @@ def layout():
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.DAILY, gap=0),
+ children=dmc.Stack(id=ElementIds.DAILY),
),
# Heatmap chart
title_with_link(
@@ -83,7 +81,7 @@ def layout():
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.HEATMAP, gap=0),
+ children=dmc.Stack(id=ElementIds.HEATMAP),
),
# Descriptive statistics
title_with_tooltip(
@@ -91,7 +89,7 @@ def layout():
tooltip_text="count, mean, std, min, max, and percentiles",
id_button=IdButtons.TABLE_TMP_RH,
),
- dmc.Stack(id=ElementIds.TABLE_TMP_HUM, gap=0),
+ dmc.Stack(id=ElementIds.TABLE_TMP_HUM),
],
),
],
diff --git a/pages/wind.py b/pages/wind.py
index 38204b9e..e3d5be43 100644
--- a/pages/wind.py
+++ b/pages/wind.py
@@ -32,12 +32,8 @@ def sliders():
"""Returns 2 sliders for the hour"""
return dmc.Stack(
id=ElementIds.SLIDER_CONTAINER,
- gap="md",
- align="center",
children=[
dmc.Group(
- gap="sm",
- align="center",
children=[
dmc.Text("Month Range"),
dcc.RangeSlider(
@@ -53,8 +49,6 @@ def sliders():
],
),
dmc.Group(
- gap="sm",
- align="center",
children=[
dmc.Text("Hour Range"),
dcc.RangeSlider(
@@ -76,7 +70,6 @@ def sliders():
def seasonal_wind_rose():
"""Return the section with the 4 seasonal wind rose graphs."""
return dmc.Stack(
- gap="md",
children=[
title_with_link(
text="Seasonal Wind Rose",
@@ -89,12 +82,11 @@ def seasonal_wind_rose():
dmc.GridCol(
span=6,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.WINTER_WIND_ROSE, w="100%"
+ id=ElementIds.WINTER_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.WINTER_WIND_ROSE_TEXT),
@@ -104,12 +96,11 @@ def seasonal_wind_rose():
dmc.GridCol(
span=6,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.SPRING_WIND_ROSE, w="100%"
+ id=ElementIds.SPRING_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.SPRING_WIND_ROSE_TEXT),
@@ -119,12 +110,11 @@ def seasonal_wind_rose():
dmc.GridCol(
span=6,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.SUMMER_WIND_ROSE, w="100%"
+ id=ElementIds.SUMMER_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.SUMMER_WIND_ROSE_TEXT),
@@ -134,12 +124,11 @@ def seasonal_wind_rose():
dmc.GridCol(
span=6,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.FALL_WIND_ROSE, w="100%"
+ id=ElementIds.FALL_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.FALL_WIND_ROSE_TEXT),
@@ -155,7 +144,6 @@ def seasonal_wind_rose():
def daily_wind_rose():
"""Return the section for the 3 daily wind rose graphs."""
return dmc.Stack(
- gap="md",
id=ElementIds.TAB5_DAILY_CONTAINER,
children=[
title_with_link(
@@ -164,17 +152,15 @@ def daily_wind_rose():
doc_link=DocLinks.WIND_ROSE,
),
dmc.Grid(
- gutter="md",
children=[
dmc.GridCol(
span=4,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.MORNING_WIND_ROSE, w="100%"
+ id=ElementIds.MORNING_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.MORNING_WIND_ROSE_TEXT),
@@ -184,12 +170,11 @@ def daily_wind_rose():
dmc.GridCol(
span=4,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.NOON_WIND_ROSE, w="100%"
+ id=ElementIds.NOON_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.NOON_WIND_ROSE_TEXT),
@@ -199,12 +184,11 @@ def daily_wind_rose():
dmc.GridCol(
span=4,
children=dmc.Stack(
- gap="xs",
children=[
dcc.Loading(
type="circle",
children=dmc.Stack(
- id=ElementIds.NIGHT_WIND_ROSE, w="100%"
+ id=ElementIds.NIGHT_WIND_ROSE,
),
),
dmc.Text(id=ElementIds.NIGHT_WIND_ROSE_TEXT),
@@ -219,32 +203,24 @@ def daily_wind_rose():
def custom_wind_rose():
return dmc.Stack(
- gap="md",
- align="stretch", # stretch 让子项默认靠左
children=[
- # 标题靠左
title_with_tooltip(
text="Customizable Wind Rose",
tooltip_text=None,
id_button=IdButtons.CUSTOM_ROSE_CHART,
),
- # 参数区(保持居中排布)
dmc.Grid(
gutter="md",
justify="center",
align="center",
maw=900,
mx="auto",
- w="100%",
children=[
dmc.GridCol(
span=6,
children=dmc.Stack(
- gap="md",
- align="center",
children=[
dmc.Group(
- gap="md",
children=[
dmc.Title(
"Start Month:",
@@ -264,7 +240,6 @@ def custom_wind_rose():
],
),
dmc.Group(
- gap="md",
children=[
dmc.Title(
"Start Hour:", order=6, w="8rem", ta="right"
@@ -285,11 +260,8 @@ def custom_wind_rose():
dmc.GridCol(
span=6,
children=dmc.Stack(
- gap="md",
- align="center",
children=[
dmc.Group(
- gap="md",
children=[
dmc.Title(
"End Month:", order=6, w="8rem", ta="right"
@@ -306,7 +278,6 @@ def custom_wind_rose():
],
),
dmc.Group(
- gap="md",
children=[
dmc.Title(
"End Hour:", order=6, w="8rem", ta="right"
@@ -328,9 +299,7 @@ def custom_wind_rose():
),
dcc.Loading(
type="circle",
- children=dmc.Stack(
- id=ElementIds.CUSTOM_WIND_ROSE, w="100%", maw=900, mx="auto"
- ),
+ children=dmc.Stack(id=ElementIds.CUSTOM_WIND_ROSE, maw=900, mx="auto"),
),
],
)
@@ -339,8 +308,6 @@ def custom_wind_rose():
def layout():
"""Contents in the fifth tab 'Wind'."""
return dmc.Stack(
- gap="md",
- align="stretch",
children=[
title_with_link(
text="Annual Wind Rose",
@@ -349,15 +316,15 @@ def layout():
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.WIND_ROSE, w="100%"),
+ children=dmc.Stack(id=ElementIds.WIND_ROSE),
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.WIND_SPEED, w="100%"),
+ children=dmc.Stack(id=ElementIds.WIND_SPEED),
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.WIND_DIRECTION, w="100%"),
+ children=dmc.Stack(id=ElementIds.WIND_DIRECTION),
),
seasonal_wind_rose(),
daily_wind_rose(),
diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js
index 26013789..2adfcbdc 100644
--- a/tests/node/cypress/e2e/spec.cy.js
+++ b/tests/node/cypress/e2e/spec.cy.js
@@ -19,8 +19,7 @@ function click_tab(name) {
cy.get('#nav-group-main').click({ force: true });
// Locate tab item by ID prefix, then find label by text
cy.get('[id^="nav-"]', { timeout: 10000 }).contains(name).click({ force: true });
- // Wait for tab content container to appear
- cy.get('#tabs-content', { timeout: 20000 }).should('exist');
+ cy.wait(500);
}
function load_epw() {
@@ -32,7 +31,7 @@ describe('Clima', () => {
cy.visit('http://127.0.0.1:8080');
cy.contains('CBE Clima Tool');
cy.contains('Current Location: N/A');
-
+
// Upload
load_epw()
cy.contains('The EPW was successfully loaded!');
@@ -124,8 +123,6 @@ describe('Clima', () => {
cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true });
cy.contains('Global Value Ranges', { timeout: 10000 }).click({ force: true });
cy.contains('-40'); // Global minimum: not something you see in Italy!
- // Reopen sidebar
- cy.get('#burger-button', { timeout: 10000 }).click();
cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true });
cy.contains('IP').click({ force: true });
cy.contains('100'); // Not a Celsius temperature!
From 5ca3b07712d8adbd4fe87fe1c89f2ec4232a458d Mon Sep 17 00:00:00 2001
From: yluo0664
Date: Wed, 17 Sep 2025 19:05:47 +1000
Subject: [PATCH 10/23] fix: removed the styles in the function
show_alert_after_delay while using default style
---
pages/lib/layout.py | 20 ++++++--------------
1 file changed, 6 insertions(+), 14 deletions(-)
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index d6891777..650a0ba9 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -198,10 +198,13 @@ def create_header():
color="blue",
variant="filled",
withCloseButton=True,
- style={"display": "none"},
+ w=400,
+ pos="fixed",
+ top="25px",
+ right="10px",
+ style={"zIndex": 1002, "display": "none"},
),
],
- h="100%",
px="md",
)
@@ -412,15 +415,4 @@ def update_nav_active_state(pathname):
prevent_initial_call=True,
)
def show_alert_after_delay(n_intervals):
- base_style = {
- "position": "fixed",
- "top": "25px",
- "right": "10px",
- "width": "400px",
- "zIndex": 1002,
- }
-
- if n_intervals == 1:
- return {**base_style, "display": "block"}
- else:
- return {**base_style, "display": "none"}
+ return {"display": "block" if n_intervals == 1 else "none"}
From b6009c1329b09ca1056fd4fa4b7c2c84ef9ea38d Mon Sep 17 00:00:00 2001
From: yluo0664
Date: Tue, 23 Sep 2025 18:28:37 +1000
Subject: [PATCH 11/23] fix: removed the unnecessary style and optimized
spacing
---
pages/explorer.py | 423 +++++++++++++-----------------
pages/lib/layout.py | 106 ++++----
pages/lib/utils.py | 10 +-
pages/natural_ventilation.py | 131 ++++-----
pages/outdoor.py | 83 +++---
pages/psy-chart.py | 240 ++++++++---------
pages/select.py | 11 +-
pages/summary.py | 5 +-
pages/sun.py | 16 +-
pages/t_rh.py | 4 +-
pages/wind.py | 13 +-
tests/node/cypress/e2e/spec.cy.js | 3 +-
12 files changed, 462 insertions(+), 583 deletions(-)
diff --git a/pages/explorer.py b/pages/explorer.py
index bc36fc65..c6162dc9 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -81,18 +81,13 @@ def section_one():
return dmc.Stack(
children=[
section_one_inputs(),
- # Yearly chart
title_with_link(
text="Yearly chart",
id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
- type="circle",
- children=dmc.Paper(
- id=ElementIds.YEARLY_EXPLORE,
- p="sm",
- ),
+ type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE, p="sm")
),
title_with_link(
text="Daily chart",
@@ -100,11 +95,7 @@ def section_one():
doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
- type="circle",
- children=dmc.Paper(
- id=ElementIds.QUERY_DAILY,
- p="sm",
- ),
+ type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY, p="sm")
),
title_with_link(
text="Heatmap chart",
@@ -112,35 +103,26 @@ def section_one():
doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
),
dcc.Loading(
- type="circle",
- children=dmc.Paper(
- id=ElementIds.QUERY_HEATMAP,
- p="sm",
- ),
+ type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP, p="sm")
),
title_with_tooltip(
text="Descriptive statistics",
tooltip_text="count, mean, std, min, max, and percentiles",
id_button=IdButtons.TABLE_EXPLORE,
),
- dmc.SimpleGrid(
- cols={"base": 1, "md": 3},
- children=[
- dmc.Stack(),
- dmc.Stack(
+ dmc.Center(
+ children=dmc.Box(
+ w="33%",
+ children=dmc.Stack(
children=[
dmc.Button(
"Apply month and hour filter",
id=ElementIds.SEC1_TIME_FILTER_INPUT,
- variant="filled",
color="blue",
- size="md",
- fullWidth=True,
),
- # Month
- dmc.Flex(
+ dmc.Group(
children=[
- dmc.Text("Month Range", miw=110),
+ dmc.Title("Month Range", order=5),
dmc.Stack(
flex=1,
children=dcc.RangeSlider(
@@ -166,9 +148,9 @@ def section_one():
),
],
),
- dmc.Flex(
+ dmc.Group(
children=[
- dmc.Text("Hour Range", miw=110),
+ dmc.Title("Hour Range", order=5),
dmc.Stack(
flex=1,
children=dcc.RangeSlider(
@@ -196,9 +178,9 @@ def section_one():
),
],
),
- dmc.Stack(),
- ],
+ )
),
+ # Results table
dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"),
],
)
@@ -207,6 +189,7 @@ def section_one():
def section_two_inputs():
"""Return all the input forms from section two."""
return dmc.Stack(
+ p="md",
children=[
title_with_tooltip(
text="Customizable heatmap",
@@ -214,41 +197,35 @@ def section_two_inputs():
id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL,
),
dmc.SimpleGrid(
- cols={"base": 1, "md": 3},
+ cols=3,
+ spacing="md",
children=[
- dmc.Stack(
- children=[
- dmc.Flex(
- children=[
- dmc.Text("Variable:", miw=110, ml="md"),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.SEC2_VAR_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
- ),
- ],
+ dmc.Group(
+ [
+ dmc.Title("Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.SEC2_VAR_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ flex=1,
),
],
+ align="flex-start",
),
dmc.Stack(
- children=[
+ [
dmc.Button(
"Apply month and hour filter",
id=ElementIds.SEC2_TIME_FILTER_INPUT,
- variant="filled",
color="blue",
- size="md",
- fullWidth=True,
),
- dmc.Flex(
- children=[
- dmc.Text("Month Range", miw=110),
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
+ dcc.RangeSlider(
id=ElementIds.SEC2_MONTH_SLIDER,
min=1,
max=12,
@@ -257,10 +234,10 @@ def section_two_inputs():
marks={1: "1", 12: "12"},
tooltip={
"always_visible": False,
- "placement": "top",
+ "placement": "topLeft",
},
- allowCross=False,
),
+ flex=1,
),
dcc.Checklist(
id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
@@ -271,12 +248,11 @@ def section_two_inputs():
),
],
),
- dmc.Flex(
- children=[
- dmc.Text("Hour Range", miw=110),
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
+ dcc.RangeSlider(
id=ElementIds.SEC2_HOUR_SLIDER,
min=0,
max=24,
@@ -287,8 +263,8 @@ def section_two_inputs():
"always_visible": False,
"placement": "topLeft",
},
- allowCross=False,
),
+ flex=1,
),
dcc.Checklist(
id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
@@ -302,53 +278,48 @@ def section_two_inputs():
],
),
dmc.Stack(
- children=[
+ [
dmc.Button(
"Apply filter",
id=ElementIds.SEC2_DATA_FILTER_INPUT,
- variant="filled",
color="blue",
- size="md",
- fullWidth=True,
),
- dmc.Flex(
- children=[
- dmc.Text("Filter Variable:", miw=130),
+ dmc.Group(
+ [
+ dmc.Title("Filter Variable:", order=5),
dmc.Stack(
- flex=1,
- children=dropdown(
+ dropdown(
id=ElementIds.SEC2_DATA_FILTER_VAR,
options=explore_dropdown_names,
value=ColNames.RH,
),
+ flex=1,
),
],
),
- dmc.Flex(
- children=[
- dmc.Text("Min Value:", miw=130),
+ dmc.Group(
+ [
+ dmc.Title("Min Value:", order=5),
dmc.Stack(
- flex=1,
- children=dmc.NumberInput(
+ dmc.NumberInput(
id=ElementIds.SEC2_MIN_VAL,
placeholder="Enter a number for the min val",
value=0,
- step=1,
),
+ flex=1,
),
],
),
- dmc.Flex(
- children=[
- dmc.Text("Max Value:", miw=130),
+ dmc.Group(
+ [
+ dmc.Title("Max Value:", order=5),
dmc.Stack(
- flex=1,
- children=dmc.NumberInput(
+ dmc.NumberInput(
id=ElementIds.SEC2_MAX_VAL,
placeholder="Enter a number for the max val",
value=100,
- step=1,
),
+ flex=1,
),
],
),
@@ -387,7 +358,6 @@ def section_two():
dcc.Loading(
type="circle",
children=dmc.Paper(
- p="sm",
children=dcc.Graph(
id=ElementIds.CUSTOM_SUMMARY,
config=fig_config,
@@ -399,173 +369,148 @@ def section_two():
def section_three_inputs():
- """"""
- return dmc.Stack(
+ return dmc.SimpleGrid(
+ cols=3,
children=[
- dmc.SimpleGrid(
- cols={"base": 1, "md": 3},
- children=[
- dmc.Stack(
- children=[
- dmc.Flex(
- children=[
- dmc.Text("X Variable:", miw=110, ml="md"),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN,
- options=explore_dropdown_names,
- value="DBT",
- ),
- ),
- ],
+ dmc.Stack(
+ [
+ dmc.Group(
+ [
+ dmc.Title("X Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN,
+ options=explore_dropdown_names,
+ value="DBT",
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Y Variable:", miw=110, ml="md"),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
- ),
- ],
- ),
- dmc.Flex(
- children=[
- dmc.Text("Color By:", miw=110, ml="md"),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN,
- options=explore_dropdown_names,
- value="glob_hor_rad",
- ),
- ),
- ],
+ ],
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Y Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ flex=1,
),
],
),
- dmc.Stack(
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- fullWidth=True,
+ dmc.Group(
+ [
+ dmc.Title("Color By:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN,
+ options=explore_dropdown_names,
+ value="glob_hor_rad",
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Month Range", miw=110),
- dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
- id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dcc.Checklist(
- id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
- ],
+ ],
+ ),
+ ],
+ ),
+ dmc.Stack(
+ [
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
+ color="blue",
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
+ dcc.RangeSlider(
+ id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER,
+ min=1,
+ max=12,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Hour Range", miw=110),
- dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
- id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dcc.Checklist(
- id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
- ],
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
- dmc.Stack(
- children=[
- dmc.Button(
- "Apply filter",
- id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
- variant="filled",
- color="blue",
- size="md",
- fullWidth=True,
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
+ dcc.RangeSlider(
+ id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER,
+ min=0,
+ max=24,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Filter Variable:", miw=130),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN,
- options=explore_dropdown_names,
- value=ColNames.RH,
- ),
- ),
- ],
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
- dmc.Flex(
- children=[
- dmc.Text("Min Value:", miw=130),
- dmc.Stack(
- flex=1,
- children=dmc.NumberInput(
- id=ElementIds.TAB6_SEC3_MIN_VAL,
- placeholder="Enter a number for the min val",
- value=0,
- step=1,
- ),
- ),
- ],
+ ],
+ ),
+ ],
+ ),
+ dmc.Stack(
+ [
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
+ color="blue",
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Filter Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Max Value:", miw=130),
- dmc.Stack(
- flex=1,
- children=dmc.NumberInput(
- id=ElementIds.TAB6_SEC3_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=100,
- step=1,
- ),
- ),
- ],
+ ],
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Min Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.TAB6_SEC3_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ ),
+ flex=1,
+ ),
+ ],
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Max Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.TAB6_SEC3_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ ),
+ flex=1,
),
],
),
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index 650a0ba9..2b532fb3 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -41,7 +41,7 @@ def get_icon(cls, page_name):
def create_navbar():
nav_link_styles = {
"root": {
- "borderRadius": "6px",
+ "borderRadius": "0.375rem",
"transition": "all 0.2s ease",
"&:hover": {"backgroundColor": "#e3f2fd"},
"&[data-active='true']": {
@@ -66,7 +66,6 @@ def create_navbar():
href=page[ColNames.PATH],
id=f"nav-{page[ColNames.PATH].replace('/', '')}",
active=False,
- mb="2px",
styles=nav_link_styles,
)
for page in dash.page_registry.values()
@@ -78,10 +77,11 @@ def create_navbar():
children=sub_links,
id=ElementIds.NAV_GROUP_MAIN,
variant="light",
+ childrenOffset=0,
+ opened=True,
)
segmented_control_styles = {
- "root": {"width": "100%"},
"control": {"flex": 1, "minWidth": 0},
}
@@ -89,29 +89,51 @@ def create_navbar():
gap="xs",
py="xs",
children=[
- dmc.SegmentedControl(
- id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT,
- value="local",
- color="blue",
- data=[
- {"label": "Global Value Ranges", "value": "global"},
- {"label": "Local Value Ranges", "value": "local"},
- ],
- radius="md",
- size="sm",
- styles=segmented_control_styles,
+ dmc.Tooltip(
+ label=dmc.Stack(
+ gap="xs",
+ children=[
+ dmc.Text(
+ "You can choose value ranges between Global and Local"
+ ),
+ ],
+ ),
+ position="right",
+ withArrow=True,
+ children=dmc.SegmentedControl(
+ id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT,
+ value="local",
+ color="blue",
+ data=[
+ {"label": "Global", "value": "global"},
+ {"label": "Local", "value": "local"},
+ ],
+ w=220,
+ size="sm",
+ styles=segmented_control_styles,
+ ),
),
- dmc.SegmentedControl(
- id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT,
- value=UnitSystem.SI,
- color="blue",
- data=[
- {"label": UnitSystem.SI.upper(), "value": UnitSystem.SI},
- {"label": UnitSystem.IP.upper(), "value": UnitSystem.IP},
- ],
- radius="md",
- size="sm",
- styles=segmented_control_styles,
+ dmc.Tooltip(
+ label=dmc.Stack(
+ gap="xs",
+ children=[
+ dmc.Text("You can choose units between SI and IP"),
+ ],
+ ),
+ position="right",
+ withArrow=True,
+ children=dmc.SegmentedControl(
+ id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT,
+ value=UnitSystem.SI,
+ color="blue",
+ data=[
+ {"label": "SI", "value": UnitSystem.SI},
+ {"label": "IP", "value": UnitSystem.IP},
+ ],
+ w=220,
+ size="sm",
+ styles=segmented_control_styles,
+ ),
),
],
)
@@ -122,7 +144,7 @@ def create_navbar():
children=[controls_stack],
id=ElementIds.NAV_GROUP_CONTROLS,
variant="light",
- childrenOffset=8,
+ childrenOffset=0,
)
# Documentation
@@ -136,7 +158,6 @@ def create_navbar():
return dmc.ScrollArea(
children=dmc.Stack(gap="xs", children=[parent_group, controls_group, doc_link]),
- pr="xs",
)
@@ -160,8 +181,6 @@ def create_header():
"CBE Clima Tool",
id=ElementIds.BANNER_TITLE,
order=2,
- fw=500,
- ff="'Open Sans', sans-serif",
lh=1.1,
c="white",
),
@@ -170,13 +189,11 @@ def create_header():
id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
size="sm",
opacity=0.85,
- ff="'Poppins', sans-serif",
- fw=400,
- h=25,
style={"overflow": "hidden"},
c="white",
),
],
+ p="xs",
),
dmc.Alert(
[
@@ -186,7 +203,7 @@ def create_header():
href="https://forms.gle/k289zP3R92jdu14M7",
target="_blank",
c="white",
- underline=True,
+ underline="always",
),
"! ☀️",
],
@@ -200,21 +217,19 @@ def create_header():
withCloseButton=True,
w=400,
pos="fixed",
- top="25px",
- right="10px",
+ top="1em",
+ right="1em",
style={"zIndex": 1002, "display": "none"},
),
],
- px="md",
+ pl="md",
)
def create_footer():
white_anchor_style = {
- "underline": True,
+ "underline": "always",
"c": "white",
- "fz": "md",
- "fw": 500,
"target": "_blank",
}
@@ -259,7 +274,7 @@ def create_footer():
Please cite us: Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5).
""",
style={
- "fontSize": "16px",
+ "fontSize": "1rem",
"lineHeight": 1.3,
"fontWeight": 400,
"color": "white",
@@ -285,7 +300,6 @@ def create_footer():
p="sm",
c="white",
bg="#003262",
- h="100%",
gap="xl",
justify="flex-start",
align="center",
@@ -324,7 +338,6 @@ def create_collapsible_layout():
dmc.AppShellNavbar(
id=ElementIds.NAVBAR,
children=create_navbar(),
- p="md",
bg="#f8f9fa",
),
# including main and footer
@@ -339,20 +352,19 @@ def create_collapsible_layout():
pos="relative",
style={
"zIndex": 1,
- "@media (max-width: 768px)": {
- "left": "0px",
+ "@media (max-width: 48rem)": {
+ "left": "0",
},
},
),
],
header={"height": 80},
navbar={
- "width": 180,
+ "width": 230,
"breakpoint": "sm",
"collapsed": {"mobile": True, "desktop": False},
"id": ElementIds.NAVBAR_CONTAINER,
},
- padding="md",
id=ElementIds.APP_SHELL,
)
@@ -377,7 +389,7 @@ def toggle_navbar_and_width(
):
navbar["collapsed"] = {"mobile": not burger_opened, "desktop": not burger_opened}
- WIDTHS = {"default": 180, "pages": 300, "tools": 400}
+ WIDTHS = {"default": 230, "pages": 230, "tools": 230}
if tools_opened is not None:
tools_expanded = tools_opened
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index 53d9c26f..ff44face 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -149,7 +149,6 @@ def generate_custom_inputs_psy(
def title_with_tooltip(text, tooltip_text, id_button):
if tooltip_text:
return dmc.Group(
- align="center",
mt="md",
px="md",
children=[
@@ -171,7 +170,13 @@ def title_with_tooltip(text, tooltip_text, id_button):
],
)
else:
- return dmc.Title(text, order=3, mt="md", px="md")
+ return dmc.Group(
+ mt="md",
+ px="md",
+ children=[
+ dmc.Title(text, order=3),
+ ],
+ )
def title_with_link(
@@ -181,7 +186,6 @@ def title_with_link(
doc_link: str = "",
):
return dmc.Group(
- align="center",
mt="md",
px="md",
children=[
diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py
index 4499cc8d..4cd2cc94 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -39,7 +39,7 @@
def layout():
- return dmc.Stack(id=ElementIds.MAIN_NV_SECTION)
+ return dmc.Stack(p="md", id=ElementIds.MAIN_NV_SECTION)
@callback(
@@ -67,7 +67,6 @@ def update_layout(si_ip):
type="circle",
children=dmc.Paper(
id=ElementIds.NV_HEATMAP_CHART,
- p="sm",
),
),
dmc.Group(
@@ -77,7 +76,6 @@ def update_layout(si_ip):
id=ElementIds.SWITCHES_INPUT,
label="",
checked=True,
- size="md",
color="blue",
style={"padding": "1rem", "marginRight": "-2rem"},
),
@@ -95,7 +93,6 @@ def update_layout(si_ip):
type="circle",
children=dmc.Paper(
id=ElementIds.NV_BAR_CHART,
- p="sm",
),
),
]
@@ -103,77 +100,70 @@ def update_layout(si_ip):
def inputs_tab(t_min, t_max, d_set):
return dmc.SimpleGrid(
- cols={"base": 1, "md": 3},
+ cols=3,
+ spacing="md",
children=[
dmc.Stack(
- children=[
+ [
dmc.Button(
"Apply filter",
- color="primary",
+ color="blue",
id=ElementIds.NV_DBT_FILTER,
variant="link",
- size="md",
- fullWidth=True,
n_clicks=1,
),
- dmc.Text(
- "Outdoor dry-bulb air temperature range",
- size="md",
- ml="md",
- ),
- dmc.Flex(
- children=[
- dmc.Text("Min Value:", size="md", ml="md"),
- dmc.NumberInput(
- id=ElementIds.NV_TDB_MIN_VAL,
- placeholder="Enter a number for the min val",
- step=1,
- value=t_min,
+ dmc.Title("Outdoor dry-bulb air temperature range", order=5),
+ dmc.Group(
+ [
+ dmc.Title("Min Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.NV_TDB_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ step=1,
+ value=t_min,
+ ),
+ flex=1,
),
],
),
- # Max
- dmc.Flex(
- children=[
- dmc.Text("Max Value:", size="md", ml="md"),
- dmc.NumberInput(
- id=ElementIds.NV_TDB_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=t_max,
- step=1,
+ dmc.Group(
+ [
+ dmc.Title("Max Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.NV_TDB_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=t_max,
+ step=1,
+ ),
+ flex=1,
),
],
),
- ],
+ ]
),
dmc.Stack(
- children=[
+ [
dmc.Button(
"Apply month and hour filter",
- color="primary",
+ color="blue",
id=ElementIds.NV_MONTH_HOUR_FILTER,
variant="link",
- size="md",
- fullWidth=True,
),
- dmc.Flex(
- children=[
- dmc.Text("Month Range", size="md", miw=120),
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
+ dcc.RangeSlider(
id=ElementIds.NV_MONTH_SLIDER,
min=1,
max=12,
step=1,
value=[1, 12],
marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
),
+ flex=1,
),
dcc.Checklist(
options=[{"label": "Invert", "value": "invert"}],
@@ -182,24 +172,19 @@ def inputs_tab(t_min, t_max, d_set):
),
],
),
- dmc.Flex(
- children=[
- dmc.Text("Hour Range", size="md", miw=120),
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
+ dcc.RangeSlider(
id=ElementIds.NV_HOUR_SLIDER,
min=0,
max=24,
step=1,
value=[0, 24],
marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
),
+ flex=1,
),
dcc.Checklist(
options=[{"label": "Invert", "value": "invert"}],
@@ -208,19 +193,15 @@ def inputs_tab(t_min, t_max, d_set):
),
],
),
- ],
+ ]
),
dmc.Stack(
- children=[
+ [
dmc.Button(
"Apply filter",
- color="primary",
+ color="blue",
id=ElementIds.NV_DPT_FILTER,
- mb="xs",
variant="link",
- size="md",
- fullWidth=True,
- n_clicks=0,
disabled=True,
),
dcc.Checklist(
@@ -232,24 +213,26 @@ def inputs_tab(t_min, t_max, d_set):
"system surface temperature, the data point is not plot."
),
"value": 1,
- },
+ }
],
value=[],
id=ElementIds.ENABLE_CONDENSATION,
),
- dmc.Flex(
- children=[
- dmc.Text("Surface temperature:", size="md"),
- dmc.NumberInput(
- id=ElementIds.NV_DPT_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=d_set,
- step=1,
- w="50%",
+ dmc.Group(
+ [
+ dmc.Title("Surface temperature:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.NV_DPT_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=d_set,
+ step=1,
+ ),
+ flex=1,
),
],
),
- ],
+ ]
),
],
)
diff --git a/pages/outdoor.py b/pages/outdoor.py
index fcc814f4..a7d9ba4d 100644
--- a/pages/outdoor.py
+++ b/pages/outdoor.py
@@ -37,58 +37,47 @@
def inputs_outdoor_comfort():
return dmc.SimpleGrid(
- cols={"base": 1, "md": 2},
+ cols=2,
children=[
- dmc.Stack(
- children=[
- dmc.Flex(
- children=[
- dmc.Text("Select a scenario:", miw=140, ml="md"),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.TAB7_DROPDOWN,
- options=outdoor_dropdown_names,
- value="utci_Sun_Wind",
- persistence=True,
- persistence_type="session",
- ),
- ),
- dmc.Center(
- children=dmc.Paper(id=ElementIds.IMAGE_SELECTION)
- ),
- ],
+ dmc.Group(
+ [
+ dmc.Title("Select a scenario:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.TAB7_DROPDOWN,
+ options=outdoor_dropdown_names,
+ value="utci_Sun_Wind",
+ persistence=True,
+ persistence_type="session",
+ ),
+ flex=1,
),
+ dmc.Paper(id=ElementIds.IMAGE_SELECTION),
],
+ align="flex-start",
),
dmc.Stack(
- children=[
+ [
dmc.Button(
"Apply month and hour filter",
id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT,
variant="filled",
color="blue",
- size="md",
- fullWidth=True,
),
- dmc.Flex(
- children=[
- dmc.Text("Month Range", miw=120),
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
+ dcc.RangeSlider(
id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
min=1,
max=12,
step=1,
value=[1, 12],
marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ tooltip={"always_visible": False},
),
+ flex=1,
),
dcc.Checklist(
id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT,
@@ -97,24 +86,20 @@ def inputs_outdoor_comfort():
),
],
),
- dmc.Flex(
- children=[
- dmc.Text("Hour Range", miw=120),
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
+ dcc.RangeSlider(
id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
min=0,
max=24,
step=1,
value=[0, 24],
marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
+ tooltip={"always_visible": False},
),
+ flex=1,
),
dcc.Checklist(
id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT,
@@ -134,9 +119,7 @@ def outdoor_comfort_chart():
children=[
dmc.Paper(
id=ElementIds.OUTDOOR_COMFORT_OUTPUT,
- p="sm",
),
- # UTCI heatmap chart
title_with_link(
text="UTCI heatmap chart",
id_button=IdButtons.UTCI_CHARTS_LABEL,
@@ -146,10 +129,8 @@ def outdoor_comfort_chart():
type="circle",
children=dmc.Paper(
id=ElementIds.UTCI_HEATMAP,
- p="sm",
),
),
- # UTCI thermal stress chart
title_with_link(
text="UTCI thermal stress chart",
id_button=IdButtons.UTCI_CHARTS_LABEL,
@@ -159,17 +140,14 @@ def outdoor_comfort_chart():
type="circle",
children=dmc.Paper(
id=ElementIds.UTCI_CATEGORY_HEATMAP,
- p="sm",
),
),
- # Normalize data
dmc.Group(
justify="center",
children=[
dmc.Switch(
id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT,
checked=True,
- size="md",
color="blue",
),
title_with_tooltip(
@@ -182,12 +160,11 @@ def outdoor_comfort_chart():
),
],
),
- # Summary chart
dcc.Loading(
type="circle",
children=dmc.Paper(
id=ElementIds.UTCI_SUMMARY_CHART,
- p="sm",
+ # p="sm",
),
),
],
@@ -196,7 +173,7 @@ def outdoor_comfort_chart():
def layout():
return dmc.Stack(
- mt="md",
+ p="md",
children=[
dcc.Loading(
type="circle",
diff --git a/pages/psy-chart.py b/pages/psy-chart.py
index 1793e076..4aca01e1 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -57,150 +57,123 @@
def inputs():
- """"""
- return dmc.Stack(
+ return dmc.SimpleGrid(
+ cols=3,
children=[
- dmc.SimpleGrid(
- cols={"base": 1, "md": 3},
- spacing="md",
- children=[
- dmc.Flex(
- align="center",
- mt="md",
- children=[
- dmc.Text("Color By:", miw=110),
+ dmc.Group(
+ [
+ dmc.Title("Color By:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.PSY_COLOR_BY_DROPDOWN,
+ options=psy_dropdown_names,
+ value="Frequency",
+ persistence=True,
+ persistence_type="session",
+ ),
+ flex=1,
+ ),
+ ],
+ align="flex-start",
+ ),
+ dmc.Stack(
+ [
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.MONTH_HOUR_FILTER,
+ color="blue",
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.PSY_COLOR_BY_DROPDOWN,
- options=psy_dropdown_names,
- value="Frequency",
- persistence=True,
- persistence_type="session",
+ dcc.RangeSlider(
+ id=ElementIds.PSY_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={"always_visible": False},
),
+ flex=1,
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_PSY,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
- dmc.Stack(
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.MONTH_HOUR_FILTER,
- variant="filled",
- color="blue",
- size="md",
- fullWidth=True,
- ),
- dmc.Flex(
- children=[
- dmc.Text("Month Range", miw=110),
- dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
- id=ElementIds.PSY_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dcc.Checklist(
- id=ElementIds.INVERT_MONTH_PSY,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
- ],
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
+ dcc.RangeSlider(
+ id=ElementIds.PSY_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={"always_visible": False},
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Hour Range", miw=110),
- dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
- id=ElementIds.PSY_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
- ),
- dcc.Checklist(
- id=ElementIds.INVERT_HOUR_PSY,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
- ],
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_PSY,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
- dmc.Stack(
- children=[
- dmc.Button(
- "Apply filter",
- id=ElementIds.DATA_FILTER,
- variant="filled",
- color="blue",
- size="md",
- fullWidth=True,
- ),
- dmc.Flex(
- children=[
- dmc.Text("Filter Variable:", miw=130),
- dmc.Stack(
- flex=1,
- children=dropdown(
- id=ElementIds.PSY_VAR_DROPDOWN,
- options=dropdown_names,
- value=ColNames.RH,
- ),
- ),
- ],
+ ],
+ ),
+ dmc.Stack(
+ [
+ dmc.Button(
+ "Apply filter",
+ id=ElementIds.DATA_FILTER,
+ color="blue",
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Filter Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.PSY_VAR_DROPDOWN,
+ options=dropdown_names,
+ value=ColNames.RH,
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Min Value:", miw=130),
- dmc.Stack(
- flex=1,
- children=dmc.NumberInput(
- id=ElementIds.PSY_MIN_VAL,
- placeholder="Enter a number for the min val",
- value=0,
- step=1,
- size="md",
- ),
- ),
- ],
+ ],
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Min Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.PSY_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ step=1,
+ ),
+ flex=1,
),
- dmc.Flex(
- children=[
- dmc.Text("Max Value:", miw=130),
- dmc.Stack(
- flex=1,
- children=dmc.NumberInput(
- id=ElementIds.PSY_MAX_VAL,
- placeholder="Enter a number for the max val",
- value=100,
- step=1,
- size="md",
- ),
- ),
- ],
+ ],
+ ),
+ dmc.Group(
+ [
+ dmc.Title("Max Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.PSY_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ step=1,
+ ),
+ flex=1,
),
],
),
@@ -212,6 +185,7 @@ def inputs():
def layout():
return dmc.Stack(
+ p="md",
children=[
title_with_link(
text="Psychrometric Chart",
@@ -225,7 +199,6 @@ def layout():
inputs(),
dmc.Paper(
id=ElementIds.PSYCH_CHART,
- p="sm",
),
],
),
@@ -234,7 +207,6 @@ def layout():
)
-# psychrometric chart
@callback(
Output(ElementIds.PSYCH_CHART, "children"),
[
diff --git a/pages/select.py b/pages/select.py
index 4a8e2a4f..60b77de2 100644
--- a/pages/select.py
+++ b/pages/select.py
@@ -56,7 +56,7 @@ def layout():
id=ElementIds.UPLOAD_DATA_BUTTON,
variant="outline",
color="gray",
- style={"borderStyle": "dashed", "borderRadius": "5px"},
+ style={"borderStyle": "dashed"},
styles={"label": {"fontWeight": 400}},
),
# Allow multiple files to be uploaded
@@ -89,14 +89,12 @@ def layout():
dmc.Button(
"Close",
id=ElementIds.MODAL_CLOSE_BUTTON,
- ml="sm",
color="gray",
variant="outline",
),
dmc.Button(
"Yes",
id=ElementIds.MODAL_YES_BUTTON,
- ml="sm",
color="blue",
),
],
@@ -105,9 +103,8 @@ def layout():
],
),
],
- mx=0,
- px=0,
- py="md",
+ p="md",
+ mb="xl",
style={
"display": "flex",
"flexDirection": "column",
@@ -393,5 +390,5 @@ def plot_location_epw_files(pathname):
id=ElementIds.TAB_ONE_MAP,
figure=fig,
config=generate_chart_name(TabNames.EPW_LOCATION_SELECT),
- style={"position": "relative", "zIndex": 5},
+ # style={"position": "relative", "zIndex": 5},
)
diff --git a/pages/summary.py b/pages/summary.py
index f96f237d..037ac7b4 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -37,7 +37,7 @@ def layout():
return dmc.Container(
fluid=True,
- px="md",
+ p="md",
children=[
dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER),
],
@@ -78,8 +78,6 @@ def update_layout(si_ip):
dcc.Loading(
type="circle",
children=dmc.Group(
- align="center",
- justify="flex-start",
children=[
dmc.Button(
"Download EPW",
@@ -105,7 +103,6 @@ def update_layout(si_ip):
),
dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD),
dmc.Group(
- align="center",
justify="center",
children=[
dmc.Text("Heating degree day (HDD) setpoint"),
diff --git a/pages/sun.py b/pages/sun.py
index 4c9b2aef..dd9d1c47 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -57,7 +57,6 @@
def sun_path():
"""Return the layout for the custom sun path and its dropdowns."""
return dmc.Stack(
- p="sm",
children=[
title_with_link(
text="Sun path chart",
@@ -104,7 +103,6 @@ def sun_path():
def explore_daily_heatmap():
"""Contents of the bottom part of the tab"""
return dmc.Stack(
- p="sm",
w="100%",
children=[
title_with_link(
@@ -125,12 +123,10 @@ def explore_daily_heatmap():
),
],
),
- dcc.Loading(
- type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY, w="100%")
- ),
+ dcc.Loading(type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY)),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.TAB4_HEATMAP, w="100%"),
+ children=dmc.Stack(id=ElementIds.TAB4_HEATMAP),
),
],
)
@@ -139,7 +135,6 @@ def explore_daily_heatmap():
def static_section():
return dmc.Stack(
id=ElementIds.STATIC_SECTION,
- p="sm",
w="100%",
children=[
# ...
@@ -150,8 +145,7 @@ def static_section():
def layout():
"""Contents of tab four."""
return dmc.Stack(
- p="sm",
- w="100%",
+ p="md",
id=ElementIds.TAB_FOUR_CONTAINER,
children=[sun_path(), static_section(), explore_daily_heatmap()],
)
@@ -173,7 +167,7 @@ def update_static_section(si_ip):
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR, w="100%"),
+ children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR),
),
title_with_link(
text="Cloud coverage",
@@ -182,7 +176,7 @@ def update_static_section(si_ip):
),
dcc.Loading(
type="circle",
- children=dmc.Stack(id=ElementIds.CLOUD_COVER, w="100%"),
+ children=dmc.Stack(id=ElementIds.CLOUD_COVER),
),
]
diff --git a/pages/t_rh.py b/pages/t_rh.py
index 7d57f698..edd1e726 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -34,14 +34,14 @@
def layout():
return dmc.Container(
fluid=True,
- px="md",
+ p="md",
children=[
dmc.Group(
justify="center",
align="center",
wrap="nowrap",
children=[
- dmc.Text("Select a variable:", fz="xl"),
+ dmc.Title("Select a variable:", order=5),
dropdown(
id=ElementIds.ID_T_RH_DROPDOWN,
options={var: dropdown_names[var] for var in var_to_plot},
diff --git a/pages/wind.py b/pages/wind.py
index e3d5be43..09738699 100644
--- a/pages/wind.py
+++ b/pages/wind.py
@@ -35,7 +35,7 @@ def sliders():
children=[
dmc.Group(
children=[
- dmc.Text("Month Range"),
+ dmc.Title("Month Range", order=5),
dcc.RangeSlider(
id=ElementIds.MONTH_SLIDER,
min=1,
@@ -50,7 +50,7 @@ def sliders():
),
dmc.Group(
children=[
- dmc.Text("Hour Range"),
+ dmc.Title("Hour Range", order=5),
dcc.RangeSlider(
id=ElementIds.HOUR_SLIDER,
min=1,
@@ -211,8 +211,6 @@ def custom_wind_rose():
),
dmc.Grid(
gutter="md",
- justify="center",
- align="center",
maw=900,
mx="auto",
children=[
@@ -242,7 +240,7 @@ def custom_wind_rose():
dmc.Group(
children=[
dmc.Title(
- "Start Hour:", order=6, w="8rem", ta="right"
+ "Start Hour:", order=5, w="8rem", ta="right"
),
dropdown(
id=ElementIds.TAB5_CUSTOM_START_HOUR,
@@ -264,7 +262,7 @@ def custom_wind_rose():
dmc.Group(
children=[
dmc.Title(
- "End Month:", order=6, w="8rem", ta="right"
+ "End Month:", order=5, w="8rem", ta="right"
),
dropdown(
id=ElementIds.TAB5_CUSTOM_END_MONTH,
@@ -280,7 +278,7 @@ def custom_wind_rose():
dmc.Group(
children=[
dmc.Title(
- "End Hour:", order=6, w="8rem", ta="right"
+ "End Hour:", order=5, w="8rem", ta="right"
),
dropdown(
id=ElementIds.TAB5_CUSTOM_END_HOUR,
@@ -308,6 +306,7 @@ def custom_wind_rose():
def layout():
"""Contents in the fifth tab 'Wind'."""
return dmc.Stack(
+ p="md",
children=[
title_with_link(
text="Annual Wind Rose",
diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js
index 2adfcbdc..38dea4fe 100644
--- a/tests/node/cypress/e2e/spec.cy.js
+++ b/tests/node/cypress/e2e/spec.cy.js
@@ -14,7 +14,6 @@
function click_tab(name) {
// Open the sidebar (burger button is fixed on screen)
- cy.get('#burger-button', { timeout: 10000 }).click({ force: true });
// Expand the main nav group if collapsed
cy.get('#nav-group-main').click({ force: true });
// Locate tab item by ID prefix, then find label by text
@@ -121,7 +120,7 @@ describe('Clima', () => {
click_tab('Temperature and Humidity')
// Expand the "Data Display Options" nav section to access controls
cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true });
- cy.contains('Global Value Ranges', { timeout: 10000 }).click({ force: true });
+ cy.contains('Global', { timeout: 10000 }).click({ force: true });
cy.contains('-40'); // Global minimum: not something you see in Italy!
cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true });
cy.contains('IP').click({ force: true });
From 56cad466285bea7a6c4ad7db8f8abfe61f4968cc Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:37:26 +1000
Subject: [PATCH 12/23] style(layout): Simplify children structure in
ScrollArea
Removed unnecessary Stack component to streamline the layout
and improve readability. Also optimized spacing in the layout
by removing redundant CSS properties.
---
assets/construction.css | 5 ----
assets/footer.css | 18 -------------
assets/tabs.css | 57 -----------------------------------------
pages/lib/layout.py | 4 +--
4 files changed, 1 insertion(+), 83 deletions(-)
delete mode 100644 assets/construction.css
delete mode 100644 assets/footer.css
diff --git a/assets/construction.css b/assets/construction.css
deleted file mode 100644
index 930d1086..00000000
--- a/assets/construction.css
+++ /dev/null
@@ -1,5 +0,0 @@
-#construction-container {
- height: 80vh;
- align-items: center;
- margin-top: 20px;
-}
\ No newline at end of file
diff --git a/assets/footer.css b/assets/footer.css
deleted file mode 100644
index b334dcd4..00000000
--- a/assets/footer.css
+++ /dev/null
@@ -1,18 +0,0 @@
-#footer-container {
- padding: 1rem;
- margin:0;
- color: white;
- background-color: #003262;
-}
-
-a {
- color: white;
- text-decoration: underline;
-}
-
-a:hover {
- color: lightgrey;
-}
-
-
-
diff --git a/assets/tabs.css b/assets/tabs.css
index 1813deda..56c05f8e 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -94,63 +94,6 @@
background-color: white;
}
-/* Tab One */
-#tab-one-container {
- height: 100%;
- padding-top: 0;
-}
-
-#tab-one-form-container {
- justify-content: space-between;
-}
-
-#store-container {
- height: 100%;
-}
-
-#tab-one-map {
- margin-top: 28px;
- /*height: 100vh;*/
- width: 100%;
-}
-
-#map-credits {
- padding-top: 10px;
-}
-
-
-/* Tab Two */
-#tab-two-container {
- justify-content: space-between;
- width: 100%;
-}
-
-p {
- margin-bottom: 0;
-}
-
-#world-map {
- height: 65%;
- width: 100%;
-}
-
-.violin-container {
- height: 100%;
- width: 100%;
- display: flex;
- align-items: stretch;
-}
-
-.loading-violin-container {
- width: 100%;
- height: 100%;
-}
-
-#temp-profile-graph, #humidity-profile-graph, #solar-radiation-graph, #wind-speed-graph {
- height: 50%;
- /*width: 100%;*/
-}
-
/* Tab Temperature RH*/
.dropdown-t-rh {
width: 15rem;
diff --git a/pages/lib/layout.py b/pages/lib/layout.py
index 2b532fb3..71b11193 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -157,7 +157,7 @@ def create_navbar():
)
return dmc.ScrollArea(
- children=dmc.Stack(gap="xs", children=[parent_group, controls_group, doc_link]),
+ children=[parent_group, controls_group, doc_link],
)
@@ -300,10 +300,8 @@ def create_footer():
p="sm",
c="white",
bg="#003262",
- gap="xl",
justify="flex-start",
align="center",
- px="lg",
)
From 773bf25a51430a284bee71b709ee5808c92338dd Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:37:38 +1000
Subject: [PATCH 13/23] style(select): Refactor layout to use Stack component
Replace Box with Stack for improved layout structure and
remove unnecessary style properties to simplify the code.
---
pages/select.py | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/pages/select.py b/pages/select.py
index 60b77de2..63db2152 100644
--- a/pages/select.py
+++ b/pages/select.py
@@ -38,7 +38,8 @@
def layout():
"""Contents in the first tab 'Select Weather File'"""
- return dmc.Box(
+ return dmc.Stack(
+ p="md",
children=[
dcc.Loading(
id=ElementIds.LOADING_ONE,
@@ -103,13 +104,6 @@ def layout():
],
),
],
- p="md",
- mb="xl",
- style={
- "display": "flex",
- "flexDirection": "column",
- "gap": "var(--mantine-spacing-md)",
- },
)
@@ -319,8 +313,8 @@ def display_modal_when_data_clicked(_, click_map, __, opened):
url = re.search(
r'href=[\'"]?([^\'" >]+)', click_map["points"][0]["customdata"][-1]
).group(1)
- return (not opened, url)
- return (opened, "")
+ return not opened, url
+ return opened, ""
@callback(
@@ -390,5 +384,4 @@ def plot_location_epw_files(pathname):
id=ElementIds.TAB_ONE_MAP,
figure=fig,
config=generate_chart_name(TabNames.EPW_LOCATION_SELECT),
- # style={"position": "relative", "zIndex": 5},
)
From cc3c63bd67ce8c773d985e5fc63d9b76fb4040be Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:37:50 +1000
Subject: [PATCH 14/23] refactor(layout): Simplify layout structure in summary
and template graphs
Consolidate container and stack components for improved readability and
maintainability. Adjust grid column spans for responsive design.
---
pages/lib/template_graphs.py | 1 +
pages/summary.py | 24 ++++++++----------------
2 files changed, 9 insertions(+), 16 deletions(-)
diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py
index b62f199b..4916381d 100644
--- a/pages/lib/template_graphs.py
+++ b/pages/lib/template_graphs.py
@@ -71,6 +71,7 @@ def violin(df, var, global_local, si_ip):
title=title,
title_x=0.5,
dragmode=False,
+ height=400,
)
fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
fig.update_yaxes(
diff --git a/pages/summary.py b/pages/summary.py
index 037ac7b4..9aaab898 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -35,13 +35,7 @@
def layout():
"""Contents in the second tab 'Climate Summary'."""
- return dmc.Container(
- fluid=True,
- p="md",
- children=[
- dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER),
- ],
- )
+ return dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, p="md")
@callback(
@@ -63,7 +57,7 @@ def update_layout(si_ip):
type="circle",
children=dmc.Stack(
id=ElementIds.LOCATION_INFO,
- p="sm",
+ gap=0,
),
),
dcc.Loading(
@@ -146,10 +140,10 @@ def update_layout(si_ip):
id=ElementIds.GRAPH_CONTAINER,
gutter="md",
children=[
- dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span=3),
- dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span=3),
- dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span=3),
- dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span=3),
+ dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
+ dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
+ dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
+ dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
],
),
],
@@ -242,8 +236,7 @@ def update_location_info(ts, df, meta, si_ip):
hottest_yearly_tmp = f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} {tmp_unit}"
coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}"
- return dmc.Stack(
- children=[
+ return [
dmc.Text(location, fw=700),
dmc.Text(lon),
dmc.Text(lat),
@@ -255,8 +248,7 @@ def update_location_info(ts, df, meta, si_ip):
dmc.Text(coldest_yearly_tmp),
dmc.Text(total_solar_rad),
dmc.Text(total_diffuse_rad),
- ],
- )
+ ]
@callback(
From 0807032bcb865ff08fa5975156bc8db3a7ec670c Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:38:00 +1000
Subject: [PATCH 15/23] style(utils): Remove unnecessary margin and padding in
tooltip functions
Clean up the layout by eliminating redundant margin and padding properties
in the title_with_tooltip function to streamline the component's styling.
---
pages/lib/utils.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index ff44face..d31af407 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -149,8 +149,6 @@ def generate_custom_inputs_psy(
def title_with_tooltip(text, tooltip_text, id_button):
if tooltip_text:
return dmc.Group(
- mt="md",
- px="md",
children=[
dmc.Title(text, order=3),
dmc.Tooltip(
@@ -186,8 +184,6 @@ def title_with_link(
doc_link: str = "",
):
return dmc.Group(
- mt="md",
- px="md",
children=[
dmc.Title(text, order=3),
dmc.Tooltip(
From 23eaa741d27c0f3f987dc6797bd3ede621876f62 Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:45:20 +1000
Subject: [PATCH 16/23] refactor(layout): Replace Container with Stack for
layout consistency
Updated the layout function to utilize the Stack component instead of
Container, improving the structure and alignment of child elements.
---
assets/tabs.css | 5 ---
pages/summary.py | 44 ++++++++++++++---------
pages/t_rh.py | 91 ++++++++++++++++++++++--------------------------
3 files changed, 69 insertions(+), 71 deletions(-)
diff --git a/assets/tabs.css b/assets/tabs.css
index 56c05f8e..b5e7b14c 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -94,11 +94,6 @@
background-color: white;
}
-/* Tab Temperature RH*/
-.dropdown-t-rh {
- width: 15rem;
-}
-
/* Tab Four */
#tab-four-container {
width: 100%;
diff --git a/pages/summary.py b/pages/summary.py
index 9aaab898..d8da56d3 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -140,10 +140,22 @@ def update_layout(si_ip):
id=ElementIds.GRAPH_CONTAINER,
gutter="md",
children=[
- dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
- dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
- dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
- dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span={"base": 12, "sm": 6, "lg": 3}),
+ dmc.GridCol(
+ id=ElementIds.TEMP_PROFILE_GRAPH,
+ span={"base": 12, "sm": 6, "lg": 3},
+ ),
+ dmc.GridCol(
+ id=ElementIds.HUMIDITY_PROFILE_GRAPH,
+ span={"base": 12, "sm": 6, "lg": 3},
+ ),
+ dmc.GridCol(
+ id=ElementIds.SOLAR_RADIATION_GRAPH,
+ span={"base": 12, "sm": 6, "lg": 3},
+ ),
+ dmc.GridCol(
+ id=ElementIds.WIND_SPEED_GRAPH,
+ span={"base": 12, "sm": 6, "lg": 3},
+ ),
],
),
],
@@ -237,18 +249,18 @@ def update_location_info(ts, df, meta, si_ip):
coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}"
return [
- dmc.Text(location, fw=700),
- dmc.Text(lon),
- dmc.Text(lat),
- dmc.Text(elevation),
- dmc.Text(period) if period else None,
- dmc.Text(climate_text) if climate_text else None,
- dmc.Text(average_yearly_tmp),
- dmc.Text(hottest_yearly_tmp),
- dmc.Text(coldest_yearly_tmp),
- dmc.Text(total_solar_rad),
- dmc.Text(total_diffuse_rad),
- ]
+ dmc.Text(location, fw=700),
+ dmc.Text(lon),
+ dmc.Text(lat),
+ dmc.Text(elevation),
+ dmc.Text(period) if period else None,
+ dmc.Text(climate_text) if climate_text else None,
+ dmc.Text(average_yearly_tmp),
+ dmc.Text(hottest_yearly_tmp),
+ dmc.Text(coldest_yearly_tmp),
+ dmc.Text(total_solar_rad),
+ dmc.Text(total_diffuse_rad),
+ ]
@callback(
diff --git a/pages/t_rh.py b/pages/t_rh.py
index edd1e726..39d38993 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -32,66 +32,57 @@
def layout():
- return dmc.Container(
- fluid=True,
+ return dmc.Stack(
p="md",
children=[
- dmc.Group(
- justify="center",
- align="center",
- wrap="nowrap",
- children=[
- dmc.Title("Select a variable:", order=5),
+ dmc.Center(
+ [
+ dmc.Title("Select a variable:", order=5, mr="md"),
dropdown(
id=ElementIds.ID_T_RH_DROPDOWN,
options={var: dropdown_names[var] for var in var_to_plot},
value=dropdown_names[var_to_plot[0]],
style={"width": "14rem"},
),
- ],
+ ]
),
- dmc.Stack(
- mt="md",
- children=[
- # Yearly Chart
- title_with_link(
- text="Yearly Chart",
- id_button=IdButtons.YEARLY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- dcc.Loading(
- type="circle",
- children=dmc.Stack(id=ElementIds.YEARLY_CHART),
- ),
- # Daily chart
- title_with_link(
- text="Daily chart",
- id_button=IdButtons.DAILY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- dcc.Loading(
- type="circle",
- children=dmc.Stack(id=ElementIds.DAILY),
- ),
- # Heatmap chart
- title_with_link(
- text="Heatmap chart",
- id_button=IdButtons.HEATMAP_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- dcc.Loading(
- type="circle",
- children=dmc.Stack(id=ElementIds.HEATMAP),
- ),
- # Descriptive statistics
- title_with_tooltip(
- text="Descriptive statistics",
- tooltip_text="count, mean, std, min, max, and percentiles",
- id_button=IdButtons.TABLE_TMP_RH,
- ),
- dmc.Stack(id=ElementIds.TABLE_TMP_HUM),
- ],
+ # Yearly Chart
+ title_with_link(
+ text="Yearly Chart",
+ id_button=IdButtons.YEARLY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(id=ElementIds.YEARLY_CHART),
+ ),
+ # Daily chart
+ title_with_link(
+ text="Daily chart",
+ id_button=IdButtons.DAILY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(id=ElementIds.DAILY),
+ ),
+ # Heatmap chart
+ title_with_link(
+ text="Heatmap chart",
+ id_button=IdButtons.HEATMAP_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(id=ElementIds.HEATMAP),
+ ),
+ # Descriptive statistics
+ title_with_tooltip(
+ text="Descriptive statistics",
+ tooltip_text="count, mean, std, min, max, and percentiles",
+ id_button=IdButtons.TABLE_TMP_RH,
),
+ dmc.Stack(id=ElementIds.TABLE_TMP_HUM),
],
)
From 178f062a54faa1381a6de13929b8e2132d998bea Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:49:16 +1000
Subject: [PATCH 17/23] refactor(layout): Move layout function to improve
structure
Refactor the layout function for tab four by removing the old definition
and ensuring the layout is consistently defined using the Stack component.
---
assets/tabs.css | 13 -------------
pages/sun.py | 18 +++++++++---------
2 files changed, 9 insertions(+), 22 deletions(-)
diff --git a/assets/tabs.css b/assets/tabs.css
index b5e7b14c..7a20c9df 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -94,19 +94,6 @@
background-color: white;
}
-/* Tab Four */
-#tab-four-container {
- width: 100%;
- justify-content: center;
- align-items: center;
-}
-
-/*#tab-four-custom-sun-container {*/
-/* justify-content: space-evenly;*/
-/* align-items: stretch;*/
-/* align-content: center;*/
-/*}*/
-
/* Tab Five */
#tab-five-container {
justify-content: center;
diff --git a/pages/sun.py b/pages/sun.py
index dd9d1c47..afd3dd86 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -54,6 +54,15 @@
sc_dropdown_names.pop("UTCI: no Sun & no Wind : categories", None)
+def layout():
+ """Contents of tab four."""
+ return dmc.Stack(
+ p="md",
+ id=ElementIds.TAB_FOUR_CONTAINER,
+ children=[sun_path(), static_section(), explore_daily_heatmap()],
+ )
+
+
def sun_path():
"""Return the layout for the custom sun path and its dropdowns."""
return dmc.Stack(
@@ -142,15 +151,6 @@ def static_section():
)
-def layout():
- """Contents of tab four."""
- return dmc.Stack(
- p="md",
- id=ElementIds.TAB_FOUR_CONTAINER,
- children=[sun_path(), static_section(), explore_daily_heatmap()],
- )
-
-
@callback(
Output(ElementIds.STATIC_SECTION, "children"),
[Input(ElementIds.ID_SUN_SI_IP_RADIO_INPUT, "value")],
From fd6ede85b7c85b0bb8eb46c3307b2a19acce620b Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 15:54:22 +1000
Subject: [PATCH 18/23] refactor(layout): Update loading component structure
Replace dcc.Loading with dmc.Center for improved layout consistency.
Remove unused CSS styles related to tabs for cleaner codebase.
---
assets/tabs.css | 145 ------------------------------------------------
pages/sun.py | 5 +-
2 files changed, 3 insertions(+), 147 deletions(-)
diff --git a/assets/tabs.css b/assets/tabs.css
index 7a20c9df..594ba325 100644
--- a/assets/tabs.css
+++ b/assets/tabs.css
@@ -9,151 +9,6 @@
height: 10rem
}
-/* Tabs */
-#tabs {
- margin: 0;
-}
-
-#tabs-content {
- padding: 1rem;
-}
-
-#tabs-parent {
- padding: 0 1rem;
- background-color: #003262 !important;
-}
-
-#store-container {
- padding: 0;
-}
-
-#loading-container {
- top: 0;
-}
-
-.custom-tabs-container {
- width: 85%;
-}
-
-.custom-tabs {
- background-color: #f9f9f9;
- padding: 0 24px;
- border-bottom: 1px solid #d6d6d6;
-}
-
-.custom-tab {
- border-color: rgb(238, 236, 236);
- border-top-left-radius: 3px;
- border-top-right-radius: 3px;
- border-top: 3px solid transparent !important;
- border-left: 1px solid lightgrey !important;
- border-right: 1px solid lightgrey !important;
- border-bottom: 1px solid #d6d6d6;
- background-color: #f6f8f8;
- padding: 12px !important;
- font-family: "system-ui";
- display: flex !important;
- align-items: center;
- justify-content: center;
-}
-
-.custom-tab:has(.active) {
- color:#586069;
- background-color: white;
- box-shadow: 1px 1px 0 white;
- border-left: 1px solid lightgrey !important;
- border-right: 1px solid lightgrey !important;
- border-top: 6px solid #abd2ff !important;
- border-bottom: 1px solid transparent;
-}
-
-.nav-pills {
- display: flex;
- flex-wrap: wrap;
-}
-
-@media (max-width: 900px) {
- .nav-pills {
- flex-direction: column;
- }
-}
-
-.nav-pills .nav-link {
- padding: 0;
- color: #586069;
- font-family: "system-ui";
- background-color: transparent;
-}
-
-.nav-pills .nav-link.disabled {
- color: #c8c6c6;
-}
-
-.nav-pills .nav-link.active {
- color: black;
- background-color: white;
-}
-
-/* Tab Five */
-#tab-five-container {
- justify-content: center;
- align-items: stretch;
-}
-
-#tab5-daily-container {
- margin-top: 60px;
-}
-
-#slider-container {
- width: 50%;
- margin: 24px;
-}
-
-.seasonal-graph {
- width: 100%;
- height: 100%;
-}
-
-.daily-wind-graph {
- width: 80%;
- height: 80%;
-}
-
-#daily-wind-rose-container {
- display: flex;
- flex-direction: row;
- max-width: 100%;
-}
-
-#daily-wind-rose-outer-container {
- align-items: stretch;
-}
-
-#slider-container * {
- justify-content: center;
-}
-
-.each-slider {
- padding: 32px 0 0 0;
- width: 60%;
-}
-
-#hour-slider, #month-slider {
- width: 60%;
-}
-
-#wind-speed, #wind-direction {
- width: 100%;
-}
-
-#custom-windrose-container {
- margin-top: 80px;
-}
-
-#tab5-custom-dropdown-container {
- margin-top: 30px;
-}
-
/* Tab Six */
#first-var-dropdown {
diff --git a/pages/sun.py b/pages/sun.py
index afd3dd86..ddccc4c5 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -101,10 +101,11 @@ def sun_path():
),
],
),
- dcc.Loading(
+ dmc.Center(dcc.Loading(
type="circle",
children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"),
- ),
+ ),),
+
],
)
From 25131f71c9243c77bf94844e6bbee89711bb2752 Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 16:00:52 +1000
Subject: [PATCH 19/23] refactor(layout): Replace Box with Stack for tab six
layout
Refactor the layout function to use a Stack component instead of Box,
improving the structure and consistency of the layout in tab six.
---
pages/explorer.py | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/pages/explorer.py b/pages/explorer.py
index c6162dc9..ab8937d1 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -60,6 +60,14 @@
explore_dropdown_names.pop("None", None)
+def layout():
+ """Return the contents of tab six."""
+ return dmc.Stack(
+ p="md",
+ children=[*section_one(), section_two(), section_three()],
+ )
+
+
def section_one_inputs():
"""Return the inputs from section one."""
return dmc.Group(
@@ -78,8 +86,7 @@ def section_one_inputs():
def section_one():
"""Return the graphs for section one"""
- return dmc.Stack(
- children=[
+ return [
section_one_inputs(),
title_with_link(
text="Yearly chart",
@@ -182,8 +189,7 @@ def section_one():
),
# Results table
dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"),
- ],
- )
+ ]
def section_two_inputs():
@@ -548,13 +554,6 @@ def section_three():
)
-def layout():
- """Return the contents of tab six."""
- return dmc.Box(
- children=[section_one(), section_two(), section_three()],
- )
-
-
@callback(
Output(ElementIds.YEARLY_EXPLORE, "children"),
# Section One
From 8c873bce76f3bd8b17cdb6bedc46b5cb228df20d Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 16:01:11 +1000
Subject: [PATCH 20/23] feat(css): remove tabs.css
---
assets/tabs.css | 69 -------------------------------------------------
1 file changed, 69 deletions(-)
delete mode 100644 assets/tabs.css
diff --git a/assets/tabs.css b/assets/tabs.css
deleted file mode 100644
index 594ba325..00000000
--- a/assets/tabs.css
+++ /dev/null
@@ -1,69 +0,0 @@
-*[data-dash-is-loading="true"]{
- visibility: hidden;
-}
-*[data-dash-is-loading="true"]::before{
- content: "";
- display: inline-block;
- color: magenta;
- visibility: visible;
- height: 10rem
-}
-
-/* Tab Six */
-
-#first-var-dropdown {
- width: 15%;
-}
-
-.month-hour-slider {
- width: 25%;
-}
-
-.var-dropdown {
- width: 35%;
-}
-
-#min-val, #max-val {
- width: 15%;
-}
-
-.row-center {
- align-items: center;
- justify-content: center;
-}
-
-.text-next-to-input {
- margin-bottom: 0;
- margin-right: 1rem;
- text-align: right;
-}
-
-#sec1-var-dropdown {
- width: 25%;
-}
-
-.three-inputs-container {
- justify-content: space-evenly;
-}
-
-.one-of-three-container {
- width: 30%;
- align-self: flex-start;
- align-items: stretch;
-}
-
-#tab6-sec2-container {
- align-items: stretch;
-}
-
-.survey-alert {
- color: white;
- background-color: #0c2772;
- opacity: 0.98;
- font-family: "system-ui";
- font-size: 15px;
- border-radius: 0.25rem;
- border: 0.5px solid lightgrey;
- z-index: 1000;
-}
-
From 0684fc4ab1b48cb022a21aa75cba99b091e6d45e Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 16:02:41 +1000
Subject: [PATCH 21/23] refactor(viz): Remove y-axis range setting hhd and ccd
Eliminate fixed y-axis range to allow for dynamic scaling based on data.
---
pages/summary.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/pages/summary.py b/pages/summary.py
index d8da56d3..7f11a30e 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -338,7 +338,6 @@ def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip):
template=template,
dragmode=False,
legend=dict(orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1),
- yaxis=dict(range=[-100, 400]),
)
fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
From 93198f1fb408b4c87568db486c7622a5ad7b7bae Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 16:09:34 +1000
Subject: [PATCH 22/23] refactor(ui): Remove inline styles from dropdown
components
Clean up the code by removing hardcoded width styles from various dropdowns
to improve layout consistency and maintainability.
---
pages/lib/utils.py | 1 +
pages/sun.py | 3 ---
pages/t_rh.py | 1 -
pages/wind.py | 4 ----
4 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/pages/lib/utils.py b/pages/lib/utils.py
index d31af407..c95c9084 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -278,6 +278,7 @@ def dropdown(options=None, **kwargs):
return dcc.Dropdown(
options=[{"label": k, "value": v} for k, v in options.items()],
clearable=False,
+ style={"width": "14rem"},
**kwargs,
)
diff --git a/pages/sun.py b/pages/sun.py
index ddccc4c5..222f5cc1 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -84,7 +84,6 @@ def sun_path():
"Cartesian": "cartesian",
},
value="polar",
- style={"width": "10rem"},
),
],
),
@@ -97,7 +96,6 @@ def sun_path():
id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN,
options=sc_dropdown_names,
value="None",
- style={"width": "20rem"},
),
],
),
@@ -129,7 +127,6 @@ def explore_daily_heatmap():
id=ElementIds.TAB_EXPLORE_DROPDOWN,
options=sun_cloud_tab_explore_dropdown_names,
value="glob_hor_rad",
- style={"width": "20rem"},
),
],
),
diff --git a/pages/t_rh.py b/pages/t_rh.py
index 39d38993..a9c01b63 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -42,7 +42,6 @@ def layout():
id=ElementIds.ID_T_RH_DROPDOWN,
options={var: dropdown_names[var] for var in var_to_plot},
value=dropdown_names[var_to_plot[0]],
- style={"width": "14rem"},
),
]
),
diff --git a/pages/wind.py b/pages/wind.py
index 09738699..195f4b6f 100644
--- a/pages/wind.py
+++ b/pages/wind.py
@@ -233,7 +233,6 @@ def custom_wind_rose():
for i, j in enumerate(month_lst)
},
value=1,
- style={"width": "6rem"},
),
],
),
@@ -248,7 +247,6 @@ def custom_wind_rose():
str(i) + ":00": i for i in range(0, 24)
},
value=0,
- style={"width": "6rem"},
),
],
),
@@ -271,7 +269,6 @@ def custom_wind_rose():
for i, j in enumerate(month_lst)
},
value=12,
- style={"width": "6rem"},
),
],
),
@@ -286,7 +283,6 @@ def custom_wind_rose():
str(i) + ":00": i for i in range(1, 25)
},
value=24,
- style={"width": "6rem"},
),
],
),
From 0ce33652848d68438091d09a2f66e8cfc9869b18 Mon Sep 17 00:00:00 2001
From: federico tartarini
Date: Fri, 26 Sep 2025 16:11:44 +1000
Subject: [PATCH 23/23] refactor(explorer): Clean up section one layout code
Reorganize the layout code in section one for better readability and maintainability.
This includes consistent indentation and formatting adjustments.
---
pages/explorer.py | 198 +++++++++++++++++++++++-----------------------
pages/sun.py | 11 +--
2 files changed, 103 insertions(+), 106 deletions(-)
diff --git a/pages/explorer.py b/pages/explorer.py
index ab8937d1..f40b4e8c 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -87,109 +87,105 @@ def section_one_inputs():
def section_one():
"""Return the graphs for section one"""
return [
- section_one_inputs(),
- title_with_link(
- text="Yearly chart",
- id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- dcc.Loading(
- type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE, p="sm")
- ),
- title_with_link(
- text="Daily chart",
- id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- dcc.Loading(
- type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY, p="sm")
- ),
- title_with_link(
- text="Heatmap chart",
- id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- dcc.Loading(
- type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP, p="sm")
- ),
- title_with_tooltip(
- text="Descriptive statistics",
- tooltip_text="count, mean, std, min, max, and percentiles",
- id_button=IdButtons.TABLE_EXPLORE,
- ),
- dmc.Center(
- children=dmc.Box(
- w="33%",
- children=dmc.Stack(
- children=[
- dmc.Button(
- "Apply month and hour filter",
- id=ElementIds.SEC1_TIME_FILTER_INPUT,
- color="blue",
- ),
- dmc.Group(
- children=[
- dmc.Title("Month Range", order=5),
- dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
- id=ElementIds.SEC1_MONTH_SLIDER,
- min=1,
- max=12,
- step=1,
- value=[1, 12],
- marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
- ),
- ),
- dcc.Checklist(
- id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
- ),
- ],
- ),
- dmc.Group(
- children=[
- dmc.Title("Hour Range", order=5),
- dmc.Stack(
- flex=1,
- children=dcc.RangeSlider(
- id=ElementIds.SEC1_HOUR_SLIDER,
- min=0,
- max=24,
- step=1,
- value=[0, 24],
- marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
- ),
+ section_one_inputs(),
+ title_with_link(
+ text="Yearly chart",
+ id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
+ dcc.Loading(
+ type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE, p="sm")
+ ),
+ title_with_link(
+ text="Daily chart",
+ id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
+ dcc.Loading(
+ type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY, p="sm")
+ ),
+ title_with_link(
+ text="Heatmap chart",
+ id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
+ doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
+ ),
+ dcc.Loading(
+ type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP, p="sm")
+ ),
+ title_with_tooltip(
+ text="Descriptive statistics",
+ tooltip_text="count, mean, std, min, max, and percentiles",
+ id_button=IdButtons.TABLE_EXPLORE,
+ ),
+ dmc.Center(
+ children=dmc.Box(
+ w="33%",
+ children=dmc.Stack(
+ children=[
+ dmc.Button(
+ "Apply month and hour filter",
+ id=ElementIds.SEC1_TIME_FILTER_INPUT,
+ color="blue",
+ ),
+ dmc.Group(
+ children=[
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC1_MONTH_SLIDER,
+ min=1,
+ max=12,
+ step=1,
+ value=[1, 12],
+ marks={1: "1", 12: "12"},
+ tooltip={
+ "always_visible": False,
+ "placement": "top",
+ },
+ allowCross=False,
),
- dcc.Checklist(
- id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE,
- options=[
- {"label": "Invert", "value": "invert"}
- ],
- value=[],
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
+ ),
+ ],
+ ),
+ dmc.Group(
+ children=[
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
+ flex=1,
+ children=dcc.RangeSlider(
+ id=ElementIds.SEC1_HOUR_SLIDER,
+ min=0,
+ max=24,
+ step=1,
+ value=[0, 24],
+ marks={0: "0", 24: "24"},
+ tooltip={
+ "always_visible": False,
+ "placement": "topLeft",
+ },
+ allowCross=False,
),
- ],
- ),
- ],
- ),
- )
- ),
- # Results table
- dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"),
- ]
+ ),
+ dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE,
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
+ ),
+ ],
+ ),
+ ],
+ ),
+ )
+ ),
+ # Results table
+ dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"),
+ ]
def section_two_inputs():
diff --git a/pages/sun.py b/pages/sun.py
index 222f5cc1..5307acc2 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -99,11 +99,12 @@ def sun_path():
),
],
),
- dmc.Center(dcc.Loading(
- type="circle",
- children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"),
- ),),
-
+ dmc.Center(
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"),
+ ),
+ ),
],
)