diff --git a/Pipfile b/Pipfile
index 9128883..0c01f3d 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 15c208f..8e475b6 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/construction.css b/assets/construction.css
deleted file mode 100644
index 930d108..0000000
--- 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 b334dcd..0000000
--- 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/layout.css b/assets/layout.css
index e9089db..42ed6cb 100644
--- a/assets/layout.css
+++ b/assets/layout.css
@@ -29,4 +29,3 @@
width: 100%; //or any percentage width you want
}
-
diff --git a/assets/tabs.css b/assets/tabs.css
deleted file mode 100644
index 15590df..0000000
--- a/assets/tabs.css
+++ /dev/null
@@ -1,288 +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
-}
-
-/* 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 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;
-}
-
-/* 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;
- 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 {
- 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;
-}
\ No newline at end of file
diff --git a/main.py b/main.py
index e5c2366..cdf1b94 100644
--- a/main.py
+++ b/main.py
@@ -1,38 +1,23 @@
-import dash_bootstrap_components as dbc
-from dash import html, dcc
-from dash_extensions.enrich import Output, Input, callback
+from dash import dcc
+import dash_mantine_components as dmc
from app import app
-from pages.lib.layout import banner, footer, build_tabs
+from pages.lib.layout import create_collapsible_layout
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(
children=[
- dcc.Location(id="url", refresh=False), # connected to callback below
- banner(),
- html.Div(id="page-content", children=build_tabs()),
- footer(),
+ dcc.Location(id=ElementIds.MAIN_URL, refresh=False),
+ 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_server(
+ app.run(
debug=AppConfig.DEBUG,
host=AppConfig.HOST,
port=AppConfig.PORT,
diff --git a/pages/explorer.py b/pages/explorer.py
index dde7df5..f40b4e8 100644
--- a/pages/explorer.py
+++ b/pages/explorer.py
@@ -1,6 +1,6 @@
import dash
-from dash import dcc, html
-import dash_bootstrap_components as dbc
+from dash import dcc
+import dash_mantine_components as dmc
from dash_extensions.enrich import Output, Input, State, callback
from dash.exceptions import PreventUpdate
@@ -22,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,
@@ -62,12 +60,21 @@
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 html.Div(
- className="container-row full-width row-center",
+ return dmc.Group(
+ mt="md",
+ justify="center",
children=[
- html.H4(className="text-next-to-input", children=["Select a variable: "]),
+ dmc.Title("Select a variable:", order=5),
dropdown(
id=ElementIds.SEC1_VAR_DROPDOWN,
options=explore_dropdown_names,
@@ -79,187 +86,147 @@ def section_one_inputs():
def section_one():
"""Return the graphs for section one"""
- return html.Div(
- className="container-col full-width",
- children=[
- section_one_inputs(),
- html.Div(
- children=title_with_link(
- text="Yearly chart",
- id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- ),
- dcc.Loading(
- type="circle",
- children=html.Div(id=ElementIds.YEARLY_EXPLORE, className="full-width"),
- ),
- html.Div(
- children=title_with_link(
- text="Daily chart",
- id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- ),
- dcc.Loading(
- html.Div(className="full-width", id=ElementIds.QUERY_DAILY),
- type="circle",
- ),
- html.Div(
- children=title_with_link(
- text="Heatmap chart",
- id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- ),
- dcc.Loading(
- html.Div(className="full-width", id=ElementIds.QUERY_HEATMAP),
- type="circle",
- ),
- html.Div(
- children=title_with_tooltip(
- text="Descriptive statistics",
- tooltip_text="count, mean, std, min, max, and percentiles",
- id_button=IdButtons.TABLE_EXPLORE,
- ),
- ),
- html.Div(
- className="container-row justify-content-center",
- children=[
- html.Div(
- 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,
- ),
- html.Div(
- className=(
- "container-row full-width justify-center mt-2"
- ),
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
- 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%"},
- ),
- dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE,
- labelStyle={"flex": "30%"},
- ),
- ],
- ),
- html.Div(
- className="container-row justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
- 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%"},
+ 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(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
- id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE,
- labelStyle={"flex": "30%"},
+ ),
+ 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,
),
- ],
- ),
- ],
- ),
- ],
- ),
- html.Div(
- id=ElementIds.TABLE_DATA_EXPLORER,
- ),
- ],
- )
+ ),
+ 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():
"""Return all the input forms from section two."""
- return html.Div(
+ return dmc.Stack(
+ p="md",
children=[
- html.Div(
- 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,
),
- html.Div(
- className="container-row full-width three-inputs-container",
+ dmc.SimpleGrid(
+ cols=3,
+ spacing="md",
children=[
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- html.Div(
- 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.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",
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply month and hour filter",
- color="primary",
id=ElementIds.SEC2_TIME_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
+ color="blue",
),
- html.Div(
- className=(
- "container-row full-width justify-center mt-2"
- ),
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.SEC2_MONTH_SLIDER,
min=1,
@@ -269,27 +236,24 @@ def section_two_inputs():
marks={1: "1", 12: "12"},
tooltip={
"always_visible": False,
- "placement": "top",
+ "placement": "topLeft",
},
- allowCross=False,
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
+ id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
options=[
- {"label": "Invert", "value": "invert"},
+ {"label": "Invert", "value": "invert"}
],
value=[],
- id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP,
- labelStyle={"flex": "30%"},
),
],
),
- html.Div(
- className="container-row justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.SEC2_HOUR_SLIDER,
min=0,
@@ -301,76 +265,63 @@ def section_two_inputs():
"always_visible": False,
"placement": "topLeft",
},
- allowCross=False,
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
+ id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
options=[
- {"label": "Invert", "value": "invert"},
+ {"label": "Invert", "value": "invert"}
],
value=[],
- id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP,
- labelStyle={"flex": "30%"},
),
],
),
],
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply filter",
- color="primary",
id=ElementIds.SEC2_DATA_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
+ color="blue",
),
- html.Div(
- 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.Group(
+ [
+ dmc.Title("Filter Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.SEC2_DATA_FILTER_VAR,
+ options=explore_dropdown_names,
+ value=ColNames.RH,
+ ),
+ flex=1,
),
],
),
- html.Div(
- 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.Group(
+ [
+ dmc.Title("Min Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.SEC2_MIN_VAL,
+ placeholder="Enter a number for the min val",
+ value=0,
+ ),
+ flex=1,
),
],
),
- html.Div(
- 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.Group(
+ [
+ dmc.Title("Max Value:", order=5),
+ dmc.Stack(
+ dmc.NumberInput(
+ id=ElementIds.SEC2_MAX_VAL,
+ placeholder="Enter a number for the max val",
+ value=100,
+ ),
+ flex=1,
),
],
),
@@ -384,205 +335,184 @@ def section_two_inputs():
def section_two():
"""Return the two graphs in section two."""
- return html.Div(
+ return dmc.Stack(
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.Paper(
+ id=ElementIds.CUSTOM_HEATMAP,
+ p="sm",
+ ),
),
- dbc.Checklist(
- options=[
- {"label": "Normalize", "value": "normal"},
+ dmc.Group(
+ children=[
+ dmc.CheckboxGroup(
+ id=ElementIds.NORMALIZE,
+ value=[],
+ children=[
+ dmc.Checkbox(label="Normalize", value="normal"),
+ ],
+ ),
],
- value=[],
- id=ElementIds.NORMALIZE,
),
dcc.Loading(
type="circle",
- children=[
- dcc.Graph(
- className="full-width",
+ children=dmc.Paper(
+ children=dcc.Graph(
id=ElementIds.CUSTOM_SUMMARY,
config=fig_config,
),
- ],
+ ),
),
],
)
def section_three_inputs():
- """"""
- return html.Div(
- className="container-row full-width three-inputs-container",
+ return dmc.SimpleGrid(
+ cols=3,
children=[
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- html.Div(
- 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.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,
),
],
),
- html.Div(
- 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.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,
),
],
),
- html.Div(
- 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.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,
),
],
),
],
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply month and hour filter",
- color="primary",
id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
+ color="blue",
),
- html.Div(
- className="container-row full-width justify-center",
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
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,
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS,
- labelStyle={"flex": "30%"},
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
- html.Div(
- className="container-row full-width justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
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",
+ "placement": "topLeft",
},
- allowCross=False,
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS,
- labelStyle={"flex": "30%"},
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
],
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply filter",
- color="primary",
id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT,
- className="mb-2",
- n_clicks=0,
+ color="blue",
),
- html.Div(
- 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%"},
+ 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,
),
],
),
- html.Div(
- 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.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,
),
],
),
- html.Div(
- 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.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,
),
],
),
@@ -594,37 +524,32 @@ def section_three_inputs():
def section_three():
"""Return the two graphs in section three."""
- return html.Div(
- className="container-col full-width",
+ return dmc.Stack(
children=[
- html.Div(
- 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(),
dcc.Loading(
- html.Div(id=ElementIds.THREE_VAR),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.THREE_VAR,
+ p="sm",
+ ),
),
dcc.Loading(
- html.Div(id=ElementIds.TWO_VAR),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.TWO_VAR,
+ p="sm",
+ ),
),
],
)
-def layout():
- """Return the contents of tab six."""
- return html.Div(
- className="justify-center",
- children=[section_one(), section_two(), section_three()],
- )
-
-
@callback(
Output(ElementIds.YEARLY_EXPLORE, "children"),
# Section One
@@ -643,7 +568,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",
@@ -781,7 +706,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",
@@ -902,7 +827,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/global_element_ids.py b/pages/lib/global_element_ids.py
index 644dcd3..63709a8 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,8 +213,23 @@ 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"
+ NAVBAR = "navbar"
+ NAV_GROUP_MAIN = "nav-group-main"
+ NAV_GROUP_CONTROLS = "nav-group-controls"
+ NAV_DOC_LINK = "nav-doc-link"
+ SELECT_URL = "url"
+ MAIN_URL = "url"
+ 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"
+ 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 f6cff01..71b1119 100644
--- a/pages/lib/layout.py
+++ b/pages/lib/layout.py
@@ -1,6 +1,5 @@
-import dash_bootstrap_components as dbc
import dash
-from dash import dcc, html
+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
@@ -8,222 +7,306 @@
from pages.lib.global_element_ids import ElementIds
-def alert():
- """Alert for survey."""
- return html.Div(
- id=ElementIds.ALERT_CONTAINER,
+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",
+ }
+
+ 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 create_navbar():
+ nav_link_styles = {
+ "root": {
+ "borderRadius": "0.375rem",
+ "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 = [
+ dmc.NavLink(
+ label=page[ColNames.NAME],
+ leftSection=DashIconify(
+ icon=NavBarIcons.get_icon(page[ColNames.NAME]), width=20
+ ),
+ href=page[ColNames.PATH],
+ id=f"nav-{page[ColNames.PATH].replace('/', '')}",
+ active=False,
+ styles=nav_link_styles,
+ )
+ for page in dash.page_registry.values()
+ if page[ColNames.NAME] not in ["404"]
+ ]
+
+ parent_group = dmc.NavLink(
+ label="Pages Menu",
+ children=sub_links,
+ id=ElementIds.NAV_GROUP_MAIN,
+ variant="light",
+ childrenOffset=0,
+ opened=True,
+ )
+
+ segmented_control_styles = {
+ "control": {"flex": 1, "minWidth": 0},
+ }
+
+ controls_stack = dmc.Stack(
+ gap="xs",
+ py="xs",
children=[
- dbc.Toast(
- [
- "If you have a moment, help us improve Clima and take a ",
- html.A(
- "quick user survey",
- href="https://forms.gle/k289zP3R92jdu14M7",
- className="alert-link",
- target="_blank",
- ),
- "! ☀️",
- ],
- id=ElementIds.ID_LAYOUT_ALERT_AUTO,
- header="CBE Clima User Survey",
- icon="info",
- is_open=False,
- dismissable=True,
- className="survey-alert",
- style={"position": "fixed", "top": 25, "right": 10, "width": 400},
+ 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,
+ ),
),
- dcc.Interval(
- id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT,
- interval=12 * 1000,
- n_intervals=0,
+ 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,
+ ),
),
],
)
+ # Tools
+ controls_group = dmc.NavLink(
+ label="Tools Menu",
+ children=[controls_stack],
+ id=ElementIds.NAV_GROUP_CONTROLS,
+ variant="light",
+ childrenOffset=0,
+ )
-def footer():
- """Build the footer at the bottom of the page."""
- return dbc.Row(
- align="center",
- justify="between",
- id=ElementIds.FOOTER_CONTAINER,
- children=[
- dbc.Col(
+ # Documentation
+ doc_link = dmc.NavLink(
+ label="Documentation",
+ href=DocLinks.MAIN.value,
+ target="_blank",
+ id=ElementIds.NAV_DOC_LINK,
+ variant="light",
+ )
+
+ return dmc.ScrollArea(
+ children=[parent_group, controls_group, doc_link],
+ )
+
+
+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=[
- dbc.Row(
- html.A(
- children=[
- html.Img(
- src="assets/img/cbe-logo.png",
- )
- ],
- href="https://cbe.berkeley.edu/",
- )
+ dmc.Title(
+ "CBE Clima Tool",
+ id=ElementIds.BANNER_TITLE,
+ order=2,
+ lh=1.1,
+ c="white",
+ ),
+ dmc.Text(
+ "Current Location: N/A",
+ id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
+ size="sm",
+ opacity=0.85,
+ style={"overflow": "hidden"},
+ c="white",
),
],
- width=12,
- md=4,
- style={"padding": "15px"},
+ p="xs",
),
- dbc.Col(
- children=[
- dbc.Row(
- [
- 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).
- """
- ),
- 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",
- ),
- dmc.Anchor(
- "Contributors",
- href="https://cbe-berkeley.gitbook.io/clima/#contributions",
- underline=True,
- c="white",
- target="_blank",
- ),
- dmc.Anchor(
- "Report issues on GitHub",
- href="https://github.com/CenterForTheBuiltEnvironment/clima/issues",
- underline=True,
- c="white",
- target="_blank",
- ),
- dmc.Anchor(
- "Contact us",
- href="https://github.com/CenterForTheBuiltEnvironment/clima/discussions",
- underline=True,
- c="white",
- target="_blank",
- ),
- dmc.Anchor(
- "Documentation",
- href="https://center-for-the-built-environment.gitbook.io/clima/",
- underline=True,
- c="white",
- target="_blank",
- ),
- dmc.Anchor(
- "License",
- href="https://center-for-the-built-environment.gitbook.io/clima/#license",
- underline=True,
- c="white",
- target="_blank",
- ),
- ],
- spacing="sm",
- style={"marginTop": "1rem"},
- ),
- ],
- style={"marginTop": "1rem"},
+ 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="always",
),
+ "! ☀️",
],
- width=12,
- md=8,
+ 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,
+ w=400,
+ pos="fixed",
+ top="1em",
+ right="1em",
+ style={"zIndex": 1002, "display": "none"},
),
],
+ pl="md",
)
-def banner():
- """Build the banner at the top of the page."""
- return html.Div(
- id=ElementIds.BANNER,
- children=[
- dmc.Group(
- position="apart",
- align="center",
+def create_footer():
+ white_anchor_style = {
+ "underline": "always",
+ "c": "white",
+ "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=[
- dmc.Group(
- align="center",
- children=[
- html.A(
- href="/",
- children=[
- dmc.Image(
- src="assets/img/cbe-logo-small.png",
- height=40,
- width="auto",
- )
- ],
- ),
- dmc.Stack(
- spacing=0,
- children=[
- dmc.Title(
- "CBE Clima Tool",
- order=1,
- id=ElementIds.BANNER_TITLE,
- style={"fontSize": "2rem"},
- ),
- dmc.Text(
- "Current Location:",
- id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE,
- size="sm",
- ),
- ],
- ),
- ],
+ 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": "1rem",
+ "lineHeight": 1.3,
+ "fontWeight": 400,
+ "color": "white",
+ "textAlign": "left",
+ },
),
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,
- },
- ],
- ),
+ [
+ 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",
+ justify="flex-start",
+ align="center",
)
-def store():
- return html.Div(
+def create_stores():
+ return dmc.Box(
id=ElementIds.STORE,
children=[
dcc.Store(id=ElementIds.ID_LAYOUT_DF_STORE, storage_type="session"),
@@ -231,53 +314,115 @@ 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 html.Div(
- 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,
- )
- ],
+def create_collapsible_layout():
+ return dmc.AppShell(
+ [
+ dmc.AppShellHeader(
+ create_header(),
+ bg="#003262",
),
- html.Div(
- id=ElementIds.STORE_CONTAINER,
- children=[
- store(),
- html.Div(
- id=ElementIds.TABS_CONTENT,
- children=[
- alert(), # alert can be removed after survey is done
- dash.page_container,
- ],
- ),
- ],
+ dmc.AppShellNavbar(
+ id=ElementIds.NAVBAR,
+ children=create_navbar(),
+ 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: 48rem)": {
+ "left": "0",
+ },
+ },
),
],
+ header={"height": 80},
+ navbar={
+ "width": 230,
+ "breakpoint": "sm",
+ "collapsed": {"mobile": True, "desktop": False},
+ "id": ElementIds.NAVBAR_CONTAINER,
+ },
+ id=ElementIds.APP_SHELL,
)
+
+
+@callback(
+ [
+ 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_navbar_and_width(
+ burger_opened, tools_opened, pages_opened, navbar, tools_expanded
+):
+ navbar["collapsed"] = {"mobile": not burger_opened, "desktop": not burger_opened}
+
+ WIDTHS = {"default": 230, "pages": 230, "tools": 230}
+
+ 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"]
+
+ return navbar, tools_expanded
+
+
+@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.MAIN_URL, "pathname"),
+ prevent_initial_call=True,
+)
+def update_nav_active_state(pathname):
+ return [
+ pathname == page[ColNames.PATH]
+ for page in dash.page_registry.values()
+ if page[ColNames.NAME] not in ["404"]
+ ]
+
+
+@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):
+ return {"display": "block" if n_intervals == 1 else "none"}
diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py
index f8e3641..4916381 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(
@@ -642,7 +643,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 +675,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 539ca23..c95c908 100644
--- a/pages/lib/utils.py
+++ b/pages/lib/utils.py
@@ -3,9 +3,9 @@
import time
import math
-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
@@ -147,38 +147,34 @@ def generate_custom_inputs_psy(
def title_with_tooltip(text, tooltip_text, id_button):
- display_tooltip = "none"
if tooltip_text:
- display_tooltip = "block"
-
- return html.Div(
- className="container-row",
- style={"padding": "1rem", "marginTop": "1rem"},
- children=[
- html.H5(text, style={"marginRight": "0.5rem"}),
- html.Div(
- [
- html.Sup(
- html.Img(
+ return dmc.Group(
+ children=[
+ dmc.Title(text, order=3),
+ dmc.Tooltip(
+ label=tooltip_text,
+ position="right",
+ withArrow=True,
+ children=[
+ dmc.Image(
id=id_button,
- src="../assets/icons/help.png",
+ src="/assets/icons/help.png",
alt="help",
- style={
- "width": "1rem",
- "height": "1rem",
- },
- ),
- ),
- dbc.Tooltip(
- tooltip_text,
- target=id_button,
- placement="right",
- ),
- ],
- style={"display": display_tooltip},
- ),
- ],
- )
+ w=16,
+ h=16,
+ )
+ ],
+ ),
+ ],
+ )
+ else:
+ return dmc.Group(
+ mt="md",
+ px="md",
+ children=[
+ dmc.Title(text, order=3),
+ ],
+ )
def title_with_link(
@@ -187,33 +183,25 @@ def title_with_link(
id_button=None,
doc_link: str = "",
):
- return html.Div(
- className="container-row",
- style={"padding": "1rem", "marginTop": "1rem"},
+ return dmc.Group(
children=[
- html.H5(text, style={"marginRight": "0.5rem"}),
- html.Div(
- [
- 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,
+ children=[
+ html.A(
+ dmc.Image(
+ id=id_button,
+ src="/assets/icons/book.png",
+ alt="book",
+ w=16,
+ h=16,
),
- ),
- dbc.Tooltip(
- tooltip_text,
- target=id_button,
- placement="right",
- ),
+ href=doc_link,
+ target="_blank",
+ )
],
),
],
@@ -247,9 +235,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"},
@@ -288,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/natural_ventilation.py b/pages/natural_ventilation.py
index ee002e2..4cd2cc9 100644
--- a/pages/natural_ventilation.py
+++ b/pages/natural_ventilation.py
@@ -1,6 +1,6 @@
import dash
-from dash import dcc, html
-import dash_bootstrap_components as dbc
+from dash import dcc
+import dash_mantine_components as dmc
from dash_extensions.enrich import Output, Input, State, callback
import numpy as np
@@ -12,8 +12,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
@@ -41,13 +39,7 @@
def layout():
- return html.Div(
- className="container-col",
- id=ElementIds.MAIN_NV_SECTION,
- children=[
- #
- ],
- )
+ return dmc.Stack(p="md", id=ElementIds.MAIN_NV_SECTION)
@callback(
@@ -65,119 +57,104 @@ def update_layout(si_ip):
dpt_set = 16
return [
- html.Div(
- 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(
- html.Div(
+ type="circle",
+ children=dmc.Paper(
id=ElementIds.NV_HEATMAP_CHART,
- style={"marginTop": "1rem"},
),
- type="circle",
),
- html.Div(
- className="container-row align-center justify-center",
+ dmc.Group(
+ justify="center",
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,
+ color="blue",
+ style={"padding": "1rem", "marginRight": "-2rem"},
),
- html.Div(
- 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(
- html.Div(
+ type="circle",
+ children=dmc.Paper(
id=ElementIds.NV_BAR_CHART,
- style={"marginTop": "1rem"},
),
- type="circle",
),
]
def inputs_tab(t_min, t_max, d_set):
- return html.Div(
- className="container-row full-width three-inputs-container",
+ return dmc.SimpleGrid(
+ cols=3,
+ spacing="md",
children=[
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply filter",
- color="primary",
+ color="blue",
id=ElementIds.NV_DBT_FILTER,
- className="mb-2",
+ variant="link",
n_clicks=1,
),
- html.H6("Outdoor dry-bulb air temperature range"),
- html.Div(
- 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.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,
),
],
),
- html.Div(
- 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.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,
),
],
),
- ],
+ ]
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply month and hour filter",
- color="primary",
+ color="blue",
id=ElementIds.NV_MONTH_HOUR_FILTER,
- className="mb-2",
- n_clicks=0,
+ variant="link",
),
- html.Div(
- className="container-row full-width justify-center mt-2",
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.NV_MONTH_SLIDER,
min=1,
@@ -185,29 +162,20 @@ def inputs_tab(t_min, t_max, d_set):
step=1,
value=[1, 12],
marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
+ options=[{"label": "Invert", "value": "invert"}],
value=[],
id=ElementIds.INVERT_MONTH_NV,
- labelStyle={"flex": "30%"},
),
],
),
- html.Div(
- className="container-row align-center justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.NV_HOUR_SLIDER,
min=0,
@@ -215,70 +183,56 @@ def inputs_tab(t_min, t_max, d_set):
step=1,
value=[0, 24],
marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
+ options=[{"label": "Invert", "value": "invert"}],
value=[],
id=ElementIds.INVERT_HOUR_NV,
- labelStyle={"flex": "30%"},
),
],
),
- ],
+ ]
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply filter",
- color="primary",
+ color="blue",
id=ElementIds.NV_DPT_FILTER,
- className="mb-2",
- n_clicks=0,
+ variant="link",
disabled=True,
),
- dbc.Checklist(
+ 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."
+ "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,
),
- html.Div(
- 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.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,
),
],
),
- ],
+ ]
),
],
)
@@ -343,12 +297,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"},
),
)
@@ -457,7 +414,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"),
],
[
@@ -538,7 +495,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 ff3b389..9fc307e 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,
@@ -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 0e4885a..a7d9ba4 100644
--- a/pages/outdoor.py
+++ b/pages/outdoor.py
@@ -1,6 +1,6 @@
import dash
from dash import dcc, html
-import dash_bootstrap_components as dbc
+import dash_mantine_components as dmc
from dash_extensions.enrich import Output, Input, State, callback
import numpy as np
@@ -36,52 +36,38 @@
def inputs_outdoor_comfort():
- return dbc.Row(
- className="container-row full-width three-inputs-container",
+ return dmc.SimpleGrid(
+ cols=2,
children=[
- dbc.Col(
- md=6,
- sm=12,
- children=[
- html.Div(
- className="container-row center-block",
- children=[
- html.H4(
- children=["Select a scenario:"],
- style={"flex": "30%"},
- ),
- dropdown(
- id=ElementIds.TAB7_DROPDOWN,
- style={"flex": "60%"},
- options=outdoor_dropdown_names,
- value="utci_Sun_Wind",
- ),
- html.Div(
- id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"}
- ),
- ],
+ 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",
),
- dbc.Col(
- md=6,
- sm=12,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply month and hour filter",
- color="primary",
- style={
- "width": "100%",
- },
id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT,
- className="mb-2",
- n_clicks=0,
+ variant="filled",
+ color="blue",
),
- html.Div(
- className="container-row full-width justify-center mt-2",
- children=[
- html.H6("Month Range", style={"flex": "5%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER,
min=1,
@@ -89,29 +75,21 @@ def inputs_outdoor_comfort():
step=1,
value=[1, 12],
marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ tooltip={"always_visible": False},
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT,
- labelStyle={"flex": "30%"},
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
- html.Div(
- className="container-row align-center justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "5%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER,
min=0,
@@ -119,21 +97,14 @@ def inputs_outdoor_comfort():
step=1,
value=[0, 24],
marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
+ tooltip={"always_visible": False},
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT,
- labelStyle={"flex": "30%"},
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
@@ -144,76 +115,76 @@ def inputs_outdoor_comfort():
def outdoor_comfort_chart():
- return html.Div(
+ return dmc.Stack(
children=[
- html.Div(id=ElementIds.OUTDOOR_COMFORT_OUTPUT),
- html.Div(
- 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,
+ ),
+ title_with_link(
+ text="UTCI heatmap chart",
+ id_button=IdButtons.UTCI_CHARTS_LABEL,
+ doc_link=DocLinks.UTCI_CHART,
),
dcc.Loading(
- html.Div(id=ElementIds.UTCI_HEATMAP),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.UTCI_HEATMAP,
+ ),
),
- html.Div(
- children=title_with_link(
- text="UTCI thermal stress chart",
- id_button=IdButtons.UTCI_CHARTS_LABEL,
- doc_link=DocLinks.UTCI_CHART,
- )
+ title_with_link(
+ text="UTCI thermal stress chart",
+ id_button=IdButtons.UTCI_CHARTS_LABEL,
+ doc_link=DocLinks.UTCI_CHART,
),
dcc.Loading(
- html.Div(id=ElementIds.UTCI_CATEGORY_HEATMAP),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.UTCI_CATEGORY_HEATMAP,
+ ),
),
- html.Div(
- className="container-row align-center justify-center",
+ dmc.Group(
+ justify="center",
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",
- },
+ checked=True,
+ color="blue",
),
- html.Div(
- 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,
),
],
),
dcc.Loading(
- html.Div(id=ElementIds.UTCI_SUMMARY_CHART),
type="circle",
+ children=dmc.Paper(
+ id=ElementIds.UTCI_SUMMARY_CHART,
+ # p="sm",
+ ),
),
],
)
def layout():
- return (
- dcc.Loading(
- type="circle",
- children=html.Div(
- className="container-col",
- children=[inputs_outdoor_comfort(), outdoor_comfort_chart()],
+ return dmc.Stack(
+ p="md",
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ children=[
+ inputs_outdoor_comfort(),
+ outdoor_comfort_chart(),
+ ],
+ ),
),
- ),
+ ],
)
@@ -406,7 +377,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 bf96775..4aca01e 100644
--- a/pages/psy-chart.py
+++ b/pages/psy-chart.py
@@ -1,6 +1,6 @@
import dash
-from dash import dcc, html
-import dash_bootstrap_components as dbc
+from dash import dcc
+import dash_mantine_components as dmc
from dash_extensions.enrich import Output, Input, State, callback
from copy import deepcopy
@@ -11,14 +11,11 @@
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 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,
@@ -60,47 +57,36 @@
def inputs():
- """"""
- return html.Div(
- className="container-row full-width three-inputs-container",
+ return dmc.SimpleGrid(
+ cols=3,
children=[
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- html.Div(
- 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.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",
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply month and hour filter",
- color="primary",
id=ElementIds.MONTH_HOUR_FILTER,
- className="mb-2",
- n_clicks=0,
+ color="blue",
),
- html.Div(
- className="container-row full-width justify-center mt-2",
- children=[
- html.H6("Month Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Month Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.PSY_MONTH_SLIDER,
min=1,
@@ -108,29 +94,21 @@ def inputs():
step=1,
value=[1, 12],
marks={1: "1", 12: "12"},
- tooltip={
- "always_visible": False,
- "placement": "top",
- },
- allowCross=False,
+ tooltip={"always_visible": False},
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
id=ElementIds.INVERT_MONTH_PSY,
- labelStyle={"flex": "30%"},
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
- html.Div(
- className="container-row align-center justify-center",
- children=[
- html.H6("Hour Range", style={"flex": "20%"}),
- html.Div(
+ dmc.Group(
+ [
+ dmc.Title("Hour Range", order=5),
+ dmc.Stack(
dcc.RangeSlider(
id=ElementIds.PSY_HOUR_SLIDER,
min=0,
@@ -138,75 +116,64 @@ def inputs():
step=1,
value=[0, 24],
marks={0: "0", 24: "24"},
- tooltip={
- "always_visible": False,
- "placement": "topLeft",
- },
- allowCross=False,
+ tooltip={"always_visible": False},
),
- style={"flex": "50%"},
+ flex=1,
),
dcc.Checklist(
- options=[
- {"label": "Invert", "value": "invert"},
- ],
- value=[],
id=ElementIds.INVERT_HOUR_PSY,
- labelStyle={"flex": "30%"},
+ options=[{"label": "Invert", "value": "invert"}],
+ value=[],
),
],
),
],
),
- html.Div(
- className=container_col_center_one_of_three,
- children=[
- dbc.Button(
+ dmc.Stack(
+ [
+ dmc.Button(
"Apply filter",
- color="primary",
id=ElementIds.DATA_FILTER,
- className="mb-2",
- n_clicks=0,
+ color="blue",
),
- html.Div(
- 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.Group(
+ [
+ dmc.Title("Filter Variable:", order=5),
+ dmc.Stack(
+ dropdown(
+ id=ElementIds.PSY_VAR_DROPDOWN,
+ options=dropdown_names,
+ value=ColNames.RH,
+ ),
+ flex=1,
),
],
),
- html.Div(
- 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%"},
+ 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,
),
],
),
- html.Div(
- 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.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,
),
],
),
@@ -217,25 +184,29 @@ def inputs():
def layout():
- return (
- html.Div(
- children=title_with_link(
+ return dmc.Stack(
+ p="md",
+ children=[
+ title_with_link(
text="Psychrometric Chart",
id_button=IdButtons.PSYCHROMETRIC_CHART_CHART,
doc_link=DocLinks.PSYCHROMETRIC_CHART,
),
- ),
- dcc.Loading(
- type="circle",
- children=html.Div(
- className="container-col",
- children=[inputs(), html.Div(id=ElementIds.PSYCH_CHART)],
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ children=[
+ inputs(),
+ dmc.Paper(
+ id=ElementIds.PSYCH_CHART,
+ ),
+ ],
+ ),
),
- ),
+ ],
)
-# psychrometric chart
@callback(
Output(ElementIds.PSYCH_CHART, "children"),
[
@@ -293,7 +264,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",
@@ -357,7 +328,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 86c22d8..63db215 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
@@ -39,8 +38,8 @@
def layout():
"""Contents in the first tab 'Select Weather File'"""
- return html.Div(
- className="container-col tab-container",
+ return dmc.Stack(
+ p="md",
children=[
dcc.Loading(
id=ElementIds.LOADING_ONE,
@@ -50,49 +49,59 @@ 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",
+ style={"borderStyle": "dashed"},
+ styles={"label": {"fontWeight": 400}},
),
# Allow multiple files to be uploaded
multiple=True,
- className="d-grid",
+ style={"display": "grid"},
),
dmc.Skeleton(
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(
- [
- 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",
+ color="gray",
+ variant="outline",
),
- dbc.Button(
+ dmc.Button(
"Yes",
id=ElementIds.MODAL_YES_BUTTON,
- className="ml-2",
- color="primary",
+ color="blue",
),
- ]
+ ],
+ justify="flex-end",
),
],
- id=ElementIds.MODAL,
- is_open=False,
),
],
)
@@ -100,12 +109,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"},
)
@@ -115,7 +123,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"),
],
@@ -149,7 +157,7 @@ def submitted_data(
None,
True,
messages_alert["not_available"],
- "warning",
+ "orange",
)
location_info = get_location_info(
lines, url_store
@@ -159,7 +167,7 @@ def submitted_data(
lines,
True,
messages_alert["success"],
- "success",
+ "green",
)
elif (
@@ -183,7 +191,7 @@ def submitted_data(
lines,
True,
messages_alert["success"],
- "success",
+ "green",
)
else:
return (
@@ -191,7 +199,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}")
@@ -200,7 +208,7 @@ def submitted_data(
None,
True,
messages_alert["wrong_extension"],
- "warning",
+ "orange",
)
raise PreventUpdate
@@ -236,15 +244,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 +275,7 @@ def enable_tabs_when_data_is_loaded(meta, data):
True,
True,
True,
+ True, # changelog always disabled
default,
)
else:
@@ -279,13 +289,14 @@ def enable_tabs_when_data_is_loaded(meta, data):
False,
False,
False,
+ True, # changelog always disabled
"Current Location: " + meta[ColNames.CITY] + ", " + meta[ColNames.COUNTRY],
)
@callback(
[
- Output(ElementIds.MODAL, "is_open"),
+ Output(ElementIds.MODAL, "opened"),
Output(ElementIds.ID_SELECT_URL_STORE, "data"),
],
[
@@ -293,30 +304,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
+ 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?"]
@@ -324,7 +330,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)
@@ -374,10 +380,8 @@ 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),
)
diff --git a/pages/summary.py b/pages/summary.py
index de5412d..7f11a30 100644
--- a/pages/summary.py
+++ b/pages/summary.py
@@ -1,11 +1,11 @@
import dash
-import dash_bootstrap_components as dbc
-from dash.exceptions import PreventUpdate
-from dash_extensions.enrich import dcc, html, 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
@@ -35,13 +35,7 @@
def layout():
"""Contents in the second tab 'Climate Summary'."""
- return html.Div(
- className="container-col",
- id=ElementIds.TAB_TWO_CONTAINER,
- children=[
- #
- ],
- )
+ return dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, p="md")
@callback(
@@ -56,134 +50,112 @@ def update_layout(si_ip):
heating_setpoint = 50
cooling_setpoint = 64
- return html.Div(
- className="container-col",
+ return dmc.Stack(
id=ElementIds.TAB2_SCE1_CONTAINER,
children=[
dcc.Loading(
type="circle",
- children=html.Div(
- className="container-col",
+ children=dmc.Stack(
id=ElementIds.LOCATION_INFO,
- style={"padding": "12px"},
+ gap=0,
),
),
dcc.Loading(
type="circle",
- children=html.Div(className="tab-two-section", id=ElementIds.WORLD_MAP),
+ children=dmc.Stack(id=ElementIds.WORLD_MAP),
),
- html.Div(
- 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",
+ children=dmc.Group(
+ children=[
+ dmc.Button(
+ "Download EPW",
+ id=ElementIds.DOWN_EPW_BUTTON,
+ color="blue",
+ variant="filled",
),
- dbc.Col(
- dbc.Button(
- "Download Clima dataframe",
- color="primary",
- id=ElementIds.DOWNLOAD_BUTTON,
- ),
- width="auto",
- ),
- 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),
],
),
),
- html.Div(
- 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),
+ dmc.Group(
+ justify="center",
+ 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=html.Div(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER),
+ children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER),
),
- html.Div(
- 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={"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},
+ ),
],
),
],
@@ -207,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"),
@@ -228,202 +197,173 @@ 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 = 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
- )
- 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),
- ],
+ average_yearly_tmp = (
+ f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} {tmp_unit}"
)
-
- return location_info
+ 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.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(
[
Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"),
- Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is_open"),
+ 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 = False
- if cdd_setpoint < hdd_setpoint:
- warning_setpoint = True
-
- 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)
- hdd = hdd / 24
- hdd = int(hdd)
- hdd_array.append(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)
-
- 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
- data = [trace2, trace1]
+ if isinstance(df, (list, tuple, dict)):
+ df = pd.DataFrame(df)
- fig = go.Figure(
- data=data,
- )
- 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
- ),
- )
+ 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()
- fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
- fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
+ for i in range(1, 13):
+ query_month = "month=="
- custom_inputs = f"{hdd_value}-{cdd_value}"
- units = generate_units_degree(si_ip)
+ 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))
- chart = dcc.Graph(
- id=ElementIds.DEGREE_DAYS_CHART,
- config=generate_chart_name(TabNames.HDD_CDD, meta, custom_inputs, units),
- figure=fig,
+ 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),
+ )
+ 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,
)
+ if warning_setpoint
+ else None
+ )
- return chart, warning_setpoint
+ return chart, alert_children
@callback(
@@ -442,7 +382,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),
)
@@ -465,7 +404,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),
)
@@ -488,7 +426,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),
)
@@ -511,7 +448,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 919655f..5307acc 100644
--- a/pages/sun.py
+++ b/pages/sun.py
@@ -2,9 +2,10 @@
from pages.lib.global_element_ids import ElementIds
import dash
-import dash_bootstrap_components as dbc
+import dash_mantine_components as dmc
+
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
@@ -53,27 +54,29 @@
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 html.Div(
- className="container-col justify-center",
+ return dmc.Stack(
children=[
- html.Div(
- 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",
children=[
- html.H6(
- className="text-next-to-input",
- children=["View: "],
- style={"width": "10rem"},
- ),
+ dmc.Title("View: ", order=5),
dropdown(
id=ElementIds.CUSTOM_SUN_VIEW_DROPDOWN,
options={
@@ -81,31 +84,25 @@ def sun_path():
"Cartesian": "cartesian",
},
value="polar",
- style={"width": "10rem"},
),
],
),
- dbc.Row(
+ dmc.Group(
align="center",
justify="center",
children=[
- html.H6(
- className="text-next-to-input",
- children=["Select variable: "],
- style={"width": "10rem"},
- ),
+ dmc.Title("Select Variable: ", order=5),
dropdown(
id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN,
options=sc_dropdown_names,
value="None",
- style={"width": "20rem"},
),
],
),
- dcc.Loading(
- type="circle",
- children=html.Div(
- id=ElementIds.CUSTOM_SUNPATH,
+ dmc.Center(
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"),
),
),
],
@@ -114,60 +111,45 @@ def sun_path():
def explore_daily_heatmap():
"""Contents of the bottom part of the tab"""
- return html.Div(
- className="container-col full-width",
+ return dmc.Stack(
+ w="100%",
children=[
- html.Div(
- 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,
),
- html.Div(
- className="container-row justify-center align-center mb-2",
+ dmc.Group(
+ align="center",
+ justify="center",
children=[
- html.H6(
- className="text-next-to-input",
- children=["Select variable: "],
- style={"width": "10rem"},
- ),
+ dmc.Title("Select variable: ", order=5),
dropdown(
id=ElementIds.TAB_EXPLORE_DROPDOWN,
options=sun_cloud_tab_explore_dropdown_names,
value="glob_hor_rad",
- style={"width": "20rem"},
),
],
),
- dcc.Loading(type="circle", children=html.Div(id=ElementIds.TAB4_DAILY)),
+ dcc.Loading(type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY)),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.TAB4_HEATMAP),
+ children=dmc.Stack(id=ElementIds.TAB4_HEATMAP),
),
],
)
def static_section():
- return html.Div(
+ return dmc.Stack(
id=ElementIds.STATIC_SECTION,
- className="container-col full-width",
+ w="100%",
children=[
# ...
],
)
-def layout():
- """Contents of tab four."""
- return html.Div(
- className="container-col",
- 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")],
@@ -177,27 +159,23 @@ def update_static_section(si_ip):
if si_ip == UnitSystem.IP:
hor_unit = "Btu/ft²"
return [
- html.Div(
- 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=html.Div(id=ElementIds.MONTHLY_SOLAR),
+ children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR),
),
- html.Div(
- 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=html.Div(id=ElementIds.CLOUD_COVER),
+ children=dmc.Stack(id=ElementIds.CLOUD_COVER),
),
]
@@ -237,11 +215,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,
)
@@ -267,6 +247,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
),
@@ -274,6 +255,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
),
@@ -299,6 +281,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),
)
@@ -321,6 +304,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 6729a31..a9c01b6 100644
--- a/pages/t_rh.py
+++ b/pages/t_rh.py
@@ -1,5 +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
@@ -30,71 +32,56 @@
def layout():
- return html.Div(
- className="container-col full-width",
+ return dmc.Stack(
+ p="md",
children=[
- html.Div(
- className="container-row full-width align-center justify-center",
- children=[
- html.H4(
- className="text-next-to-input", children=["Select a variable: "]
- ),
+ dmc.Center(
+ [
+ dmc.Title("Select a variable:", order=5, mr="md"),
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]],
),
- ],
+ ]
),
- html.Div(
- className="container-col",
- children=[
- html.Div(
- children=title_with_link(
- text="Yearly Chart",
- id_button=IdButtons.YEARLY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- ),
- dcc.Loading(
- type="circle",
- children=html.Div(id=ElementIds.YEARLY_CHART),
- ),
- html.Div(
- children=title_with_link(
- text="Daily chart",
- id_button=IdButtons.DAILY_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- ),
- dcc.Loading(
- type="circle",
- children=html.Div(id=ElementIds.DAILY),
- ),
- html.Div(
- children=title_with_link(
- text="Heatmap chart",
- id_button=IdButtons.HEATMAP_CHART_LABEL,
- doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED,
- ),
- ),
- dcc.Loading(
- type="circle",
- children=html.Div(id=ElementIds.HEATMAP),
- ),
- html.Div(
- children=title_with_tooltip(
- text="Descriptive statistics",
- tooltip_text="count, mean, std, min, max, and percentiles",
- id_button=IdButtons.TABLE_TMP_RH,
- ),
- ),
- html.Div(
- 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),
],
)
@@ -192,7 +179,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"),
@@ -205,7 +192,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(
@@ -260,7 +247,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 8b74444..195f4b6 100644
--- a/pages/wind.py
+++ b/pages/wind.py
@@ -1,10 +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
@@ -29,14 +30,12 @@
def sliders():
"""Returns 2 sliders for the hour"""
- return html.Div(
- className="container-col justify-center",
+ return dmc.Stack(
id=ElementIds.SLIDER_CONTAINER,
children=[
- html.Div(
- className="container-row each-slider",
+ dmc.Group(
children=[
- html.P("Month Range"),
+ dmc.Title("Month Range", order=5),
dcc.RangeSlider(
id=ElementIds.MONTH_SLIDER,
min=1,
@@ -49,10 +48,9 @@ def sliders():
),
],
),
- html.Div(
- className="container-row each-slider",
+ dmc.Group(
children=[
- html.P("Hour Range"),
+ dmc.Title("Hour Range", order=5),
dcc.RangeSlider(
id=ElementIds.HOUR_SLIDER,
min=1,
@@ -71,87 +69,71 @@ def sliders():
def seasonal_wind_rose():
"""Return the section with the 4 seasonal wind rose graphs."""
- return html.Div(
- className="container-col",
+ return dmc.Stack(
children=[
- html.Div(
- 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,
),
- html.Div(
- className=container_row_center_full,
+ dmc.Grid(
+ gutter="md",
children=[
- html.Div(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=html.Div(
- id=ElementIds.WINTER_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.WINTER_WIND_ROSE,
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.WINTER_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.WINTER_WIND_ROSE_TEXT),
+ ],
+ ),
),
- html.Div(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=html.Div(
- id=ElementIds.SPRING_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.SPRING_WIND_ROSE,
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.SPRING_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.SPRING_WIND_ROSE_TEXT),
+ ],
+ ),
),
- ],
- ),
- html.Div(
- className=container_row_center_full,
- children=[
- html.Div(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=html.Div(
- id=ElementIds.SUMMER_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.SUMMER_WIND_ROSE,
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.SUMMER_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.SUMMER_WIND_ROSE_TEXT),
+ ],
+ ),
),
- html.Div(
- className="container-col",
- children=[
- dcc.Loading(
- type="circle",
- children=html.Div(
- id=ElementIds.FALL_WIND_ROSE,
- className="daily-wind-graph",
+ dmc.GridCol(
+ span=6,
+ children=dmc.Stack(
+ children=[
+ dcc.Loading(
+ type="circle",
+ children=dmc.Stack(
+ id=ElementIds.FALL_WIND_ROSE,
+ ),
),
- ),
- html.P(
- className="seasonal-text",
- id=ElementIds.FALL_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.FALL_WIND_ROSE_TEXT),
+ ],
+ ),
),
],
),
@@ -161,74 +143,57 @@ def seasonal_wind_rose():
def daily_wind_rose():
"""Return the section for the 3 daily wind rose graphs."""
- return html.Div(
- className="container-col full-width",
+ return dmc.Stack(
id=ElementIds.TAB5_DAILY_CONTAINER,
children=[
- html.Div(
- 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,
),
- html.Div(
- id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER,
- className="container-row full-width",
+ dmc.Grid(
children=[
- html.Div(
- className="container-col",
- children=[
- html.Div(
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ children=[
dcc.Loading(
type="circle",
- children=html.Div(
- className="daily-wind-graph",
+ children=dmc.Stack(
id=ElementIds.MORNING_WIND_ROSE,
),
),
- ),
- html.P(
- className="daily-text",
- id=ElementIds.MORNING_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.MORNING_WIND_ROSE_TEXT),
+ ],
+ ),
),
- html.Div(
- className="container-col",
- children=[
- html.Div(
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ children=[
dcc.Loading(
type="circle",
- children=html.Div(
- className="daily-wind-graph",
+ children=dmc.Stack(
id=ElementIds.NOON_WIND_ROSE,
),
),
- ),
- html.P(
- className="daily-text",
- id=ElementIds.NOON_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.NOON_WIND_ROSE_TEXT),
+ ],
+ ),
),
- html.Div(
- className="container-col",
- children=[
- html.Div(
+ dmc.GridCol(
+ span=4,
+ children=dmc.Stack(
+ children=[
dcc.Loading(
type="circle",
- children=html.Div(
- className="daily-wind-graph",
+ children=dmc.Stack(
id=ElementIds.NIGHT_WIND_ROSE,
),
),
- ),
- html.P(
- className="daily-text",
- id=ElementIds.NIGHT_WIND_ROSE_TEXT,
- ),
- ],
+ dmc.Text(id=ElementIds.NIGHT_WIND_ROSE_TEXT),
+ ],
+ ),
),
],
),
@@ -237,103 +202,98 @@ def daily_wind_rose():
def custom_wind_rose():
- return html.Div(
- className="container-col justify-center full-width",
+ return dmc.Stack(
children=[
- html.Div(
- 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,
),
- html.Div(
- className="container-row full-width justify-center",
- id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER,
+ dmc.Grid(
+ gutter="md",
+ maw=900,
+ mx="auto",
children=[
- html.Div(
- className="container-col justify-center p-2 mr-2",
- children=[
- html.Div(
- 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"},
- ),
- ],
- ),
- html.Div(
- 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(
+ children=[
+ dmc.Group(
+ 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,
+ ),
+ ],
+ ),
+ dmc.Group(
+ children=[
+ dmc.Title(
+ "Start Hour:", order=5, w="8rem", ta="right"
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_START_HOUR,
+ options={
+ str(i) + ":00": i for i in range(0, 24)
+ },
+ value=0,
+ ),
+ ],
+ ),
+ ],
+ ),
),
- html.Div(
- className="container-col justify-center p-2 ml-2",
- children=[
- html.Div(
- 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"},
- ),
- ],
- ),
- html.Div(
- 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(
+ children=[
+ dmc.Group(
+ children=[
+ dmc.Title(
+ "End Month:", order=5, w="8rem", ta="right"
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_END_MONTH,
+ options={
+ j: i + 1
+ for i, j in enumerate(month_lst)
+ },
+ value=12,
+ ),
+ ],
+ ),
+ dmc.Group(
+ children=[
+ dmc.Title(
+ "End Hour:", order=5, w="8rem", ta="right"
+ ),
+ dropdown(
+ id=ElementIds.TAB5_CUSTOM_END_HOUR,
+ options={
+ str(i) + ":00": i for i in range(1, 25)
+ },
+ value=24,
+ ),
+ ],
+ ),
+ ],
+ ),
),
],
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.CUSTOM_WIND_ROSE),
+ children=dmc.Stack(id=ElementIds.CUSTOM_WIND_ROSE, maw=900, mx="auto"),
),
],
)
@@ -341,29 +301,25 @@ def custom_wind_rose():
def layout():
"""Contents in the fifth tab 'Wind'."""
- return html.Div(
- className="container-col justify-center",
+ return dmc.Stack(
+ p="md",
children=[
- html.Div(
- 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=html.Div(
- id=ElementIds.WIND_ROSE,
- ),
+ children=dmc.Stack(id=ElementIds.WIND_ROSE),
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.WIND_SPEED),
+ children=dmc.Stack(id=ElementIds.WIND_SPEED),
),
dcc.Loading(
type="circle",
- children=html.Div(id=ElementIds.WIND_DIRECTION),
+ children=dmc.Stack(id=ElementIds.WIND_DIRECTION),
),
seasonal_wind_rose(),
daily_wind_rose(),
@@ -372,7 +328,6 @@ def layout():
)
-# wind rose
@callback(
Output(ElementIds.WIND_ROSE, "children"),
Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"),
@@ -383,8 +338,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(
@@ -393,10 +346,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"),
@@ -408,8 +359,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(
@@ -418,13 +367,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"),
@@ -432,8 +377,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(
@@ -442,10 +385,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"),
@@ -462,14 +403,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)
@@ -482,6 +420,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
)
@@ -508,9 +447,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"),
@@ -524,18 +461,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]
@@ -564,8 +500,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(
@@ -592,6 +527,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(
@@ -618,7 +554,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"),
@@ -627,7 +562,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"),
@@ -636,20 +570,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])
@@ -675,21 +606,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/requirements.txt b/requirements.txt
index d2d7baa..14cdb68 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 8cb4133..38dea4f 100644
--- a/tests/node/cypress/e2e/spec.cy.js
+++ b/tests/node/cypress/e2e/spec.cy.js
@@ -1,7 +1,24 @@
+/*
+ ⚠️ 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)
+ // 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 });
+ cy.wait(500);
}
function load_epw() {
@@ -13,7 +30,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!');
@@ -26,7 +43,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');
@@ -101,9 +118,12 @@ 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', { timeout: 10000 }).click({ force: true });
cy.contains('-40'); // Global minimum: not something you see in Italy!
- cy.contains('IP').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)');
});