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%"), + ), + ), ], )